Skip to content

feat(integration): Add health-tracking integration feature#7956

Open
afsuyadi wants to merge 50 commits into
Flagsmith:mainfrom
afsuyadi:feat/track-integration-health
Open

feat(integration): Add health-tracking integration feature#7956
afsuyadi wants to merge 50 commits into
Flagsmith:mainfrom
afsuyadi:feat/track-integration-health

Conversation

@afsuyadi

@afsuyadi afsuyadi commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Thanks for submitting a PR! Please check the boxes below:

  • [✔] I have read the Contributing Guide.
  • [ ] I have added information to docs/ if required so people know about the feature.
  • [✔] I have filled in the "Changes" section below.
  • [✔] I have filled in the "How did you test this code" section below.

Changes

Contributes to #4324

What I understand and how I build this feature:

  1. Users need health tracker for every thirdy-party integration tool that will indicate whether it has showed healthy / unhealthy responses.

  2. We need a specific table (IntegrationHealthRecord) that holds the information about which third-party tool we integrate with and its health status.

  3. I create 2 helper functions:

  • record_integration_health(): create records in the specific table, if the response is 2xx.
  • get_latest_integration_health(): verify if the latest record on a thirdyparty config is present
  1. When our server sends payload through a wrapper, that third-party tool will send back a response. That response is what we capture and stores in the special table. I don't include the data payload of the response, to prevent the bulking of database's size.

  2. For every third-party tool, a health indicator on the client will appear with 3 status: Healthy (2xx response), Unhealthy (anything that excludes from 2xx response), and No Health Data (indicates that integrations hasn't happened, but the user has add that config with base url)

  3. For additional information:

  • I include only third-party wrappers that have straightforward HTTP's response. Other than that, I think they will need special handling.
  • This feature causes the serializer's payload format to be changed by the addition of latest_health field. To mitigate this, I also create unit tests to cover this.
  • Since the serializer's payload format is changed, other unit tests and input for functions will also have to accomodate.

How did you test this code?

I create several unit tests that cover various flow:

Tests Data flow tested
services.py tests Helper ↔ DB (does the query return the right dict?)
serializers.py tests Helper → Serialiser → Client (is latest_health present and shaped correctly in the JSON response?)
Wrapper tests Wrapper → Helper → DB (does the wrapper actually call record_integration_health after the HTTP call?)

CLI: .venv/bin/pytest --ds=app.settings.test tests/unit/integrations/ -n0
Result of testing all of the file:
Screenshot from 2026-07-07 11-30-27

After changes:
Screenshot from 2026-07-06 14-26-05

Screenshot from 2026-07-06 14-54-49 Screenshot from 2026-07-06 14-53-31

@afsuyadi afsuyadi requested review from a team as code owners July 7, 2026 07:44
@afsuyadi afsuyadi requested review from emyller and talissoncosta and removed request for a team July 7, 2026 07:44
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

@afsuyadi is attempting to deploy a commit to the Flagsmith Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds an integrations.common Django app with an IntegrationHealthRecord model, migration, and service functions for recording and retrieving latest integration health. Multiple integration wrappers now accept configuration objects and write health records after HTTP calls. Serialisers and list endpoints expose latest_health, the frontend displays the health state, and tests were added or updated across backend and frontend paths.

Estimated code review effort: 4 (Complex) | ~60 minutes


Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added front-end Issue related to the React Front End Dashboard api Issue related to the REST API labels Jul 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
api/integrations/sentry/change_tracking.py (1)

94-107: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Network-level failures never produce a health record.

record_integration_health is only reached on the success path (line 100), before raise_for_status(). If requests.post itself raises (ConnectionError, Timeout, etc.), execution never reaches line 100, so no health record is written at all — the integration's health status silently goes stale instead of reflecting the outage.

💡 Suggested direction
         try:
             response = requests.post(
                 url=self.webhook_url,
                 headers=headers,
                 data=json_payload,
+                timeout=10,
             )
             record_integration_health(self.config, response.status_code)
             response.raise_for_status()  # NOTE: This is for future-proofing, as Sentry won't respond 4xx.
         except requests.exceptions.RequestException as error:
+            record_integration_health(self.config, 0)
             log.warning(
                 "request-failure",
                 error=error,
             )
             return

get_latest_integration_health already treats any non-2xx status_code as unhealthy, so a sentinel value like 0 would correctly surface as "Unhealthy". This same gap likely applies to the other refactored wrappers (e.g. new_relic.py, which has no exception handling at all around its POST call).

api/integrations/mixpanel/mixpanel.py (1)

22-40: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard the health write behind best-effort handling. record_integration_health(...) can raise, and identify_user_async() does not catch it, so a DB hiccup here can fail the Mixpanel identify call.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b1890d82-3475-42d1-a441-d6f46c22fa78

📥 Commits

Reviewing files that changed from the base of the PR and between da194ca and b6d4f87.

📒 Files selected for processing (44)
  • api/app/settings/common.py
  • api/audit/signals.py
  • api/integrations/amplitude/amplitude.py
  • api/integrations/amplitude/serializers.py
  • api/integrations/common/apps.py
  • api/integrations/common/migrations/0001_initial.py
  • api/integrations/common/migrations/__init__.py
  • api/integrations/common/models.py
  • api/integrations/common/serializers.py
  • api/integrations/common/services.py
  • api/integrations/datadog/datadog.py
  • api/integrations/dynatrace/dynatrace.py
  • api/integrations/dynatrace/serializers.py
  • api/integrations/grafana/grafana.py
  • api/integrations/heap/heap.py
  • api/integrations/heap/serializers.py
  • api/integrations/mixpanel/mixpanel.py
  • api/integrations/mixpanel/serializers.py
  • api/integrations/new_relic/new_relic.py
  • api/integrations/rudderstack/serializers.py
  • api/integrations/segment/serializers.py
  • api/integrations/sentry/change_tracking.py
  • api/integrations/sentry/serializers.py
  • api/integrations/webhook/serializers.py
  • api/integrations/webhook/webhook.py
  • api/tests/integration/sentry/test_change_tracking_webhook_integration.py
  • api/tests/unit/integrations/amplitude/test_unit_amplitude.py
  • api/tests/unit/integrations/amplitude/test_unit_amplitude_views.py
  • api/tests/unit/integrations/common/test_unit_integrations_common_serializers.py
  • api/tests/unit/integrations/common/test_unit_integrations_common_services.py
  • api/tests/unit/integrations/datadog/test_unit_datadog.py
  • api/tests/unit/integrations/dynatrace/test_unit_dynatrace.py
  • api/tests/unit/integrations/dynatrace/test_unit_dynatrace_views.py
  • api/tests/unit/integrations/grafana/test_grafana.py
  • api/tests/unit/integrations/heap/test_unit_heap.py
  • api/tests/unit/integrations/heap/test_unit_heap_views.py
  • api/tests/unit/integrations/mixpanel/test_unit_mixpanel.py
  • api/tests/unit/integrations/mixpanel/test_unit_mixpanel_views.py
  • api/tests/unit/integrations/new_relic/test_unit_new_relic.py
  • api/tests/unit/integrations/rudderstack/test_unit_rudderstack_views.py
  • api/tests/unit/integrations/segment/test_unit_segment_views.py
  • api/tests/unit/integrations/webhook/test_unit_webhook.py
  • frontend/common/types/responses.ts
  • frontend/web/components/IntegrationList.tsx

Comment thread api/integrations/amplitude/amplitude.py Outdated
Comment thread api/integrations/amplitude/amplitude.py Outdated
Comment thread api/integrations/common/models.py
Comment thread api/integrations/common/serializers.py Outdated
Comment thread api/integrations/common/services.py Outdated
Comment thread api/tests/unit/integrations/common/test_unit_integrations_common_services.py Outdated
Comment thread api/tests/unit/integrations/webhook/test_unit_webhook.py
Comment thread frontend/web/components/IntegrationList.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6d8fc160-2c9d-479f-91c1-e8c57287cd64

📥 Commits

Reviewing files that changed from the base of the PR and between b6d4f87 and 9b5a2a9.

📒 Files selected for processing (1)
  • frontend/web/components/IntegrationList.tsx

Comment thread frontend/web/components/IntegrationList.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7498720d-f05b-44b4-b96c-3cf6692192c7

📥 Commits

Reviewing files that changed from the base of the PR and between cf38f33 and ece3fbb.

📒 Files selected for processing (1)
  • api/integrations/amplitude/amplitude.py

Comment thread api/integrations/amplitude/amplitude.py Outdated
Comment on lines +29 to +30
response = requests.post(self.url, data=payload, timeout=10)
record_integration_health(self.config, response.status_code)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Network/timeout failures never produce a health record.

If requests.post raises (now more likely to time out given the newly added timeout=10, or on ConnectionError), the exception propagates before record_integration_health is reached — so the one case this feature most needs to surface (an unreachable/slow Amplitude endpoint) leaves the integration stuck at "No Health Data" rather than "Unhealthy". This appears to be the same pattern in sibling wrappers (heap, mixpanel), so may be a systemic gap across the cohort rather than amplitude-specific.

♻️ Suggested fix
     def _identify_user(self, user_data: AmplitudeUserData) -> None:
         payload = {"api_key": self.api_key, "identification": json.dumps([user_data])}
 
-        response = requests.post(self.url, data=payload, timeout=10)
-        record_integration_health(self.config, response.status_code)
-        logger.debug(
-            "Sent event to Amplitude. Response code was: %s" % response.status_code
-        )
+        try:
+            response = requests.post(self.url, data=payload, timeout=10)
+        except requests.exceptions.RequestException:
+            logger.exception("Failed to send event to Amplitude.")
+            record_integration_health(self.config, 503)
+            return
+        record_integration_health(self.config, response.status_code)
+        logger.debug(
+            "Sent event to Amplitude. Response code was: %s" % response.status_code
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response = requests.post(self.url, data=payload, timeout=10)
record_integration_health(self.config, response.status_code)
def _identify_user(self, user_data: AmplitudeUserData) -> None:
payload = {"api_key": self.api_key, "identification": json.dumps([user_data])}
try:
response = requests.post(self.url, data=payload, timeout=10)
except requests.exceptions.RequestException:
logger.exception("Failed to send event to Amplitude.")
record_integration_health(self.config, 503)
return
record_integration_health(self.config, response.status_code)
logger.debug(
"Sent event to Amplitude. Response code was: %s" % response.status_code
)

afsuyadi and others added 28 commits July 9, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant