Skip to content

feat(integrations): disconnect individual checks per task#2491

Merged
tofikwest merged 3 commits intomainfrom
tofik/disconnect-integration-from-evidence
Apr 9, 2026
Merged

feat(integrations): disconnect individual checks per task#2491
tofikwest merged 3 commits intomainfrom
tofik/disconnect-integration-from-evidence

Conversation

@tofikwest
Copy link
Copy Markdown
Contributor

Summary

Lets users disconnect a single integration check (e.g. GitHub Branch Protection) from one evidence task while keeping the integration fully connected for every other task that uses it. Previously the only way to drop one check was to fully disconnect the integration and reconnect with a subset — slow, destructive, and lost history.

No DB migration. Disable state lives under IntegrationConnection.metadata.disabledTaskChecks as a map of taskId → checkId[]. The metadata column is already Json?, already returned to the frontend, and the existing PATCH connection endpoint already does a shallow merge, so there is nothing schema-level to change.

Works for both static and dynamic integrations — both types share the same IntegrationConnection table and the same check run pipeline (getManifest() is source-agnostic), so one filter point covers both.

What changed

Backend (apps/api)

  • utils/disabled-task-checks.ts — pure, immutable helpers: parseDisabledTaskChecks, isCheckDisabledForTask, withCheckDisabled, withCheckEnabled.
  • services/task-integration-checks.service.ts — orchestrates disable/enable with org scoping and validates that the task, connection, and check all belong together.
  • controllers/task-integrations.controller.ts
    • GET /v1/integrations/tasks/:taskId/checks now returns isDisabledForTask on each check.
    • POST /v1/integrations/tasks/:taskId/run-check returns 400 if the (task, check) pair is disconnected.
    • New POST /v1/integrations/tasks/:taskId/checks/disconnect
    • New POST /v1/integrations/tasks/:taskId/checks/reconnect
    • Both gated by integration:update.
  • trigger/integration-platform/run-integration-checks-schedule.ts — daily orchestrator filters disabled checks out of each task's run list before batching.
  • trigger/integration-platform/run-task-integration-checks.ts — defensive second filter in the worker for races between schedule and execution; the rest of the flow (lastSyncAt, task status eval, return payload) is untouched.

Frontend (apps/app)

  • hooks/useIntegrationChecks.ts — adds disconnectCheckFromTask / reconnectCheckToTask with SWR optimistic updates and rollback on error.
  • components/TaskIntegrationChecks.tsx
    • New Unplug icon button on each connected check, behind an AlertDialog confirm.
    • New "Disconnected from this task" section with per-check Reconnect buttons.
    • Empty-state branching adjusted so disabled-but-reconnectable checks are never hidden behind the "nothing connected" fallback.

Data shape (example)

{
  "metadata": {
    "connectionName": "My GitHub",
    "disabledTaskChecks": {
      "tsk_abc123": ["branch_protection", "dependabot"],
      "tsk_xyz789": ["sanitized_inputs"]
    }
  }
}

Backward compatibility

  • Existing connections have no disabledTaskChecks key → parseDisabledTaskChecks returns {} → every filter/predicate no-ops → identical behavior to before.
  • isDisabledForTask is a new additive field on the check DTO; no existing consumer needs to change.
  • No breaking changes to existing endpoints, no schema changes, no migrations.
  • If the whole integration is eventually disconnected, the metadata (including disable state) is preserved on the row — and if the user creates a fresh connection, that's a fresh row with empty metadata, i.e. "reconnect = fresh state" for free.

Test plan

  • API unit tests — apps/api (Jest) — 32/32 passing
    • utils/disabled-task-checks.spec.ts (22 tests): parse, isDisabled, withDisabled, withEnabled — empty/null inputs, malformed data, idempotency, immutability.
    • services/task-integration-checks.service.spec.ts (10 tests): success path, idempotent disconnect, org scoping (connection from another org → 404), task not in org → 404, unknown check id → 400, reconnect cleans up empty lists.
  • Frontend hook tests — apps/app (Vitest) — 4/4 passing
    • Loads isDisabledForTask from the API response.
    • disconnectCheckFromTask POSTs to the disconnect endpoint and updates the SWR cache.
    • reconnectCheckToTask POSTs to the reconnect endpoint and updates the SWR cache.
    • Rolls back the optimistic update when the request fails.
  • Lint clean on all touched/added files
  • Typecheck clean on all touched/added files (pre-existing unrelated errors in other files remain untouched)

Manual QA (for reviewer)

  • On a task with GitHub connected, click the new unplug icon on Branch Protection → confirm dialog appears → confirm → check moves to "Disconnected from this task".
  • Click Reconnect → check returns to the active list.
  • Try the Run button on a disconnected check via API directly → should return 400.
  • Trigger the daily orchestrator manually (or wait for 6am UTC) → confirm the disabled check is not in the queued payload (visible in Trigger.dev logs).
  • Disconnect a check for Task A, verify Task B using the same check still runs on schedule.
  • Disconnect the whole integration and reconnect → disable state is reset (new connection = fresh metadata).

🤖 Generated with Claude Code

…ring down the whole connection

Lets users disconnect a single integration check (e.g. GitHub branch protection)
from one evidence task while keeping the integration connected for every other
task that uses it. Previously users had to fully disconnect the integration and
reconnect with a subset of checks, which was slow and destructive.

Disable state lives under `IntegrationConnection.metadata.disabledTaskChecks` —
no new table, no migration. Works uniformly for static manifest-based and
dynamic integrations since both share the same connection pipeline.

- Per-task disable helpers with defensive merges (no mutation of inputs)
- `TaskIntegrationChecksService` handles enable/disable with org scoping
- New endpoints: POST `/v1/integrations/tasks/:taskId/checks/{disconnect,reconnect}`
- `runCheckForTask` returns 400 when invoked for a disabled (task, check) pair
- Daily orchestrator and per-task worker both filter disabled checks out of
  the run list (orchestrator primary, worker defensive for race conditions)
- Task detail UI: per-check disconnect button with confirm dialog, new
  "Disconnected from this task" section with reconnect buttons
- SWR hook exposes optimistic `disconnectCheckFromTask` / `reconnectCheckToTask`
  with rollback on error

Tests: 32 new API unit tests + 4 frontend hook tests covering success,
idempotency, org scoping, unknown checks, and optimistic rollback.
@cursor
Copy link
Copy Markdown

cursor bot commented Apr 9, 2026

PR Summary

Medium Risk
Changes task integration execution paths (manual runs and scheduled Trigger.dev runs) to honor per-task disabled checks, which could unintentionally skip checks if metadata handling is wrong. No schema changes, and behavior is additive with fallback to existing behavior when metadata is absent.

Overview
Adds per-task disable state for integration checks, stored in IntegrationConnection.metadata.disabledTaskChecks, plus shared helpers (parseDisabledTaskChecks, isCheckDisabledForTask, withCheckDisabled, withCheckEnabled) and unit tests.

Updates the task integrations API to return isDisabledForTask, block manual runs of disabled checks, and add new endpoints POST /v1/integrations/tasks/:taskId/checks/disconnect and .../reconnect backed by TaskIntegrationChecksService (org/task/manifest validation).

Updates the daily scheduler and worker to filter out disabled checks (including a defensive re-check in the worker), and updates the task UI + useIntegrationChecks hook to support disconnect/reconnect with optimistic SWR updates and a confirmation dialog.

Reviewed by Cursor Bugbot for commit 7795398. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Apr 9, 2026 6:33pm
comp-framework-editor Ready Ready Preview, Comment Apr 9, 2026 6:33pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped Apr 9, 2026 6:33pm

Request Review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 383f85e. Configure here.

Comment thread apps/api/src/integration-platform/services/task-integration-checks.service.ts Outdated
…ect PR

1. Keep the disconnect confirmation dialog open during the async request so
   the "Disconnecting…" state is visible and any API error is surfaced in
   the dialog context. Radix's AlertDialogAction auto-closes on click, which
   was firing before handleConfirmDisconnect awaited — users never saw the
   loading state, and failures rendered on the main page behind the backdrop.
   Fix: e.preventDefault() in the action onClick, ignore close attempts
   while togglingCheck is set, and render errors inline inside the dialog.

2. Remove the unused TaskIntegrationChecksService.getDisabledCheckIdsForTask
   method. It was only referenced by its own tests — the controller reads
   disable state directly via the isCheckDisabledForTask utility, so the
   service method was dead code. Tests updated accordingly.
@vercel vercel bot temporarily deployed to Preview – portal April 9, 2026 18:31 Inactive
@tofikwest tofikwest merged commit 132aa95 into main Apr 9, 2026
13 checks passed
@tofikwest tofikwest deleted the tofik/disconnect-integration-from-evidence branch April 9, 2026 18:58
@claudfuen
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.19.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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.

2 participants