ops_utils.jira_util

Module for interacting with Jira tickets.

  1"""Module for interacting with Jira tickets."""
  2import os
  3import logging
  4from jira import JIRA
  5from google.cloud import secretmanager
  6from typing import Optional
  7
  8WORKBENCH_SERVER = "https://broadworkbench.atlassian.net/"
  9"""The default Broad workbench server address"""
 10BROAD_INSTITUTE_SERVER = "https://broadinstitute.atlassian.net/"
 11"""The default Broad Jira server address"""
 12
 13
 14class Jira:
 15    """
 16    A class to assist in interacting with JIRA tickets.
 17    
 18    Provides functionality to help in updating tickets
 19    (adding comments, updating ticket fields, and transitioning statuses). Also provides a way to query
 20    existing JIRA tickets using certain filters. Assumes that an accessible JIRA API key is stored in
 21    Google's SecretManger
 22    """
 23
 24    def __init__(self, server: str, gcp_project_id: str, jira_api_key_secret_name: str) -> None:
 25        """
 26        Initialize the Jira instance using the provided server.
 27
 28        **Args:**
 29        - server (str): The server URL to connect to. For example: `https://broadinstitute.atlassian.net/`
 30        - gcp_project_id (str): The GCP project ID used to locate the Jira API key that is stored in SecretManager.
 31        - jira_api_key_secret_name (str): The name of the Jira API key secret that is stored in SecretManager.
 32        """
 33        self.server = server
 34        """@private"""
 35        self.gcp_project_id = gcp_project_id
 36        """@private"""
 37        self.jira_api_key_secret_name = jira_api_key_secret_name
 38        """@private"""
 39        self.jira = self._connect_to_jira()
 40        """@private"""
 41
 42    def _connect_to_jira(self) -> JIRA:
 43        """
 44        Obtain credentials and establish the Jira connection.
 45
 46        User must have token stored in ~/.jira_api_key
 47        """
 48        if os.getenv("RUN_IN_CLOUD") == "yes":
 49            jira_user = f'{os.getenv("JIRA_USER")}@broadinstitute.org'
 50        else:
 51            jira_user = f'{os.getenv("USER")}@broadinstitute.org'
 52
 53        # This is necessary for when scripts are using this utility in a Cloud Function
 54        try:
 55            jira_api_token_file_path = os.path.expanduser("~/.jira_api_key")
 56            with open(jira_api_token_file_path, "r") as token_file:
 57                token = token_file.read().strip()
 58        except FileNotFoundError:
 59            client = secretmanager.SecretManagerServiceClient()
 60            name = f"projects/{self.gcp_project_id}/secrets/{self.jira_api_key_secret_name}/versions/latest"
 61            token = client.access_secret_version(name=name).payload.data.decode("UTF-8")
 62
 63        return JIRA(server=self.server, basic_auth=(jira_user, token))
 64
 65    def update_ticket_fields(self, issue_key: str, field_update_dict: dict) -> None:
 66        """
 67        Update a Jira ticket with new field values.
 68
 69        **Args:**
 70        - issue_key (str): The issue key to update
 71        - field_update_dict (dict): The field values to update. Formatted with the field ID as the key,
 72        and the updated value as the key's value.
 73        """
 74        issue = self.jira.issue(issue_key)
 75        issue.update(fields=field_update_dict)
 76
 77    def add_comment(self, issue_key: str, comment: str) -> None:
 78        """
 79        Add a comment to a Jira ticket.
 80
 81        **Args:**
 82        - issue_key (str): The issue key to update
 83        - comment (str): The comment to add
 84        """
 85        self.jira.add_comment(issue_key, comment)
 86
 87    def transition_ticket(self, issue_key: str, transition_id: int) -> None:
 88        """
 89        Transition a Jira ticket to a new status.
 90
 91        **Args:**
 92        - issue_key (str): The issue key to update
 93        - transition_id (int): The status ID to transition the issue to
 94        """
 95        self.jira.transition_issue(issue_key, transition_id)
 96
 97    def get_issues_by_criteria(
 98            self,
 99            criteria: str,
100            max_results: int = 200,
101            fields: Optional[list[str]] = None,
102            expand_info: Optional[str] = None
103    ) -> list[dict]:
104        """
105        Get all issues by defining specific criteria.
106
107        **Args:**
108        - criteria (str): The criteria to search for. This should be formatted in a supported JIRA search filter
109        (i.e. `project = '{project}' AND sprint = '{sprint_name}' AND status = {status}`)
110        - max_results (int): The maximum number of results to return. Defaults to 200.
111        - fields (list[string], optional): The fields to include in the return for each
112        ticket (i.e. `["summary", "status", "assignee"]`)
113
114        **Returns:**
115        - list[dict]: The list of issues matching the criteria
116        """
117        logging.info(f"Getting issues by criteria: {criteria}")
118        if fields:
119            return self.jira.search_issues(criteria, fields=fields, maxResults=max_results, expand=expand_info)
120        return self.jira.search_issues(criteria, maxResults=max_results, expand=expand_info)
WORKBENCH_SERVER = 'https://broadworkbench.atlassian.net/'

