import logging
import pandas as pd
from jira import JIRA
from jira.exceptions import JIRAError
DEFAULT_FIELDS = [
'created', 'creator', 'assignee', 'status', 'issuetype', 'priority', 'summary', 'description',
'resolution', 'resolutiondate'
]
logger = logging.getLogger(__name__)
[docs]class JiraClient:
[docs] def __init__(self, server, username, api_key):
"""
Client object for invoking JIRA Python API "https://pypi.org/project/jira/"
Parameters
----------
server: str
The server address and context path to use. Defaults to http://localhost:2990/jira
username : str
Username to establish a session via HTTP BASIC authentication.
api_key : str
API Key to establish a session via HTTP BASIC authentication.
"""
options = {'server': server}
self._client = JIRA(options, basic_auth=(username, api_key))
[docs] def transition_issue(self, issue_key, status):
"""
Perform a transition on an issue.
Parameters
----------
issue_key : str
ID or key of the issue to add the comment to (e.g. IM-1)
status : str
Name of the transition to perform
Raises
------
JiraError
If the issue does not exist or you do not have permission to see it.
KeyError
If transition does not exist for the issue.
Returns
------
None
"""
try:
issue = self._client.issue(issue_key)
transition_id = self._client.find_transitionid_by_name(issue_key, status)
if transition_id is None:
raise KeyError(
f'Error: Transition "{status}" does not exist for Issue {issue_key}.'
)
self._client.transition_issue(issue, transition_id)
except JIRAError:
logger.error(f'Error: Issue "{issue_key}" does not exist.')
raise
[docs] def get_issue_field(self, issue, field):
"""
Helper function that returns the value of the requested value for an issue. This function is used in the
get_issues() funtion to extract the fields of an issue.
Parameters
----------
issue : <class 'jira.resources.Issue'>
Issue Object of class <class 'jira.resources.Issue'>.
field : str
Name of the expected field. (e.g status).
Note: for a custom field, use the custom field id. (e.g. customfield_10028)
Raises
------
AttributeError
If the Field does not exist for the issue.
Returns
------
str
For a text field : returns a string with the value of the requested field. (e.g. 'Error')
For a field with multiple options : returns a string of comma separated values from all the options
available in the field. (for e.g 'User1,User2')
"""
try:
field_value = getattr(issue.fields, field)
if isinstance(field_value, list):
return (','.join(map(str, field_value)))
else:
return str(field_value)
except AttributeError:
logger.error(f'Field "{field}" does not exist for issue "{issue}."')
raise
[docs] def get_issues(self, project, fields=DEFAULT_FIELDS):
"""
Returns a Pandas DataFrame of all JIRA Issues for the requested project with values for
all requested fields.
Parameters
----------
project : str
Project name or key to pull issues from.
fields : list, optional
A list of fields to be extracted for each issue in the project.
(default is ['created', 'creator', 'assignee', 'status', 'issuetype', 'priority',
'summary', 'description', 'resolution', 'resolutiondate'])
Note: for a custom field, use the custom field id. (e.g. customfield_10028)
Raises
------
AttributeError
If the Field does not exist for the issue.
Returns
------
pandas DataFrame
A pandas DataFrame of all issues for the requested project with values for all the
requested fields.
The column header of the resulting DataFrame is of the form:
If "fields" is defined: ['project', 'key', field1, field2 , field3....]
(default) If "fields" is not defined: ['project', 'key' ,'created', 'creator',
'assignee','status', 'issuetype', 'priority', 'summary', 'description',
'resolution', 'resolutiondate']
"""
block_num = 0
block_size = 1000
# define base header list
header_list = ['project', 'key']
# extend the base header list to include requested fields
header_list.extend(fields)
# initialize lists
all_issues = []
issues_list = []
# Retrieve all issues from JIRA Project
issues = self._client.search_issues(f'project={project}', 0, block_size)
while len(issues) > 0:
all_issues.extend(issues)
block_num += 1
issues = self._client.search_issues(
f'project={project}', block_num * block_size, block_size
)
# Loop over all issues and extract project, issue and requested fields defined by the
# parameter fields
for issue in all_issues:
issue_details = [project, issue.key]
extract_fields = list(map(lambda n: self.get_issue_field(issue, n), fields))
issue_details.extend(extract_fields)
issues_list.append(issue_details)
df_issues = pd.DataFrame(issues_list, columns=header_list, index=None)
return df_issues