ops_utils.jira_util
Module for interacting with Jira tickets.
1"""Module for interacting with Jira tickets.""" 2import os 3import logging 4from atlassian 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 JiraUtil: 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_connection = 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(url=self.server, username=jira_user, password=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 self.jira_connection.issue_update(issue_key, field_update_dict) 75 76 def add_comment(self, issue_key: str, comment: str) -> None: 77 """ 78 Add a comment to a Jira ticket. 79 80 **Args:** 81 - issue_key (str): The issue key to update 82 - comment (str): The comment to add 83 """ 84 self.jira_connection.issue_add_comment(issue_key, comment) 85 86 def transition_ticket(self, issue_key: str, transition_id: int) -> None: 87 """ 88 Transition a Jira ticket to a new status. 89 90 **Args:** 91 - issue_key (str): The issue key to update 92 - transition_id (int): The status ID to transition the issue to 93 """ 94 self.jira_connection.set_issue_status_by_transition_id(issue_key, transition_id) 95 96 def get_issues_by_criteria( 97 self, 98 criteria: str, 99 max_results: int = 200, 100 fields: Optional[list[str]] = None, 101 expand_info: Optional[str] = None 102 ) -> list[dict]: 103 """ 104 Get all issues by defining specific criteria. 105 106 **Args:** 107 - criteria (str): The criteria to search for. This should be formatted in a supported JIRA search filter 108 (i.e. `project = '{project}' AND sprint = '{sprint_name}' AND status = {status}`) 109 - max_results (int): The maximum number of results to return. Defaults to 200. 110 - fields (list[string], optional): A list of the fields to include in the return for each 111 ticket (i.e. `["summary", "status", "assignee"]`) 112 113 **Returns:** 114 - list[dict]: The list of issues matching the criteria 115 """ 116 logging.info(f"Getting issues by criteria: {criteria}") 117 118 payload = { 119 "jql": criteria, 120 "maxResults": max_results 121 } 122 123 if fields: 124 payload["fields"] = fields 125 if expand_info: 126 payload["expand"] = expand_info 127 128 return self.jira_connection.post("rest/api/3/search/jql", data=payload)
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
JiraUtil:
15class JiraUtil: 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_connection = 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(url=self.server, username=jira_user, password=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 self.jira_connection.issue_update(issue_key, 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_connection.issue_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_connection.set_issue_status_by_transition_id(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): A list of 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 119 payload = { 120 "jql": criteria, 121 "maxResults": max_results 122 } 123 124 if fields: 125 payload["fields"] = fields 126 if expand_info: 127 payload["expand"] = expand_info 128 129 return self.jira_connection.post("rest/api/3/search/jql", data=payload)
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
JiraUtil(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_connection = 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 self.jira_connection.issue_update(issue_key, 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:
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_connection.issue_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:
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_connection.set_issue_status_by_transition_id(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]:
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): A list of 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 119 payload = { 120 "jql": criteria, 121 "maxResults": max_results 122 } 123 124 if fields: 125 payload["fields"] = fields 126 if expand_info: 127 payload["expand"] = expand_info 128 129 return self.jira_connection.post("rest/api/3/search/jql", data=payload)
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): A list of 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