The default Broad workbench server address

BROAD_INSTITUTE_SERVER = 'https://broadinstitute.atlassian.net/'

The default Broad Jira server address

class Jira:
 15class Jira:
 16    """
 17    A class to assist in interacting with JIRA tickets.
 18    
 19    Provides functionality to help in updating tickets
 20    (adding comments, updating ticket fields, and transitioning statuses). Also provides a way to query
 21    existing JIRA tickets using certain filters. Assumes that an accessible JIRA API key is stored in
 22    Google's SecretManger
 23    """
 24
 25    def __init__(self, server: str, gcp_project_id: str, jira_api_key_secret_name: str) -> None:
 26        """
 27        Initialize the Jira instance using the provided server.
 28
 29        **Args:**
 30        - server (str): The server URL to connect to. For example: `https://broadinstitute.atlassian.net/`
 31        - gcp_project_id (str): The GCP project ID used to locate the Jira API key that is stored in SecretManager.
 32        - jira_api_key_secret_name (str): The name of the Jira API key secret that is stored in SecretManager.
 33        """
 34        self.server = server
 35        """@private"""
 36        self.gcp_project_id = gcp_project_id
 37        """@private"""
 38        self.jira_api_key_secret_name = jira_api_key_secret_name
 39        """@private"""
 40        self.jira = self._connect_to_jira()
 41        """@private"""
 42
 43    def _connect_to_jira(self) -> JIRA:
 44        """
 45        Obtain credentials and establish the Jira connection.
 46
 47        User must have token stored in ~/.jira_api_key
 48        """
 49        if os.getenv("RUN_IN_CLOUD") == "yes":
 50            jira_user = f'{os.getenv("JIRA_USER")}@broadinstitute.org'
 51        else:
 52            jira_user = f'{os.getenv("USER")}@broadinstitute.org'
 53
 54        # This is necessary for when scripts are using this utility in a Cloud Function
 55        try:
 56            jira_api_token_file_path = os.path.expanduser("~/.jira_api_key")
 57            with open(jira_api_token_file_path, "r") as token_file:
 58                token = token_file.read().strip()
 59        except FileNotFoundError:
 60            client = secretmanager.SecretManagerServiceClient()
 61            name = f"projects/{self.gcp_project_id}/secrets/{self.jira_api_key_secret_name}/versions/latest"
 62            token = client.access_secret_version(name=name).payload.data.decode("UTF-8")
 63
 64        return JIRA(server=self.server, basic_auth=(jira_user, token))
 65
 66    def update_ticket_fields(self, issue_key: str, field_update_dict: dict) -> None:
 67        """
 68        Update a Jira ticket with new field values.
 69
 70        **Args:**
 71        - issue_key (str): The issue key to update
 72        - field_update_dict (dict): The field values to update. Formatted with the field ID as the key,
 73        and the updated value as the key's value.
 74        """
 75        issue = self.jira.issue(issue_key)
 76        issue.update(fields=field_update_dict)
 77
 78    def add_comment(self, issue_key: str, comment: str) -> None:
 79        """
 80        Add a comment to a Jira ticket.
 81
 82        **Args:**
 83        - issue_key (str): The issue key to update
 84        - comment (str): The comment to add
 85        """
 86        self.jira.add_comment(issue_key, comment)
 87
 88    def transition_ticket(self, issue_key: str, transition_id: int) -> None:
 89        """
 90        Transition a Jira ticket to a new status.
 91
 92        **Args:**
 93        - issue_key (str): The issue key to update
 94        - transition_id (int): The status ID to transition the issue to
 95        """
 96        self.jira.transition_issue(issue_key, transition_id)
 97
 98    def get_issues_by_criteria(
 99            self,
100            criteria: str,
101            max_results: int = 200,
102            fields: Optional[list[str]] = None,
103            expand_info: Optional[str] = None
104    ) -> list[dict]:
105        """
106        Get all issues by defining specific criteria.
107
108        **Args:**
109        - criteria (str): The criteria to search for. This should be formatted in a supported JIRA search filter
110        (i.e. `project = '{project}' AND sprint = '{sprint_name}' AND status = {status}`)
111        - max_results (int): The maximum number of results to return. Defaults to 200.
112        - fields (list[string], optional): The fields to include in the return for each
113        ticket (i.e. `["summary", "status", "assignee"]`)
114
115        **Returns:**
116        - list[dict]: The list of issues matching the criteria
117        """
118        logging.info(f"Getting issues by criteria: {criteria}")
119        if fields:
120            return self.jira.search_issues(criteria, fields=fields, maxResults=max_results, expand=expand_info)
121        return self.jira.search_issues(criteria, maxResults=max_results, expand=expand_info)

