コード例 #1
0
def create_jira_task(host, user, password, assignee):
    jira = JIRA(basic_auth=(user, password), server=host)
    issue = jira.create_issue(project=JIRA_PROJECT,
                              summary=JIRA_ISSUE_SUMMARY,
                              description=JIRA_ISSUE_DESCRIPTION,
                              issuetype={'name': 'Task'})
    username = jira.search_users(assignee, maxResults=1)[0].name
    issue.update(assignee={'name': username})
    transition_id = jira.find_transitionid_by_name(issue.id, JIRA_ISSUE_STATUS)
    jira.transition_issue(issue.id, transition_id)
コード例 #2
0
class JiraClient:

    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))

    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

    def add_comment(self, issue_key, comment):
        """
        Add a comment from the current authenticated user on the specified issue and return a Resource for it.

        Parameters
        ----------
        issue_key : str
            ID or key of the issue to add the comment to (e.g. IM-1)
        comment : str
            Text of the comment to add

        Raises
        ------
        JiraError
            If the issue does not exist or you do not have permission to see it.

        Returns
        ------
        Resource object of <class 'jira.resources.Comment'> with Comment ID.
        """
        try:
            return self._client.add_comment(issue_key, comment)
        except JIRAError:
            logger.error(f'Error: Issue "{issue_key}" does not exist.')
            raise

    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

    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
コード例 #3
0
action('Creating branch ' + bold(branch))

try:
    if os.environ.get('JBRANCH_NO_SWITCH'):
        subprocess.check_output(['git', 'branch', branch],
                                stderr=subprocess.STDOUT)
    else:
        subprocess.check_output(['git', 'checkout', '-b', branch],
                                stderr=subprocess.STDOUT)
except OSError as e:
    error(str(e))
except subprocess.CalledProcessError as e:
    error(e.output.rstrip())
else:
    success()

transition = os.environ.get('JBRANCH_TRANSITION')
if transition:
    action('Triggering transition: ' + bold(transition))
    transition_id = jira.find_transitionid_by_name(issue, transition)
    if transition_id is None:
        error('the transition {0} is not available for this issue'.format(
            bold(transition)))
    try:
        jira.transition_issue(issue, transition_id)
    except JIRAError as jira_error:
        error(jira_error.text)
    else:
        success()
コード例 #4
0
ファイル: jira_api.py プロジェクト: ClearcodeHQ/jira-git-flow
class Jira(object):
    """JIRA objects and operations."""
    def __init__(self, url, username, token, project, max_results):
        self.jira = JIRA(url, basic_auth=(username, token))
        self.project = project
        self.max_results = max_results

    def search_issues(self, keyword, **kwargs):
        """
        Search Jira issues.

        Filtering by keyword is not done on JIRA query becasue it does not support
        filtering both summary and key by string values.
        """
        keyword = keyword.lower()
        query_parameters = []
        if kwargs.get('type'):
            query_parameters.append('type = "{}"'.format(kwargs.get('type')))

        query = ' OR'.join(query_parameters) + ' order by created desc'
        issues = self.jira.search_issues(query, maxResults=self.max_results)

        matching_issues = []
        for issue in issues:
            if keyword in issue.key.lower(
            ) or keyword in issue.fields.summary.lower():
                matching_issues.append(issue)

        return matching_issues

    def get_issue_by_key(self, key):
        """Get issue by key"""
        try:
            return self.jira.issue(key)
        except JIRAError as e:
            if e.status_code == 404:
                raise click.UsageError(
                    'The specified JIRA issue: {}, does not exist.'.format(
                        key))
            raise

    def create_issue(self, fields):
        return self.jira.create_issue(fields=fields)

    def get_resolution_by_name(self, name):
        resolutions = self.jira.resolutions()
        for r in resolutions:
            if r.name == name:
                return r.id
        return None

    def get_transition(self, issue, name):
        return self.jira.find_transitionid_by_name(issue, name)

    def transition_issue(self, issue, transition):
        transition_id = self.get_transition(issue, transition)
        if transition_id:
            self.jira.transition_issue(issue, transition_id)

    def assign_issue(self, issue, assignee):
        self.jira.assign_issue(issue, assignee)
