Skip to content

chore: add lifecycle client and shutdown aborted notifications#3163

Open
ivankalinovski wants to merge 4 commits intomainfrom
chore/add-lifecycle-events
Open

chore: add lifecycle client and shutdown aborted notifications#3163
ivankalinovski wants to merge 4 commits intomainfrom
chore/add-lifecycle-events

Conversation

@ivankalinovski
Copy link
Copy Markdown
Contributor

Add LifecycleClient for the ingest lifecycle API and notify aborted resyncs on graceful shutdown when transform is enabled. Wire is_transform_enabled, sync/mixin and settings changes, and tests for the client, organization mixin, and transform flag.

Made-with: Cursor

Description

What -

Why -

How -

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)
  • Non-breaking change (fix of existing functionality that will not change current behavior)
  • Documentation (added/updated documentation)

All tests should be run against the port production environment(using a testing org).

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
  • Completed a full resync from a freshly installed integration and it completed successfully
  • 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.

Add LifecycleClient for the ingest lifecycle API and notify aborted resyncs on graceful shutdown when transform is enabled. Wire is_transform_enabled, sync/mixin and settings changes, and tests for the client, organization mixin, and transform flag.

Made-with: Cursor
@ivankalinovski ivankalinovski requested a review from a team as a code owner April 29, 2026 14:23
@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add lifecycle client and transform feature support for resync notifications

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add LifecycleClient for resync lifecycle event notifications
• Integrate transform feature flag checks with graceful shutdown handling
• Add TransformSettings configuration with enabled toggle and base_url
• Notify lifecycle API on resync started, finished, failed, and aborted states
• Add comprehensive tests for lifecycle client and transform feature detection
Diagram
flowchart LR
  A["LifecycleClient"] -->|"POST /v1/lifecycle"| B["Lifecycle API"]
  C["Ocean App"] -->|"notify_started/finished/failed/aborted"| A
  D["TransformSettings"] -->|"enabled + base_url"| C
  E["is_transform_enabled()"] -->|"checks org flags + local config"| F["Feature Decision"]
  F -->|"true"| C
  G["Graceful Shutdown"] -->|"notify_aborted"| A
Loading

Grey Divider

File Changes

1. port_ocean/clients/lifecycle.py ✨ Enhancement +102/-0

New HTTP client for lifecycle event notifications

port_ocean/clients/lifecycle.py


2. port_ocean/config/settings.py ⚙️ Configuration changes +6/-0

Add TransformSettings configuration class

port_ocean/config/settings.py


3. port_ocean/core/models.py ✨ Enhancement +1/-0

Add DATA_SOURCE_PROCESSOR_ENABLED feature flag

port_ocean/core/models.py


View more (8)
4. port_ocean/core/integrations/mixins/utils.py ✨ Enhancement +27/-0

Add is_transform_enabled feature detection function

port_ocean/core/integrations/mixins/utils.py


5. port_ocean/core/integrations/mixins/sync_raw.py ✨ Enhancement +35/-0

Integrate lifecycle notifications into resync flow

port_ocean/core/integrations/mixins/sync_raw.py


6. port_ocean/ocean.py ✨ Enhancement +14/-0

Initialize LifecycleClient and handle shutdown notifications

port_ocean/ocean.py


7. port_ocean/clients/port/mixins/organization.py Formatting +1/-1

Remove trailing newline from file

port_ocean/clients/port/mixins/organization.py


8. port_ocean/tests/clients/test_lifecycle_client.py 🧪 Tests +232/-0

Comprehensive test suite for LifecycleClient

port_ocean/tests/clients/test_lifecycle_client.py


9. port_ocean/tests/clients/port/mixins/test_organization_mixin.py 🧪 Tests +37/-1

Add tests for organization ID retrieval and caching

port_ocean/tests/clients/port/mixins/test_organization_mixin.py


10. port_ocean/tests/core/integrations/mixins/test_transform_enabled.py 🧪 Tests +89/-0

Test suite for transform feature flag detection

port_ocean/tests/core/integrations/mixins/test_transform_enabled.py


11. port_ocean/tests/core/conftest.py 🧪 Tests +1/-0

Add lifecycle_client mock to test fixtures

port_ocean/tests/core/conftest.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented Apr 29, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Lifecycle URL miswired 🐞 Bug ≡ Correctness
Description
Ocean initializes LifecycleClient with config.port.ingest_url, ignoring the newly added
transform.base_url setting, so lifecycle notifications will be sent to the Port ingest endpoint
rather than the intended lifecycle/transform service. This breaks lifecycle reporting even when
transform is enabled.
Code

port_ocean/ocean.py[R111-114]

+        self.lifecycle_client: LifecycleClient = LifecycleClient(
+            base_url=str(self.config.port.ingest_url),
+            auth=self.port_client.auth,
+        )
Evidence
Ocean wires LifecycleClient to PortSettings.ingest_url, while the PR adds TransformSettings.base_url
specifically for transform/lifecycle offload; additionally, other ingest-style calls use the
per-integration ingestUrl returned from Port (metricAttributes.ingestUrl), not the static
config.port.ingest_url, making the current lifecycle base URL highly likely to be wrong.

port_ocean/ocean.py[108-114]
port_ocean/config/settings.py[52-58]
port_ocean/config/settings.py[84-87]
port_ocean/clients/port/mixins/integrations.py[96-101]
port_ocean/clients/port/mixins/integrations.py[203-231]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`LifecycleClient` is currently initialized with `config.port.ingest_url`, but the PR introduces `config.transform.base_url` and the feature is described as offloading lifecycle events to a transform/lifecycle service. As-is, lifecycle POSTs will go to the wrong host/path.

## Issue Context
- `TransformSettings.base_url` is added but never used.
- Port ingest URLs are often integration-specific (`metricAttributes.ingestUrl`) rather than the global default.

## Fix Focus Areas
- port_ocean/ocean.py[108-114]
- port_ocean/config/settings.py[84-87]

## Suggested fix
- Initialize `LifecycleClient` with `base_url=str(self.config.transform.base_url)` when provided.
- If you need a fallback, make it explicit (e.g., fallback to `metricAttributes.ingestUrl` fetched from Port, or disable lifecycle reporting when `transform.base_url` is missing).
- Consider updating `is_transform_enabled()` to also require `transform.base_url` if lifecycle reporting cannot function without it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Org mixin test fails🐞 Bug ≡ Correctness
Description
New tests call OrganizationClientMixin.get_org_id() and expect caching/empty-string behavior, but
OrganizationClientMixin has no get_org_id method, causing AttributeError and failing the test suite.
This also leaves the intended org-id caching behavior unimplemented.
Code

port_ocean/tests/clients/port/mixins/test_organization_mixin.py[R31-64]

+async def test_get_org_id_returns_id_from_organization_response(
+    mocked_org_mixin: OrganizationClientMixin,
+) -> None:
+    result = await mocked_org_mixin.get_org_id()
+
+    assert result == "org-123"
+
+
+async def test_get_org_id_caches_result_and_calls_api_once(
+    mocked_org_mixin: OrganizationClientMixin,
+) -> None:
+    # Call twice
+    result1 = await mocked_org_mixin.get_org_id()
+    result2 = await mocked_org_mixin.get_org_id()
+
+    assert result1 == result2 == "org-123"
+    # The underlying GET should only have been called once (cached after first call)
+    assert mocked_org_mixin.client.get.call_count == 1
+
+
+async def test_get_org_id_returns_empty_string_when_id_missing(
+) -> None:
+    auth = MagicMock()
+    auth.headers = AsyncMock(return_value={})
+    client = MagicMock()
+    response = MagicMock()
+    response.json = MagicMock(return_value={"organization": {}})
+    response.status_code = 200
+    client.get = AsyncMock(return_value=response)
+
+    mixin = OrganizationClientMixin(auth=auth, client=client)
+    result = await mixin.get_org_id()
+
+    assert result == ""
Evidence
The added tests invoke OrganizationClientMixin.get_org_id, but the mixin only implements
feature-flag retrieval. A get_org_id exists on PortClient (not the mixin) and does not implement the
caching/empty-string semantics the new tests require.

port_ocean/tests/clients/port/mixins/test_organization_mixin.py[31-64]
port_ocean/clients/port/mixins/organization.py[8-31]
port_ocean/clients/port/client.py[79-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`OrganizationClientMixin` does not implement `get_org_id()`, but the PR adds tests expecting it (including caching and empty-string fallback when missing). This will raise `AttributeError` and fail CI.

## Issue Context
- The mixin already calls `GET /organization` for feature flags.
- `PortClient.get_org_id()` exists but is not on the mixin and does not match the new tests’ semantics.

## Fix Focus Areas
- port_ocean/clients/port/mixins/organization.py[8-31]
- port_ocean/tests/clients/port/mixins/test_organization_mixin.py[31-64]

## Suggested fix
- Add `self._org_id: str | None = None` to the mixin.
- Implement `async def get_org_id(self) -> str`:
 - If cached, return it.
 - Otherwise call the same `/organization` endpoint, parse `response.json().get("organization", {}).get("id", "")`.
 - Cache the resulting string (including "").
- Ensure `client.get` is only called once across two calls (per test expectation).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Lifecycle client tests broken🐞 Bug ☼ Reliability
Description
New lifecycle client tests call notify_finished/notify_failed/notify_aborted without the required
integration_type argument and assert bodies that omit integration_type, so the tests will fail with
TypeError and/or assertion failures. This will break CI even if the runtime code paths are correct.
Code

port_ocean/tests/clients/test_lifecycle_client.py[R113-122]

+            await lifecycle_client.notify_finished(
+                resync_id="r1",
+                integration_id="i1",
+            )
+
+        call_kwargs = mock_client.post.call_args
+        assert call_kwargs[0][0] == "http://localhost:3017/v1/lifecycle/r1/i1"
+        body = call_kwargs[1]["json"]
+        assert body == {"status": "finished"}
+
Evidence
LifecycleClient requires integration_type for finished/failed/aborted and always includes it in the
JSON body, but the new tests omit the argument and expect bodies without integration_type.

port_ocean/clients/lifecycle.py[65-100]
port_ocean/tests/clients/test_lifecycle_client.py[98-122]
port_ocean/tests/clients/test_lifecycle_client.py[124-162]
port_ocean/tests/clients/test_lifecycle_client.py[164-189]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`test_lifecycle_client.py` calls `notify_finished/notify_failed/notify_aborted` without `integration_type` and asserts payloads without `integration_type`, but `LifecycleClient` requires `integration_type` and includes it in the payload.

## Issue Context
This is a test-suite correctness issue that will fail CI.

## Fix Focus Areas
- port_ocean/tests/clients/test_lifecycle_client.py[98-122]
- port_ocean/tests/clients/test_lifecycle_client.py[124-162]
- port_ocean/tests/clients/test_lifecycle_client.py[164-189]
- port_ocean/clients/lifecycle.py[65-100]

## Suggested fix
Option A (preferred): Update tests to pass `integration_type="github"` (or similar) and assert that the JSON includes `integration_type`.

Option B: If the intended API is to *not* require integration_type, change `LifecycleClient` method signatures to make it optional and remove it from the body (but then update production call sites accordingly).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Repeated transform flag fetch 🐞 Bug ➹ Performance
Description
sync_raw_all calls is_transform_enabled multiple times per resync, and is_transform_enabled makes an
HTTP request to fetch org feature flags each time, adding avoidable latency and failure surface on
the resync hot path. It also logs feature flags and the toggle at INFO on every check, creating
noisy logs.
Code

port_ocean/core/integrations/mixins/sync_raw.py[R1170-1176]

+            if await is_transform_enabled():
+                await ocean.app.lifecycle_client.notify_started(
+                    resync_id=event.id,
+                    integration_id=ocean.config.integration.identifier,
+                    integration_type=ocean.config.integration.type,
+                    started_at=datetime.now(timezone.utc),
+                )
Evidence
There are several if await is_transform_enabled(): checks in sync_raw_all. Each call triggers
PortClient.get_organization_feature_flags() which issues an HTTP GET to /organization, and
is_transform_enabled logs at INFO level on every call.

port_ocean/core/integrations/mixins/sync_raw.py[1170-1177]
port_ocean/core/integrations/mixins/sync_raw.py[1211-1226]
port_ocean/core/integrations/mixins/sync_raw.py[1267-1280]
port_ocean/core/integrations/mixins/utils.py[56-75]
port_ocean/clients/port/mixins/organization.py[17-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`sync_raw_all()` repeatedly calls `is_transform_enabled()`, which performs an HTTP request for feature flags each time and logs at INFO.

## Issue Context
This runs during every resync and can add multiple extra `/organization` calls.

## Fix Focus Areas
- port_ocean/core/integrations/mixins/sync_raw.py[1166-1281]
- port_ocean/core/integrations/mixins/utils.py[56-80]

## Suggested fix
- Compute once near the start of `sync_raw_all`:
 - `transform_enabled = await is_transform_enabled()`
- Reuse `transform_enabled` for all lifecycle notify branches.
- Downgrade the per-check logs in `is_transform_enabled` to DEBUG or remove them (keep the warning log on exception).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread port_ocean/ocean.py
Comment on lines +111 to +114
self.lifecycle_client: LifecycleClient = LifecycleClient(
base_url=str(self.config.port.ingest_url),
auth=self.port_client.auth,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Lifecycle url miswired 🐞 Bug ≡ Correctness

Ocean initializes LifecycleClient with config.port.ingest_url, ignoring the newly added
transform.base_url setting, so lifecycle notifications will be sent to the Port ingest endpoint
rather than the intended lifecycle/transform service. This breaks lifecycle reporting even when
transform is enabled.
Agent Prompt
## Issue description
`LifecycleClient` is currently initialized with `config.port.ingest_url`, but the PR introduces `config.transform.base_url` and the feature is described as offloading lifecycle events to a transform/lifecycle service. As-is, lifecycle POSTs will go to the wrong host/path.

## Issue Context
- `TransformSettings.base_url` is added but never used.
- Port ingest URLs are often integration-specific (`metricAttributes.ingestUrl`) rather than the global default.

## Fix Focus Areas
- port_ocean/ocean.py[108-114]
- port_ocean/config/settings.py[84-87]

## Suggested fix
- Initialize `LifecycleClient` with `base_url=str(self.config.transform.base_url)` when provided.
- If you need a fallback, make it explicit (e.g., fallback to `metricAttributes.ingestUrl` fetched from Port, or disable lifecycle reporting when `transform.base_url` is missing).
- Consider updating `is_transform_enabled()` to also require `transform.base_url` if lifecycle reporting cannot function without it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread port_ocean/tests/clients/test_lifecycle_client.py
Comment thread port_ocean/tests/clients/port/mixins/test_organization_mixin.py Outdated
@github-actions
Copy link
Copy Markdown
Contributor

Code Coverage Artifact 📈: https://github.com/port-labs/ocean/actions/runs/25130334734/artifacts/6716596690

Code Coverage Total Percentage: 90.96%

@github-actions
Copy link
Copy Markdown
Contributor

Code Coverage Artifact 📈: https://github.com/port-labs/ocean/actions/runs/25130668983/artifacts/6716747312

Code Coverage Total Percentage: 90.96%

@github-actions
Copy link
Copy Markdown
Contributor

Code Coverage Artifact 📈: https://github.com/port-labs/ocean/actions/runs/25130718095/artifacts/6716757340

Code Coverage Total Percentage: 90.96%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant