Skip to content

ENG-3451: Fix Manage integration modal reporting dirty on mount#7934

Open
gilluminate wants to merge 2 commits intomainfrom
gill/ENG-3451/fix-integration-modal-dirty
Open

ENG-3451: Fix Manage integration modal reporting dirty on mount#7934
gilluminate wants to merge 2 commits intomainfrom
gill/ENG-3451/fix-integration-modal-dirty

Conversation

@gilluminate
Copy link
Copy Markdown
Contributor

@gilluminate gilluminate commented Apr 15, 2026

Ticket ENG-3451

Description Of Changes

Clicking Manage on an integration connection opened the configure modal with the form already reporting as dirty: the Save button was enabled and cancelling triggered the "unsaved changes" warning even though the user hadn't touched any fields.

Root cause: the form's dirty detection was !isEqual(Form.useWatch(..), initialValues), comparing a manually constructed initialValues object against the Ant Design form store. The two drift on mount for several reasons — conditionally rendered fields (dataset, system_fides_key) present in initialValues but not registered with the form, async-loaded secret schemas, and value coercions like String(boolean) — so the deep-equality check returned false before the user did anything.

Swapped that logic for Ant Design's built-in form.isFieldsTouched(), which only flips to true once the user has actually interacted with a field. Form.useWatch is retained to drive re-renders for the existing validity check.

Code Changes

  • clients/admin-ui/src/features/integrations/add-integration/ConfigureIntegrationForm.tsx — replaced the useMemo + isEqual dirty check with form.isFieldsTouched(); dropped the now-unused isEqual import.

Steps to Confirm

  1. Run the admin UI dev server (npm run dev in clients/admin-ui/) with a local backend and log in.
  2. Navigate to Integrations, pick any existing integration, and click Manage.
  3. Verify the Save button is disabled on open, and closing the modal closes immediately with no "unsaved changes" prompt.
  4. Edit any field (e.g. Description); verify Save becomes enabled and the close-confirm prompt appears on cancel/overlay click.
  5. Repeat on a DataHub integration (renders the Dataset field) and on a Website / Manual Task / Jira Ticket integration (omits the System field) to cover the conditional-rendering variants that originally triggered the bug.
  6. Save a real edit end-to-end to confirm submit still works.

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
    • No followup issues
  • Database migrations:
    • No migrations
  • Documentation:
    • No documentation updates required

gilluminate and others added 2 commits April 15, 2026 12:27
Replace fragile deep-equality dirty check with Ant Design's
form.isFieldsTouched(). The previous logic compared Form.useWatch output
to a manually constructed initialValues object, which mismatched on
mount (conditionally rendered fields, async-loaded secret schemas,
boolean-to-string coercions), causing the Save button to be enabled
and the close-confirm prompt to trigger before any user interaction.

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

vercel bot commented Apr 15, 2026

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

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
fides-plus-nightly Ignored Ignored Apr 15, 2026 6:31pm
fides-privacy-center Ignored Ignored Apr 15, 2026 6:31pm

Request Review

@github-actions
Copy link
Copy Markdown

Title Lines Statements Branches Functions
admin-ui Coverage: 8%
6.1% (2663/43633) 5.22% (1293/24738) 4.19% (542/12913)
fides-js Coverage: 78%
78.98% (1962/2484) 65.55% (1214/1852) 72.57% (336/463)
privacy-center Coverage: 88%
85.97% (331/385) 81.36% (179/220) 78.87% (56/71)

@gilluminate gilluminate marked this pull request as ready for review April 15, 2026 20:17
@gilluminate gilluminate requested a review from a team as a code owner April 15, 2026 20:17
@gilluminate gilluminate requested review from kruulik and removed request for a team April 15, 2026 20:17
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Code Review: ENG-3451 — Fix Manage integration modal dirty state on mount

Summary

This is a clean, minimal, and correct fix. The root cause diagnosis in the PR description is accurate: manually deep-comparing Form.useWatch output against a hand-built initialValues object is fragile because the two representations can differ on mount due to conditional field registration, async secret schema loading, and type coercions — all before the user touches anything.

What the fix does

Replaces the useMemo + isEqual dirty check with form.isFieldsTouched(), which is Ant Design's built-in mechanism for tracking whether a user has actually interacted with any field. This is the idiomatic and semantically correct approach for "has the user changed anything?"

Observations

Reactivity is preserved. Form.useWatch([], form) (kept for the validity useEffect) triggers a re-render on any field value change, so isDirty (now an inline expression) is recomputed at the right times. The onFormStateChange effect's dependency on isDirty will fire correctly when the boolean flips.

useMemo import is correctly retained — it's still used for systemOptions, onSystemSearch, and initialValues elsewhere in the component. Only the now-unused isEqual import is removed.

form.isFieldsTouched() called with no arguments — this uses the default allTouched = false, meaning it returns true when any field has been touched. That's the correct semantics for a "form is dirty" check.

Reset behaviour — Ant Design's form.resetFields() also resets the touched state, so if the form is reset after a successful save, isDirty will correctly return to false.

Minor note

isDirty is now evaluated inline in the render body rather than in a useMemo. This is fine here since form.isFieldsTouched() is a cheap synchronous call and re-renders are already gated by Form.useWatch. No action needed, just worth being aware of if this pattern is adopted in heavier forms.

No issues found

The change is correct, minimal, and well-scoped. No test coverage exists for this component (pre-existing gap, not introduced here). The manual Steps to Confirm in the PR description cover the important variants (DataHub with dataset field, integrations without system field, normal edit/save flow).

🔬 Codegraph: connected (46725 nodes)


💡 Write /code-review in a comment to re-run this review.

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