コード例 #5
0
class JiraLocal(object):
    def __init__(self, jira_url, auth_user, auth_token, rule,
                 jira_fields_dict):
        self.jira_instance = JIRA(jira_url, basic_auth=(auth_user, auth_token))
        self.jira_config = rule['jira_config']
        self.jira_fields_dict = jira_fields_dict
        self.jira_issue_id_field_key = jira_fields_dict[
            rule['jira_config']["jira_issue_id_field"]]
        self.log = Logger(rule=rule)
        return

    def get_jira_issues(self, project_key, halo_issues):
        jira_issues_dict = {}
        with ThreadPoolExecutor(max_workers=os.cpu_count() * 2) as executor:
            future_to_issue_id = {
                executor.submit(self.get_jira_issues_for_halo_issue,
                                issue["id"], project_key): issue["id"]
                for issue in halo_issues
            }
            for future in as_completed(future_to_issue_id):
                issue_id = future_to_issue_id[future]
                jira_issues = future.result()
                jira_issues_dict[issue_id] = jira_issues
        return jira_issues_dict

    def get_jira_epics_or_issues(self,
                                 project_keys,
                                 issuetype,
                                 dict_format=True):
        if isinstance(project_keys, str):
            project_keys = [project_keys]
        jira_issues_dict = defaultdict(list)
        jira_issues = self.jira_instance.search_issues(
            f'project in ({", ".join(x for x in project_keys)}) AND '
            f'resolution = Unresolved AND '
            f'issuetype={issuetype} AND '
            f'"{self.jira_config["jira_issue_id_field"]}" is not EMPTY',
            maxResults=False)
        if not dict_format:
            return jira_issues
        for issue in jira_issues:
            jira_issues_dict[issue.raw["fields"][
                self.jira_issue_id_field_key]].append(issue)
        return jira_issues_dict

    def get_jira_issues_for_halo_issue(self, issue_id, project_key):
        results = self.jira_instance.search_issues(
            f'project="{project_key}" AND '
            f'"{self.jira_config["jira_issue_id_field"]}"~{issue_id} AND '
            f'issuetype="{self.jira_config["jira_issue_type"]}"')
        return results

    def create_jira_epic(self, group_key_hash, group_key_str, project_key):
        # Get IDs for epic fields
        epic_dict = {
            'project': {
                'key': project_key
            },
            'summary': group_key_str,
            self.jira_fields_dict["Epic Name"]: group_key_str,
            'description': group_key_str,
            'issuetype': {
                'name': 'Epic'
            },
            self.jira_issue_id_field_key: group_key_hash
        }
        epic = self.jira_instance.create_issue(fields=epic_dict)
        return epic

    def create_jira_issue(self, issue, epic, jira_fields_dict, fields,
                          project_key):
        epic_link = None
        if epic:
            epic_link = epic.key

        summary, description, field_mapping = self.prepare_issue(
            issue, fields, jira_fields_dict)

        issue_dict = {
            'project': {
                'key': project_key
            },
            'issuetype': {
                'name': self.jira_config['jira_issue_type']
            },
            self.jira_issue_id_field_key: issue["id"],
            self.jira_fields_dict["Epic Link"]: epic_link,
            'summary': summary,
            'description': description
        }

        issue_dict.update(field_mapping)
        self.log.info(f"Creating issue: {issue['id']}")
        self.jira_instance.create_issue(fields=issue_dict)

    def update_jira_issue(self, issue, jira_issues, jira_fields_dict, fields):
        self.log.info(f"Updating issue: {issue['id']}")
        for jira_issue in jira_issues:
            summary, description, field_mapping = self.prepare_issue(
                issue, fields, jira_fields_dict)
            issue_dict = {'summary': summary, 'description': description}
            issue_dict.update(field_mapping)
            jira_issue.update(fields=issue_dict)
            if issue["status"] == "resolved":
                self.transition_issue(jira_issue,
                                      self.jira_config["issue_status_closed"])
            elif jira_issue.raw["fields"]["status"][
                    "name"] == self.jira_config["issue_status_closed"]:
                self.transition_issue(
                    jira_issue, self.jira_config["issue_status_reopened"])

    def transition_issue(self, issue, transition_name):
        self.log.info(f"Transitioning issue {issue.key} to {transition_name}")
        transition_id = self.jira_instance.find_transitionid_by_name(
            issue, transition_name)
        try:
            self.jira_instance.transition_issue(issue, transition_id)
        except JIRAError:
            self.log.error(
                f"Could not transition Jira Issue '{issue.key}' "
                f"from {issue.raw['fields']['status']['name']} to {transition_name}"
            )

    def prepare_issue(self, issue, fields, jira_fields_dict):
        asset_formatted = Formatter.format_object(issue["asset_type"],
                                                  issue.pop("asset"))
        finding_formatted = Formatter.format_object("findings",
                                                    issue.pop("findings"))
        issue_formatted = Formatter.format_object("issue", issue)

        summary = Formatter.format_summary(issue)
        description = issue_formatted + asset_formatted + finding_formatted
        description = description[:32759] + '{code}\n\n'

        dynamic_map = fields.get("mapping") or {}
        static = fields.get("static") or {}
        field_mapping = map_fields(dynamic_map, static, issue,
                                   jira_fields_dict)

        return summary, description, field_mapping

    def push_issues(self,
                    issues,
                    jira_epics_dict,
                    jira_issues_dict,
                    jira_fields_dict,
                    fields,
                    project_key=None):
        with ThreadPoolExecutor(max_workers=os.cpu_count() * 2) as executor:
            for issue in issues:
                jira_issues = jira_issues_dict.get(issue["id"])
                if jira_issues:
                    executor.submit(self.update_jira_issue, issue, jira_issues,
                                    jira_fields_dict, fields)
                else:
                    groupby_key = issue.pop("groupby_key", "")
                    epic = jira_epics_dict.get(groupby_key)
                    executor.submit(self.create_jira_issue, issue, epic,
                                    jira_fields_dict, fields, project_key)

    def cleanup_epics(self, project_keys):
        jira_issues = self.get_jira_epics_or_issues(
            project_keys,
            self.jira_config["jira_issue_type"],
            dict_format=False)
        epics_set = set(issue.raw["fields"][self.jira_fields_dict["Epic Link"]]
                        for issue in jira_issues)
        jira_epics = self.get_jira_epics_or_issues(project_keys,
                                                   "Epic",
                                                   dict_format=False)

        with ThreadPoolExecutor(max_workers=os.cpu_count() * 2) as executor:
            for epic in jira_epics:
                if epic.key not in epics_set:
                    self.log.info(f"Deleting epic: {epic.key}")
                    executor.submit(self.transition_issue, epic,
                                    self.jira_config["issue_status_closed"])
コード例 #6
0
class Jira(object):
    """JIRA objects and operations."""
    def __init__(self,
                 instance,
                 project,
                 workflow,
                 connection_user,
                 max_results=50):
        self.workflow = workflow
        self.project = project
        self.username = instance.credentials.username
        self.max_results = max_results
        self.jira = JIRA(instance.url,
                         basic_auth=(connection_user,
                                     instance.credentials.token))

    def search_issues(self, keyword, **kwargs):
        """
        Search Jira issues.

        Filtering by keyword is not done on JIRA query becasue it does not support
        filtering both summary and key by string values.
        """
        keyword = keyword.lower()
        query_parameters = []
        if kwargs.get("types"):
            for type in kwargs.get("types"):
                query_parameters.append('type = "{}"'.format(type))

        query = " OR ".join(query_parameters) + " order by created desc"
        issues = self.jira.search_issues(query, maxResults=self.max_results)

        matching_issues = []
        for issue in issues:
            if keyword in issue.key.lower(
            ) or keyword in issue.fields.summary.lower():
                matching_issues.append(self._convert_to_issue(issue))

        return matching_issues

    def get_issue_by_key(self, key):
        """Get issue by key"""
        try:
            jira_issue = self.jira.issue(key)
            return self._convert_to_issue(jira_issue)

        except JIRAError as e:
            if e.status_code == 404:
                raise click.UsageError(
                    "The specified JIRA issue: {}, does not exist.".format(
                        key))
            raise

    def create_issue(self, fields):
        jira_issue = self.jira.create_issue(fields=fields)
        return self._convert_to_issue(jira_issue)

    def get_resolution_by_name(self, name):
        resolutions = self.jira.resolutions()
        for r in resolutions:
            if r.name == name:
                return r.id
        return None

    def get_transition(self, issue, name):
        return self.jira.find_transitionid_by_name(issue, name)

    def transition_issue(self, issue, transition):
        transition_id = self.get_transition(issue, transition)
        if transition_id:
            self.jira.transition_issue(issue, transition_id)

    def assign_issue(self, issue, assignee):
        self.jira.assign_issue(issue, assignee)

    def make_action(self, action, issue):
        issue.status = action.next_state
        # Get jira issue to perform transitions
        jira_issue = self.jira.issue(issue.key)
        for transition in action.transitions:
            self.transition_issue(jira_issue, transition)
        if action.assign_to_user:
            self.assign_issue(jira_issue, self.username)
        return issue

    def _convert_to_issue(self, jira_issue):
        """Convert to simplified issue."""
        i = Issue(
            jira_issue.key,
            jira_issue.fields.summary,
            self._get_type(jira_issue),
            self._get_status(jira_issue),
        )
        i.subtasks = self._get_subtasks(jira_issue)
        return i

    def _get_type(self, jira_issue):
        jira_type = jira_issue.fields.issuetype.name
        for type_mapping in self.workflow.types:
            if type_mapping.mapping == jira_type:
                return type_mapping.issue_type
        raise Exception(f"Unable to map issue type: {jira_type}")

    def _get_status(self, jira_issue):
        jira_status = jira_issue.fields.status.name
        for status_mapping in self.workflow.statuses:
            if jira_status in status_mapping.mapping:
                return status_mapping.status
        raise Exception(f"Unable to map issue status: {jira_status}")

    def _get_subtasks(self, jira_issue):
        try:
            return [
                self._convert_to_issue(subtask)
                for subtask in jira_issue.fields.subtasks
            ]
        except AttributeError as e:
            return []