Skip to content

ENG-3000: add Privacy requests tab to integrations (alternative to #8121)#8126

Draft
adamsachs wants to merge 12 commits intomainfrom
ENG-3000-privacy-requests-tab
Draft

ENG-3000: add Privacy requests tab to integrations (alternative to #8121)#8126
adamsachs wants to merge 12 commits intomainfrom
ENG-3000-privacy-requests-tab

Conversation

@adamsachs
Copy link
Copy Markdown
Contributor

@adamsachs adamsachs commented May 6, 2026

Ticket ENG-3000

Note

This is a sibling PR to #8121, which proposes the same fix with a more scoped UI: an Enable toggle and a datasets picker as inline fields on the create/edit form. This PR keeps everything #8121 does for the bug fix, but moves the privacy-request controls to a dedicated tab and adds tag/marketing fixes that surfaced along the way. Pick one direction; the other can be closed.

Description Of Changes

Three pieces, layered:

1. Stop using deprecated /system/{key}/connection endpoints (same as #8121, included here so this PR is self-contained against main). ConfigureIntegrationForm was wired to two deprecated, system-scoped endpoints that conflate connection lifecycle with system linking and don't compose with multi-integration systems. Refactored handleSubmit to a uniform two-step flow:

  1. Create / update the connection without any system context — new SaaS connections via POST /connection/instantiate/{type}, everything else via PATCH /connection.
  2. Reconcile the system link via PUT /connection/{key}/system-links — only when the desired state differs from initialSystemFidesKey.

This fixes the original ENG-3000 bug (SaaS create + system selected fails) and the secondary bug where clearing the System field on edit didn't actually unlink.

2. Add a "Privacy requests" tab on the integration detail page. New IntegrationPrivacyRequests component, placed after Data discovery (discovery surfaces the data, privacy requests act on it). Two stacked sections:

  • Status — a Switch toggle backed by ConnectionConfig.disabled. Always visible. Immediate-save on flip via PATCH /connection. No confirmation modal — toast on success/error.
  • Datasets — only rendered for SystemType.DATABASE integrations. Lists currently linked datasets (router-link to dataset detail; fides_key shown as a monospaced Tag on the line below the name; ctl_dataset.description as the secondary line). "Link dataset" opens a search modal of unlinked datasets. Both flows write through usePutDatasetConfigsMutation (the PUT endpoint replaces the whole linked-set, so unlink/link are expressed as a PUT of the new desired key list). Bulk responses surface per-entry failed messages via toast — same pattern as patchConnectionConfig in the System → Integrations form.

The tab itself is gated client-side on connection_type, mirroring supportsSystemLinking — shown for everything except WEBSITE (consent only) and DATAHUB (sync only).

3. Add DSR Automation marketing tag to PostgreSQL, RDS MySQL, and Google Cloud SQL for MySQL. They all extend SQLConnector and support privacy requests, but their integration-picker tag arrays were missing the tag (peer connectors like BigQuery / MySQL / RDS Postgres / Google Cloud SQL Postgres already had it). Reordered Postgres tags to (DSR Automation, Discovery, Detection) to match the rest.

4. Documentation comments on ConnectionConfig.disabled (model + 2 schemas) clarifying that it gates DSR execution only — not discovery monitors, not connection tests. No behaviour change.

Known limitations

  • enabled_actions is not exposed in this UI yet. The new top-level Integrations form's create payload doesn't include enabled_actions, so connections it creates land with NULL. The DSR runner treats NULL as "all actions enabled" for access/erasure but disables consent (it explicitly requires enabled_actions IS NOT NULL AND contains consent — see graph_task.py:1100-1115). For most integration types this is fine; SaaS consent integrations created through this form need their request types set via the System → Integrations form afterward. Surfacing the field on the Privacy requests tab is a deferred followup (needs a small base-schema addition so POST /connection/instantiate/{type} and PATCH /connection accept enabled_actions instead of silently dropping it).

Code Changes

  • clients/admin-ui/src/features/integrations/add-integration/ConfigureIntegrationForm.tsx:
    • Replace the three-way connection-creation branch with a binary one (SaaS-create → unlinked instantiate; everything else → PATCH /connection).
    • Extract a single reconcileSystemLink() helper that fires only when values.system_fides_key !== initialSystemFidesKey, sends [] to unlink.
    • Drop usage of usePatchSystemConnectionConfigsMutation, useCreatePlusSaasConnectionConfigMutation, useCreateSassConnectionConfigMutation.
  • clients/admin-ui/src/features/integrations/IntegrationPrivacyRequests.tsx (new): tab component.
  • clients/admin-ui/src/features/integrations/hooks/useFeatureBasedTabs.tsx: import the component, accept a supportsPrivacyRequests prop, push the tab after Data discovery.
  • clients/admin-ui/src/pages/integrations/[id]/index.tsx: compute supportsPrivacyRequests based on connection_type.
  • clients/admin-ui/src/features/integrations/integration-type-info/postgreSQLInfo.tsx, rdsMySQLInfo.tsx, googleCloudSQLMySQLInfo.tsx: add DSR Automation tag.
  • src/fides/api/models/connectionconfig.py + src/fides/api/schemas/connection_configuration/connection_config.py: doc comments on disabled.
  • clients/admin-ui/cypress/e2e/integration-management.cy.ts: update the "add a new integration associated with a system" test to wait on PATCH /connection + PUT /connection/{key}/system-links instead of the deprecated PATCH /system/{key}/connection.

Steps to Confirm

  1. The original ENG-3000 bug. On the new top-level Integrations management UI, create a SaaS integration with a System selected. Confirm the integration is created, the system link is set, the saas_config + dataset exist. Network: POST /api/v1/connection/instantiate/{type} then PUT /api/v1/connection/{key}/system-links. No call to /system/{key}/connection.
  2. Edit, clear system. Edit an existing linked integration → clear the System field → save. GET /api/v1/connection/{key}/system-links returns [].
  3. Edit, no system change. Edit name/description only — setSystemLinks is not called (no-op skip).
  4. Privacy requests tab — toggle. On a database integration's detail page, switch to "Privacy requests". Flip the toggle. Toast appears; GET /api/v1/connection/{key} reflects new disabled value. A privacy request that would have hit this connection now skips it (graph_task.skip_if_disabled).
  5. Privacy requests tab — datasets. On a database integration: empty state shows the placeholder text and "Link dataset" button. Clicking opens a search modal of unlinked datasets, each row showing [name] on its own line with a [fides_key] tag below and the description underneath. Linking persists via PUT /api/v1/connection/{key}/datasetconfig. Unlinking shows a confirmation modal.
  6. Privacy requests tab — link error surfacing. Try to link a dataset that the backend rejects (e.g. a BigQuery dataset that's missing namespace metadata). Confirm the error toast shows the failure message and no "Dataset linked successfully" toast appears.
  7. Privacy requests tab gating. Confirm the tab does NOT appear for a Website or DataHub integration. Confirm it DOES appear for SaaS / database / email / manual_task / jira_ticket.
  8. Marketing tag. Open the integration picker, search for "Postgres" / "RDS MySQL" / "Cloud SQL - MySQL" — each card shows a "DSR Automation" tag.

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Add a db-migration This indicates that a change includes a database migration label to the entry if your change includes a DB migration
    • Add a high-risk This issue suggests changes that have a high-probability of breaking existing code label to the entry if your change includes a high-risk change (i.e. potential for performance impact or unexpected regression) that should be flagged
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • All UX related changes have been reviewed by a designer
    • No UX review needed
  • Followup issues:
    • Followup issues created — Privacy requests tab: surface enabled_actions / Request types
    • No followup issues
  • Database migrations:
    • No migrations
  • Documentation:
    • No documentation updates required

🤖 Generated with Claude Code

adamsachs and others added 5 commits May 6, 2026 15:48
… new Integrations form

ConfigureIntegrationForm relied on two deprecated, system-scoped endpoints
that conflate connection lifecycle with system linking:

  * PATCH /system/{fides_key}/connection
  * POST  /(plus/)system/{fides_key}/connection/instantiate/{type}

Both bake a single system into the connection-create/update call. That
implicitly assumes one-integration-per-system semantics and produces
inconsistent behavior now that a system can have multiple linked
integrations — for example, creating a SaaS integration with a system
selected would error out (the patch path only routes to template
instantiation when secrets is truthy, so SaaS connectors without required
secrets fell through to the standard path and never got a saas_config or
dataset). The deprecation notes on both endpoints already point at the
right replacement: do connection work via the top-level /connection
endpoints, then manage the link via PUT /connection/{key}/system-links.

Refactor handleSubmit to that two-step flow:

  1. Create / update the connection without any system context. New SaaS
     connections go through POST /connection/instantiate/{type}; everything
     else (non-SaaS create, all edits) goes through PATCH /connection.
  2. Reconcile the system link via PUT /connection/{key}/system-links —
     only when the desired state differs from initialSystemFidesKey, so
     common edits (e.g. updating only secrets) skip a redundant idempotent
     call. Passing links: [] correctly unlinks, fixing the secondary bug
     where clearing the System field on edit silently left the prior link
     in place.

Drops the form's dependency on usePatchSystemConnectionConfigsMutation,
useCreatePlusSaasConnectionConfigMutation, and
useCreateSassConnectionConfigMutation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add comments to the model column and the two related Pydantic schemas
clarifying that this flag excludes the connection from privacy request
(DSR) execution only. It does not affect discovery monitors, connection
tests, or any non-DSR consumer; those have their own enable/disable
controls.

Comments only — no behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New IntegrationPrivacyRequests component, exposed as a top-level tab
alongside Linked system / Data discovery / etc. on the integration detail
page. Two stacked sections:

  * Status — a Switch toggle backed by ConnectionConfig.disabled, immediate
    save on flip via PATCH /connection (no confirmation modal). Always
    visible. Lets users enable / disable the integration for privacy
    request execution without re-opening the connection form.
  * Datasets — only rendered for SystemType.DATABASE integrations. Lists
    currently linked datasets (router-link to the dataset detail page,
    fides_key shown as a monospaced tag, ctl_dataset description as the
    secondary line). "Link dataset" opens a search modal of unlinked
    datasets. Both flows write through usePutDatasetConfigsMutation; the
    PUT endpoint replaces the whole linked-set, so unlink / link are
    expressed as a PUT of the new desired key list.

The tab is gated client-side on connection_type, mirroring the
supportsSystemLinking pattern: shown for everything except WEBSITE
(consent only) and DATAHUB (sync only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ectors

PostgreSQL, RDS MySQL, and Google Cloud SQL for MySQL all extend
SQLConnector and support privacy request execution, but their
integration-type-info marketing tag arrays were missing 'DSR Automation'
(only 'Discovery' / 'Detection' were set). Add the tag so the integration
picker cards accurately advertise the capability and match peer connectors
(BigQuery, MySQL, RDS Postgres, Google Cloud SQL Postgres, etc.).

Reordered postgreSQLInfo tags from ('Detection', 'Discovery') to
('DSR Automation', 'Discovery', 'Detection') for consistency with the
rest of the database connectors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 6, 2026

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

Project Deployment Actions Updated (UTC)
fides-plus-nightly Ready Ready Preview, Comment May 8, 2026 8:09pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
fides-privacy-center Ignored Ignored May 8, 2026 8:09pm

Request Review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Title Lines Statements Branches Functions
admin-ui Coverage: 8%
6.59% (3042/46126) 5.91% (1572/26575) 4.61% (630/13637)
fides-js Coverage: 78%
79.56% (2028/2549) 66.4% (1259/1896) 73.31% (349/476)
privacy-center Coverage: 85%
82.53% (364/441) 79.74% (189/237) 74.07% (60/81)

@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.23%. Comparing base (aaf933d) to head (fbed4e1).
⚠️ Report is 21 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8126      +/-   ##
==========================================
+ Coverage   85.19%   85.23%   +0.03%     
==========================================
  Files         638      638              
  Lines       42008    42011       +3     
  Branches     4937     4937              
==========================================
+ Hits        35788    35807      +19     
+ Misses       5111     5096      -15     
+ Partials     1109     1108       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The 'add a new integration associated with a system' test was waiting on
PATCH /api/v1/system/{key}/connection — the deprecated, system-scoped
endpoint this PR routes off of. With the form now creating the connection
via PATCH /connection and reconciling the link via
PUT /connection/{key}/system-links, the test waits on those two requests
instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
adamsachs and others added 2 commits May 7, 2026 08:10
…tab order

  * Datasets section paragraph: 'Datasets that use this integration to fulfill
    privacy requests.' (drops the longer 'are used to traverse and fulfill'
    phrasing).
  * Both dataset list surfaces (link modal selector and the main linked-
    datasets list) now stack the name and the fides_key Tag vertically inside
    the title slot, with the Tag using max-w-full so longer fides_keys can use
    the row's full width before truncating. Avoids the overflow seen when the
    fides_key is long enough to push the name into ellipsis.
  * Reorder the Privacy requests tab to sit immediately after Data discovery
    (instead of immediately after Linked system). Discovery surfaces the
    data; privacy requests act on it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nfig

The dataset-config bulk endpoint returns 200 OK even when individual
entries fail validation (e.g. 'Dataset for bigquery connection must
either have namespace metadata or the connection must have values for
the following fields...'). Previously the link/unlink handlers treated
any non-error HTTP response as success and showed a 'Dataset linked
successfully' toast even when the operation actually failed.

Inspect response.data.failed and bail out with the first failure's
message before claiming success — same pattern as patchConnectionConfig
in the System -> Integrations form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new top-level Integrations form's create path doesn't include
enabled_actions, so connections it creates land with NULL. The DSR runner
treats NULL as 'all actions enabled' for access/erasure but disables
consent. Add a comment next to the create payload explaining the
trade-off and pointing future readers at the System → Integrations form
as the workaround until the field is exposed on the Privacy requests
tab.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updated section headings, toggle label, and helper text per latest copy
review:

  * Section heading: 'Status' → 'Privacy request automation'
  * Toggle label: 'Enable for privacy requests' → 'Automate privacy
    requests with this integration'
  * Toggle helper: 'When enabled, this integration is used during privacy
    request execution.' → 'When off, Fides won't run privacy requests
    against data from this integration.'
  * Datasets section heading: 'Datasets' → 'Linked datasets'
  * Datasets section helper: 'Datasets that use this integration to
    fulfill privacy requests.' → 'Choose which datasets Fides traverses
    when fulfilling a request. Each dataset can be linked to one
    integration.'

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the per-row 'Link' Button + List in the Link datasets modal
with a Table-based bulk-select flow modeled on the Add datasets modal
elsewhere in the product:

  * Modal title now shows a 'N selected' Tag pill once the user picks
    something. Modal width grows from 520 to 720 to accommodate three
    columns.
  * Three columns: Dataset (name, falling back to fides_key when null),
    Fides key (monospaced, secondary), Description (secondary, em-dash
    placeholder when null). All three columns ellipsis with tooltips.
  * Antd Table rowSelection drives the checkbox column with built-in
    select-all in the header. preserveSelectedRowKeys is true so the
    user can search → select → search → select without losing prior
    picks. Search now matches name + fides_key + description.
  * Modal footer changes from a lone Cancel to Cancel + a primary
    'Link N dataset(s)' button (disabled when nothing is selected,
    label updates with the selection count). Confirms perform a single
    PUT with [...linked, ...selected]; on bulk-write failures the modal
    stays open with the first failure's message, so the user can adjust
    their selection and retry.
  * Pagination defaults to 10 rows, hidden on a single page. The body
    is height-bounded with scroll={{ y: 320 }} to keep the modal
    compact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant