diff --git a/docs/providers/documentation/jira-provider.mdx b/docs/providers/documentation/jira-provider.mdx index 89a5d8e5e..0ab6badcd 100644 --- a/docs/providers/documentation/jira-provider.mdx +++ b/docs/providers/documentation/jira-provider.mdx @@ -20,6 +20,7 @@ The `notify` function take following parameters as inputs: - `summary` (required): Incident/issue name or short description. - `description` (optional): Additional details related to the incident/issue. - `issue_type` (optional): Issue type name. For example: `Story`, `Bug` etc +- `issue_id` (optional): When you want to update an existing issue, provide the issue id. ## Outputs diff --git a/examples/workflows/update_jira_ticket.yml b/examples/workflows/update_jira_ticket.yml new file mode 100644 index 000000000..b445144aa --- /dev/null +++ b/examples/workflows/update_jira_ticket.yml @@ -0,0 +1,15 @@ +workflow: + id: update-jira-ticket + triggers: + - type: manual + actions: + - name: jira-action + provider: + config: '{{ providers.Jira }}' + type: jira + with: + board_name: '' + description: Update description of an issue + issue_id: 10023 + project_key: '' + summary: Update summary of an issue diff --git a/keep/providers/jira_provider/jira_provider.py b/keep/providers/jira_provider/jira_provider.py index 3a6488184..b79216594 100644 --- a/keep/providers/jira_provider/jira_provider.py +++ b/keep/providers/jira_provider/jira_provider.py @@ -304,6 +304,67 @@ def __create_issue( return {"issue": response.json()} except Exception as e: raise ProviderException(f"Failed to create an issue: {e}") + + def __update_issue( + self, + issue_id: str, + summary: str, + description: str = "", + labels: List[str] = None, + components: List[str] = None, + custom_fields: dict = None, + **kwargs: dict, + ): + """ + Helper method to update an issue in jira. + """ + try: + self.logger.info("Updating an issue...") + + url = self.__get_url(paths=["issue", issue_id]) + + update = { } + + if summary: + update["summary"] = [{"set": summary}] + + if description: + update["description"] = [{"set": description}] + + if components: + update["components"] = [{"set": component} for component in components] + + if labels: + update["labels"] = [{"set": label} for label in labels] + + if custom_fields: + update.update(custom_fields) + + request_body = { "update": update } + + response = requests.put( + url=url, json=request_body, auth=self.__get_auth(), verify=False + ) + + try: + if response.status_code != 204: + response.raise_for_status() + except Exception: + self.logger.exception( + "Failed to update an issue", extra=response.text + ) + raise ProviderException("Failed to update an issue") + self.logger.info("Updated an issue!") + return { + "issue": { + "id": issue_id, + "key": self._extract_issue_key_from_issue_id(issue_id), + "self": self.__get_url(paths=["issue", issue_id]), + } + } + + except Exception as e: + raise ProviderException(f"Failed to update an issue: {e}") def _extract_project_key_from_board_name(self, board_name: str): boards_response = requests.get( @@ -328,6 +389,19 @@ def _extract_project_key_from_board_name(self, board_name: str): ) else: raise Exception("Could not fetch boards: " + boards_response.text) + + def _extract_issue_key_from_issue_id(self, issue_id: str): + issue_key = requests.get( + f"{self.jira_host}/rest/api/2/issue/{issue_id}", + auth=self.__get_auth(), + headers={"Accept": "application/json"}, + verify=False, + ) + + if issue_key.status_code == 200: + return issue_key.json()["key"] + else: + raise Exception("Could not fetch issue key: " + issue_key.text) def _notify( self, @@ -336,6 +410,7 @@ def _notify( issue_type: str = "", project_key: str = "", board_name: str = "", + issue_id: str = None, labels: List[str] = None, components: List[str] = None, custom_fields: dict = None, @@ -344,18 +419,37 @@ def _notify( """ Notify jira by creating an issue. """ - # if the user didn't provider a project_key, try to extract it from the board name - if not project_key: - project_key = self._extract_project_key_from_board_name(board_name) issue_type = issue_type if issue_type else kwargs.get("issuetype", "Task") - if not project_key or not summary or not issue_type or not description: - raise ProviderException( - f"Project key and summary are required! - {project_key}, {summary}, {issue_type}, {description}" - ) if labels and isinstance(labels, str): labels = json.loads(labels.replace("'", '"')) try: self.logger.info("Notifying jira...") + + if issue_id: + result = self.__update_issue( + issue_id=issue_id, + summary=summary, + description=description, + labels=labels, + components=components, + custom_fields=custom_fields, + **kwargs, + ) + + issue_key = self._extract_issue_key_from_issue_id(issue_id) + + result["ticket_url"] = f"{self.jira_host}/browse/{issue_key}" + + self.logger.info("Updated a jira issue: " + str(result)) + return result + + if not project_key: + project_key = self._extract_project_key_from_board_name(board_name) + if not project_key or not summary or not issue_type or not description: + raise ProviderException( + f"Project key and summary are required! - {project_key}, {summary}, {issue_type}, {description}" + ) + result = self.__create_issue( project_key=project_key, summary=summary, diff --git a/keep/providers/jiraonprem_provider/jiraonprem_provider.py b/keep/providers/jiraonprem_provider/jiraonprem_provider.py index 479a5c9e4..726c4597b 100644 --- a/keep/providers/jiraonprem_provider/jiraonprem_provider.py +++ b/keep/providers/jiraonprem_provider/jiraonprem_provider.py @@ -323,6 +323,78 @@ def __create_issue( return {"issue": response.json()} except Exception as e: raise ProviderException(f"Failed to create an issue: {e}") + + def __update_issue( + self, + issue_id: str, + summary: str = "", + description: str = "", + priority: str = "Medium", + labels: List[str] = None, + components: List[str] = None, + custom_fields: dict = None, + **kwargs: dict, + ): + """ + Helper method to update an issue in jira. + """ + try: + self.logger.info("Updating an issue...") + + url = self.__get_url(paths=["issue", issue_id]) + + update = { } + + if summary: + update["summary"] = [{"set": summary}] + + if description: + update["description"] = [{"set": description}] + + if priority: + update["priority"] = [{"set": {"name": priority}}] + + if components: + update["components"] = [{"set": [{"name": component} for component in components]}] + + if labels: + update["labels"] = [{"set": label} for label in labels] + + if custom_fields: + update.update(custom_fields) + + request_body = {"update": update} + + response = requests.put( + url=url, + json=request_body, + headers=self.__get_auth_header(), + verify=False, + timeout=10, + ) + + try: + if response.status_code != 204: + response.raise_for_status() + except Exception: + self.logger.exception( + "Failed to update an issue", extra=response.text + ) + raise ProviderException("Failed to update an issue") + + result = { + "issue": { + "id": issue_id, + "key": self._extract_issue_key_from_issue_id(issue_id), + "self": self.__get_url(paths=["issue", issue_id]), + } + } + + self.logger.info("Updated an issue!") + return result + + except Exception as e: + raise ProviderException(f"Failed to update an issue: {e}") def _extract_project_key_from_board_name(self, board_name: str): headers = { @@ -385,6 +457,24 @@ def _extract_project_key_from_board_name(self, board_name: str): ) else: raise Exception("Could not fetch boards: " + boards_response.text) + + def _extract_issue_key_from_issue_id(self, issue_id: str): + headers = { + "Accept": "application/json", + } + headers.update(self.__get_auth_header()) + + issue_key = requests.get( + f"{self.jira_host}/rest/api/2/issue/{issue_id}", + headers=headers, + verify=False, + timeout=10, + ) + + if issue_key.status_code == 200: + return issue_key.json()["key"] + else: + raise Exception("Could not fetch issue key: " + issue_key.text) def _notify( self, @@ -393,6 +483,7 @@ def _notify( issue_type: str = "", project_key: str = "", board_name: str = "", + issue_id: str = None, labels: List[str] = None, components: List[str] = None, custom_fields: dict = None, @@ -403,17 +494,38 @@ def _notify( Notify jira by creating an issue. """ # if the user didn't provider a project_key, try to extract it from the board name - if not project_key: - project_key = self._extract_project_key_from_board_name(board_name) issue_type = issue_type if issue_type else kwargs.get("issuetype", "Task") - if not project_key or not summary or not issue_type or not description: - raise ProviderException( - f"Project key and summary are required! - {project_key}, {summary}, {issue_type}, {description}" - ) if labels and isinstance(labels, str): labels = json.loads(labels.replace("'", '"')) try: self.logger.info("Notifying jira...") + + if issue_id: + result = self.__update_issue( + issue_id=issue_id, + summary=summary, + description=description, + labels=labels, + components=components, + custom_fields=custom_fields, + priority=priority, + **kwargs, + ) + + issue_key = self._extract_issue_key_from_issue_id(issue_id) + + result["ticket_url"] = f"{self.jira_host}/browse/{issue_key}" + + self.logger.info("Updated a jira issue: " + str(result)) + return result + + if not project_key: + project_key = self._extract_project_key_from_board_name(board_name) + if not project_key or not summary or not issue_type or not description: + raise ProviderException( + f"Project key and summary are required! - {project_key}, {summary}, {issue_type}, {description}" + ) + result = self.__create_issue( project_key=project_key, summary=summary,