A class to assist in interacting with JIRA tickets.

Provides functionality to help in updating tickets (adding comments, updating ticket fields, and transitioning statuses). Also provides a way to query existing JIRA tickets using certain filters. Assumes that an accessible JIRA API key is stored in Google's SecretManger

Jira(server: str, gcp_project_id: str, jira_api_key_secret_name: str)
25    def __init__(self, server: str, gcp_project_id: str, jira_api_key_secret_name: str) -> None:
26        """
27        Initialize the Jira instance using the provided server.
28
29        **Args:**
30        - server (str): The server URL to connect to. For example: `https://broadinstitute.atlassian.net/`
31        - gcp_project_id (str): The GCP project ID used to locate the Jira API key that is stored in SecretManager.
32        - jira_api_key_secret_name (str): The name of the Jira API key secret that is stored in SecretManager.
33        """
34        self.server = server
35        """@private"""
36        self.gcp_project_id = gcp_project_id
37        """@private"""
38        self.jira_api_key_secret_name = jira_api_key_secret_name
39        """@private"""
40        self.jira = self._connect_to_jira()
41        """@private"""

Initialize the Jira instance using the provided server.

Args:

  • server (str): The server URL to connect to. For example: https://broadinstitute.atlassian.net/
  • gcp_project_id (str): The GCP project ID used to locate the Jira API key that is stored in SecretManager.
  • jira_api_key_secret_name (str): The name of the Jira API key secret that is stored in SecretManager.
def update_ticket_fields(self, issue_key: str, field_update_dict: dict) -> None:
66    def update_ticket_fields(self, issue_key: str, field_update_dict: dict) -> None:
67        """
68        Update a Jira ticket with new field values.
69
70        **Args:**
71        - issue_key (str): The issue key to update
72        - field_update_dict (dict): The field values to update. Formatted with the field ID as the key,
73        and the updated value as the key's value.
74        """
75        issue = self.jira.issue(issue_key)
76        issue.update(fields=field_update_dict)

Update a Jira ticket with new field values.

Args:

  • issue_key (str): The issue key to update
  • field_update_dict (dict): The field values to update. Formatted with the field ID as the key, and the updated value as the key's value.
def add_comment(self, issue_key: str, comment: str) -> None:
78    def add_comment(self, issue_key: str, comment: str) -> None:
79        """
80        Add a comment to a Jira ticket.
81
82        **Args:**
83        - issue_key (str): The issue key to update
84        - comment (str): The comment to add
85        """
86        self.jira.add_comment(issue_key, comment)

Add a comment to a Jira ticket.

Args:

  • issue_key (str): The issue key to update
  • comment (str): The comment to add
def transition_ticket(self, issue_key: str, transition_id: int) -> None:
88    def transition_ticket(self, issue_key: str, transition_id: int) -> None:
89        """
90        Transition a Jira ticket to a new status.
91
92        **Args:**
93        - issue_key (str): The issue key to update
94        - transition_id (int): The status ID to transition the issue to
95        """
96        self.jira.transition_issue(issue_key, transition_id)

Transition a Jira ticket to a new status.

Args:

  • issue_key (str): The issue key to update
  • transition_id (int): The status ID to transition the issue to
def get_issues_by_criteria( self, criteria: str, max_results: int = 200, fields: Optional[list[str]] = None, expand_info: Optional[str] = None) -> list[dict]:
 98    def get_issues_by_criteria(
 99            self,
100            criteria: str,
101            max_results: int = 200,
102            fields: Optional[list[str]] = None,
103            expand_info: Optional[str] = None
104    ) -> list[dict]:
105        """
106        Get all issues by defining specific criteria.
107
108        **Args:**
109        - criteria (str): The criteria to search for. This should be formatted in a supported JIRA search filter
110        (i.e. `project = '{project}' AND sprint = '{sprint_name}' AND status = {status}`)
111        - max_results (int): The maximum number of results to return. Defaults to 200.
112        - fields (list[string], optional): The fields to include in the return for each
113        ticket (i.e. `["summary", "status", "assignee"]`)
114
115        **Returns:**
116        - list[dict]: The list of issues matching the criteria
117        """
118        logging.info(f"Getting issues by criteria: {criteria}")
119        if fields:
120            return self.jira.search_issues(criteria, fields=fields, maxResults=max_results, expand=expand_info)
121        return self.jira.search_issues(criteria, maxResults=max_results, expand=expand_info)

Get all issues by defining specific criteria.

Args:

  • criteria (str): The criteria to search for. This should be formatted in a supported JIRA search filter (i.e. project = '{project}' AND sprint = '{sprint_name}' AND status = {status})
  • max_results (int): The maximum number of results to return. Defaults to 200.
  • fields (list[string], optional): The fields to include in the return for each ticket (i.e. ["summary", "status", "assignee"])

Returns:

  • list[dict]: The list of issues matching the criteria