Skip to content

Commit b9a0a93

Browse files
authored
[Core] Updated webhook processor handling (#1737)
### **User description** # Description What - Updated webhook processor handling implementation to prevent throwing unhandled exception when event for a supported kind is received but the kind is absent in the mapping Why - Currently the webhook processor raises an error when an event is coming for a supported kind not present in the mapping How - Only raise value error for unknown events without supported processor ## Type of change Please leave one option from the following and delete the rest: - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] New Integration (non-breaking change which adds a new integration) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [x] Non-breaking change (fix of existing functionality that will not change current behavior) - [ ] Documentation (added/updated documentation) <h4> All tests should be run against the port production environment(using a testing org). </h4> ### Core testing checklist - [ ] Integration able to create all default resources from scratch - [ ] Resync finishes successfully - [ ] Resync able to create entities - [ ] Resync able to update entities - [ ] Resync able to detect and delete entities - [ ] Scheduled resync able to abort existing resync and start a new one - [ ] Tested with at least 2 integrations from scratch - [ ] Tested with Kafka and Polling event listeners - [ ] Tested deletion of entities that don't pass the selector ### Integration testing checklist - [ ] Integration able to create all default resources from scratch - [ ] Resync able to create entities - [ ] Resync able to update entities - [ ] Resync able to detect and delete entities - [ ] Resync finishes successfully - [ ] If new resource kind is added or updated in the integration, add example raw data, mapping and expected result to the `examples` folder in the integration directory. - [ ] If resource kind is updated, run the integration with the example data and check if the expected result is achieved - [ ] If new resource kind is added or updated, validate that live-events for that resource are working as expected - [ ] Docs PR link [here](#) ### Preflight checklist - [ ] Handled rate limiting - [ ] Handled pagination - [ ] Implemented the code in async - [ ] Support Multi account ## Screenshots Include screenshots from your environment showing how the resources of the integration will look. ## API Documentation Provide links to the API documentation used for this integration. ___ ### **PR Type** Bug fix, Enhancement ___ ### **Description** - Improved webhook processor handling for absent resource mappings. - Now logs info instead of raising errors if processors exist but resource types are disabled. - Only raises errors for truly unknown webhook event types. - Updated changelog to document the new webhook event handling behavior. - Bumped project version to 0.24.3 in `pyproject.toml`. ___ ### **Changes walkthrough** 📝 <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Bug fix</strong></td><td><table> <tr> <td> <details> <summary><strong>processor_manager.py</strong><dd><code>Enhance webhook processor error handling and logging</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> port_ocean/core/handlers/webhook/processor_manager.py <li>Refined logic for extracting matching processors for webhook events.<br> <li> Logs info if processors exist but resource types are not configured, <br>instead of raising an error.<br> <li> Only raises ValueError for unknown event types with no processors.<br> <li> Adds detailed logging for both scenarios. </details> </td> <td><a href="https://github.com/port-labs/ocean/pull/1737/files#diff-58a2a8bcf5e453cdb2b298a8edcb716c3c3454bb37eed3d6fb3c0c88b50e8b6d">+17/-1</a>&nbsp; &nbsp; </td> </tr> </table></td></tr><tr><td><strong>Documentation</strong></td><td><table> <tr> <td> <details> <summary><strong>CHANGELOG.md</strong><dd><code>Document improved webhook event handling in changelog</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> CHANGELOG.md <li>Added entry describing improved webhook event handling for absent <br>resource mappings. </details> </td> <td><a href="https://github.com/port-labs/ocean/pull/1737/files#diff-06572a96a58dc510037d5efa622f9bec8519bc1beab13c9f251e97e657a9d4ed">+5/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></td></tr><tr><td><strong>Configuration changes</strong></td><td><table> <tr> <td> <details> <summary><strong>pyproject.toml</strong><dd><code>Bump version to 0.24.3</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pyproject.toml - Bumped version from 0.24.2 to 0.24.3 to reflect the new changes. </details> </td> <td><a href="https://github.com/port-labs/ocean/pull/1737/files#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> </table></td></tr></tr></tbody></table> ___ > <details> <summary> Need help?</summary><li>Type <code>/help how to ...</code> in the comments thread for any questions about Qodo Merge usage.</li><li>Check out the <a href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a> for more information.</li></details>
1 parent 2d729b0 commit b9a0a93

File tree

5 files changed

+126
-6
lines changed

5 files changed

+126
-6
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
<!-- towncrier release notes start -->
9+
10+
## 0.24.11 (2025-06-16)
11+
12+
### Bug Fixes
13+
- Prevented unhandled exception when webhook event is received for resource types not present in mapping but can be handled by processors.
14+
915
## 0.24.10 (2025-06-15)
1016

1117
### Bug Fixes
1218
- Fixed overwriting syncing state metrics reporting during resource processing
1319

14-
1520
## 0.24.9 (2025-06-15)
1621

1722
### Improvements

port_ocean/core/handlers/webhook/processor_manager.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from port_ocean.core.handlers.port_app_config.models import ResourceConfig
99
from port_ocean.core.integrations.mixins.events import EventsMixin
1010
from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
11+
from port_ocean.exceptions.webhook_processor import WebhookEventNotSupportedError
1112
from .webhook_event import WebhookEvent, WebhookEventRawResults, LiveEventTimestamp
1213
from port_ocean.context.event import event
1314

@@ -53,18 +54,35 @@ async def _extract_matching_processors(
5354
"""Find and extract the matching processor for an event"""
5455

5556
created_processors: list[tuple[ResourceConfig, AbstractWebhookProcessor]] = []
57+
event_processor_names = []
5658

5759
for processor_class in self._processors_classes[path]:
5860
processor = processor_class(webhook_event.clone())
5961
if await processor.should_process_event(webhook_event):
62+
event_processor_names.append(processor.__class__.__name__)
6063
kinds = await processor.get_matching_kinds(webhook_event)
6164
for kind in kinds:
6265
for resource in event.port_app_config.resources:
6366
if resource.kind == kind:
6467
created_processors.append((resource, processor))
6568

6669
if not created_processors:
67-
raise ValueError("No matching processors found")
70+
if event_processor_names:
71+
logger.info(
72+
"Webhook processors are available to handle this webhook event, but the corresponding kinds are not configured in the integration's mapping",
73+
processors_available=event_processor_names,
74+
webhook_path=path,
75+
)
76+
return []
77+
else:
78+
logger.warning(
79+
"Unknown webhook event type received",
80+
webhook_path=path,
81+
message="No processors registered to handle this webhook event type.",
82+
)
83+
raise WebhookEventNotSupportedError(
84+
"No matching processors found for webhook event"
85+
)
6886

6987
logger.info(
7088
"Found matching processors for webhook event",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
1+
from port_ocean.exceptions.base import BaseOceanException
2+
3+
14
class RetryableError(Exception):
25
"""Base exception class for errors that should trigger a retry."""
36

47
pass
8+
9+
10+
class WebhookProcessingError(BaseOceanException):
11+
"""Base exception for webhook processing errors"""
12+
13+
pass
14+
15+
16+
class WebhookEventNotSupportedError(WebhookProcessingError):
17+
pass

port_ocean/tests/core/handlers/webhook/test_processor_manager.py

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737
)
3838
from port_ocean.core.integrations.mixins.live_events import LiveEventsMixin
3939
from port_ocean.core.models import Entity
40-
from port_ocean.exceptions.webhook_processor import RetryableError
40+
from port_ocean.exceptions.webhook_processor import (
41+
RetryableError,
42+
WebhookEventNotSupportedError,
43+
)
4144
from port_ocean.core.handlers.queue import LocalQueue
4245

4346

@@ -354,7 +357,9 @@ async def test_extractMatchingProcessors_noMatch(
354357
test_path = "/test"
355358
processor_manager.register_processor(test_path, MockProcessorFalse)
356359

357-
with pytest.raises(ValueError, match="No matching processors found"):
360+
with pytest.raises(
361+
WebhookEventNotSupportedError, match="No matching processors found"
362+
):
358363
async with event_context(
359364
EventType.HTTP_REQUEST, trigger_type="request"
360365
) as event:
@@ -409,6 +414,82 @@ async def test_extractMatchingProcessors_onlyOneMatches(
409414
assert processor.event.payload == webhook_event.payload
410415

411416

417+
@pytest.mark.asyncio
418+
async def test_extractMatchingProcessors_noProcessorsRegistered(
419+
processor_manager: LiveEventsProcessorManager,
420+
webhook_event: WebhookEvent,
421+
mock_port_app_config: PortAppConfig,
422+
) -> None:
423+
"""Test that WebhookEventNotSupportedError is raised for unknown events without any registered processors"""
424+
test_path = "/unknown_path"
425+
# No processors registered for this path
426+
427+
# Manually add the path to _processors_classes to simulate a path with no processors
428+
processor_manager._processors_classes[test_path] = []
429+
430+
with pytest.raises(
431+
WebhookEventNotSupportedError, match="No matching processors found"
432+
):
433+
async with event_context(
434+
EventType.HTTP_REQUEST, trigger_type="request"
435+
) as event:
436+
event.port_app_config = mock_port_app_config
437+
await processor_manager._extract_matching_processors(
438+
webhook_event, test_path
439+
)
440+
441+
442+
@pytest.mark.asyncio
443+
async def test_extractMatchingProcessors_processorsAvailableButKindsNotConfigured(
444+
processor_manager: LiveEventsProcessorManager,
445+
webhook_event: WebhookEvent,
446+
) -> None:
447+
"""Test that processors available but kinds not configured returns empty list"""
448+
test_path = "/test"
449+
450+
from port_ocean.core.handlers.port_app_config.models import (
451+
PortAppConfig,
452+
ResourceConfig,
453+
)
454+
455+
# Create a mock processor that will match the event but return a kind not in the port app config
456+
class MockProcessorWithUnmappedKind(AbstractWebhookProcessor):
457+
async def authenticate(
458+
self, payload: Dict[str, Any], headers: Dict[str, str]
459+
) -> bool:
460+
return True
461+
462+
async def validate_payload(self, payload: Dict[str, Any]) -> bool:
463+
return True
464+
465+
async def handle_event(
466+
self, payload: EventPayload, resource: ResourceConfig
467+
) -> WebhookEventRawResults:
468+
return WebhookEventRawResults(
469+
updated_raw_results=[], deleted_raw_results=[]
470+
)
471+
472+
async def should_process_event(self, event: WebhookEvent) -> bool:
473+
return True # This processor will match
474+
475+
async def get_matching_kinds(self, event: WebhookEvent) -> list[str]:
476+
return ["unmapped_kind"] # This kind is not in the mock_port_app_config
477+
478+
processor_manager.register_processor(test_path, MockProcessorWithUnmappedKind)
479+
480+
empty_port_app_config = PortAppConfig(
481+
resources=[],
482+
)
483+
484+
async with event_context(EventType.HTTP_REQUEST, trigger_type="request") as event:
485+
event.port_app_config = empty_port_app_config
486+
processors = await processor_manager._extract_matching_processors(
487+
webhook_event, test_path
488+
)
489+
490+
assert len(processors) == 0
491+
492+
412493
def test_registerProcessor_registrationWorks(
413494
processor_manager: LiveEventsProcessorManager,
414495
) -> None:
@@ -853,7 +934,10 @@ async def patched_extract_matching_processors(
853934
except asyncio.TimeoutError:
854935
pytest.fail("Event processing timed out")
855936

856-
assert isinstance(test_state["exception_thrown"], ValueError) is True
937+
assert (
938+
isinstance(test_state["exception_thrown"], WebhookEventNotSupportedError)
939+
is True
940+
)
857941

858942
mock_upsert.assert_not_called()
859943
mock_delete.assert_not_called()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "port-ocean"
3-
version = "0.24.10"
3+
version = "0.24.11"
44
description = "Port Ocean is a CLI tool for managing your Port projects."
55
readme = "README.md"
66
homepage = "https://app.getport.io"

0 commit comments

Comments
 (0)