From 895fa7e3b72f3d0c675d62ab6a1b821d9131da92 Mon Sep 17 00:00:00 2001 From: lordsarcastic Date: Thu, 9 Jan 2025 13:10:23 +0100 Subject: [PATCH] [Integration][Jira] Fix webhook does not respect selector parameters (#1299) --- integrations/jira/CHANGELOG.md | 8 +++ integrations/jira/jira/client.py | 2 +- integrations/jira/main.py | 95 +++++++++++++++++++++----------- integrations/jira/pyproject.toml | 2 +- 4 files changed, 73 insertions(+), 34 deletions(-) diff --git a/integrations/jira/CHANGELOG.md b/integrations/jira/CHANGELOG.md index d8b32230c7..d866095440 100644 --- a/integrations/jira/CHANGELOG.md +++ b/integrations/jira/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## 0.2.18 (2025-01-08) + + +### Bug Fixes + +- Fixed a bug where webhook processes issues that are not allowed in a user's integration due to JQL filters + + ## 0.2.17 (2025-01-08) diff --git a/integrations/jira/jira/client.py b/integrations/jira/jira/client.py index 9cb037fe11..8cf1510f21 100644 --- a/integrations/jira/jira/client.py +++ b/integrations/jira/jira/client.py @@ -5,6 +5,7 @@ from port_ocean.context.ocean import ocean from port_ocean.utils import http_async_client + PAGE_SIZE = 50 WEBHOOK_NAME = "Port-Ocean-Events-Webhook" @@ -144,7 +145,6 @@ async def get_paginated_issues( self, params: dict[str, Any] = {} ) -> AsyncGenerator[list[dict[str, Any]], None]: logger.info("Getting issues from Jira") - params.update(self._generate_base_req_params()) total_issues = (await self._get_paginated_issues(params))["total"] diff --git a/integrations/jira/main.py b/integrations/jira/main.py index b5983b14a3..b07814cb70 100644 --- a/integrations/jira/main.py +++ b/integrations/jira/main.py @@ -8,7 +8,12 @@ from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE from jira.client import JiraClient -from jira.overrides import JiraIssueConfig, JiraProjectResourceConfig +from jira.overrides import ( + JiraIssueConfig, + JiraIssueSelector, + JiraPortAppConfig, + JiraProjectResourceConfig, +) class ObjectKind(StrEnum): @@ -17,6 +22,15 @@ class ObjectKind(StrEnum): USER = "user" +def create_jira_client() -> JiraClient: + """Create JiraClient with current configuration.""" + return JiraClient( + ocean.integration_config["jira_host"], + ocean.integration_config["atlassian_user_email"], + ocean.integration_config["atlassian_user_token"], + ) + + async def setup_application() -> None: logic_settings = ocean.integration_config app_host = logic_settings.get("app_host") @@ -27,11 +41,7 @@ async def setup_application() -> None: ) return - jira_client = JiraClient( - logic_settings["jira_host"], - logic_settings["atlassian_user_email"], - logic_settings["atlassian_user_token"], - ) + jira_client = create_jira_client() await jira_client.create_events_webhook( logic_settings["app_host"], @@ -40,11 +50,7 @@ async def setup_application() -> None: @ocean.on_resync(ObjectKind.PROJECT) async def on_resync_projects(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - client = JiraClient( - ocean.integration_config["jira_host"], - ocean.integration_config["atlassian_user_email"], - ocean.integration_config["atlassian_user_token"], - ) + client = create_jira_client() selector = cast(JiraProjectResourceConfig, event.resource_config).selector params = {"expand": selector.expand} @@ -56,11 +62,7 @@ async def on_resync_projects(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: @ocean.on_resync(ObjectKind.ISSUE) async def on_resync_issues(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - client = JiraClient( - ocean.integration_config["jira_host"], - ocean.integration_config["atlassian_user_email"], - ocean.integration_config["atlassian_user_token"], - ) + client = create_jira_client() params = {} config = typing.cast(JiraIssueConfig, event.resource_config) @@ -81,11 +83,7 @@ async def on_resync_issues(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: @ocean.on_resync(ObjectKind.USER) async def on_resync_users(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: - client = JiraClient( - ocean.integration_config["jira_host"], - ocean.integration_config["atlassian_user_email"], - ocean.integration_config["atlassian_user_token"], - ) + client = create_jira_client() async for users in client.get_paginated_users(): logger.info(f"Received users batch with {len(users)} users") @@ -94,11 +92,7 @@ async def on_resync_users(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: @ocean.router.post("/webhook") async def handle_webhook_request(data: dict[str, Any]) -> dict[str, Any]: - client = JiraClient( - ocean.integration_config["jira_host"], - ocean.integration_config["atlassian_user_email"], - ocean.integration_config["atlassian_user_token"], - ) + client = create_jira_client() webhook_event = data.get("webhookEvent") if not webhook_event: @@ -108,21 +102,58 @@ async def handle_webhook_request(data: dict[str, Any]) -> dict[str, Any]: logger.info(f"Processing webhook event: {webhook_event}") match webhook_event: - case event if event.startswith("user_"): + case event_data if event_data.startswith("user_"): account_id = data["user"]["accountId"] logger.debug(f"Fetching user with accountId: {account_id}") item = await client.get_single_user(account_id) kind = ObjectKind.USER - case event if event.startswith("project_"): + case event_data if event_data.startswith("project_"): project_key = data["project"]["key"] logger.debug(f"Fetching project with key: {project_key}") item = await client.get_single_project(project_key) kind = ObjectKind.PROJECT - case event if event.startswith("jira:issue_"): + case event_data if event_data.startswith("jira:issue_"): issue_key = data["issue"]["key"] - logger.debug(f"Fetching issue with key: {issue_key}") - item = await client.get_single_issue(issue_key) - kind = ObjectKind.ISSUE + logger.info( + f"Fetching issue with key: {issue_key} and applying specified JQL filter" + ) + resource_configs = cast(JiraPortAppConfig, event.port_app_config).resources + + matching_resource_configs: list[JiraIssueConfig] = [ + resource_config # type: ignore + for resource_config in resource_configs + if ( + resource_config.kind == ObjectKind.ISSUE + and isinstance(resource_config.selector, JiraIssueSelector) + ) + ] + + for config in matching_resource_configs: + + params = {} + + if config.selector.jql: + params["jql"] = ( + f"{config.selector.jql} AND key = {data['issue']['key']}" + ) + else: + params["jql"] = f"key = {data['issue']['key']}" + + issues: list[dict[str, Any]] = [] + async for issues in client.get_paginated_issues(params): + issues.extend(issues) + + if not issues: + logger.warning( + f"Issue {data['issue']['key']} not found" + f" using the following query: {params['jql']}," + " trying to remove..." + ) + await ocean.unregister_raw(ObjectKind.ISSUE, [data["issue"]]) + else: + await ocean.register_raw(ObjectKind.ISSUE, issues) + + return {"ok": True} case _: logger.error(f"Unknown webhook event type: {webhook_event}") return { diff --git a/integrations/jira/pyproject.toml b/integrations/jira/pyproject.toml index 8932d88c08..3291fe6d15 100644 --- a/integrations/jira/pyproject.toml +++ b/integrations/jira/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "jira" -version = "0.2.17" +version = "0.2.18" description = "Integration to bring information from Jira into Port" authors = ["Mor Paz "]