-
Bug
-
Resolution: Unresolved
-
Normal
-
None
-
quay-v3.16.1
-
Security & Compliance
-
False
-
-
False
-
-
Bug Summary
The "Enabled" checkbox in Repository Mirroring applies changes immediately via API, but also incorrectly marks the form as dirty, causing the "Update Mirror" button to enable unnecessarily.
Current Behavior (BUG)
When toggling the "Enabled" checkbox:
- ✅ API call happens immediately (toggleMirroring called)
- ✅ Success message appears: "Mirror enabled/disabled successfully"
- ❌ BUG: "Update Mirror" button becomes enabled (form marked as dirty)
- ❌ User might click "Update Mirror" button again (re-applying same change unnecessarily)
Expected Behavior
When toggling the "Enabled" checkbox:
- ✅ API call happens immediately
- ✅ Success message appears
- ✅ Checkbox visual state updates
- ✅ "Update Mirror" button remains disabled (no pending changes - already applied!)
The Problem
Since the "Enabled" toggle already applies immediately via API:
- There is nothing pending to update
- The "Update Mirror" button should not enable
- Clicking "Update Mirror" would re-submit the same enabled/disabled state (redundant)
- This creates confusion: "The change already happened, why is the button enabled?"
Steps to Reproduce
- Create repository with existing mirror configuration
- Navigate to Mirroring tab
- Observe "Update Mirror" button is disabled
- Uncheck the "Enabled" checkbox
- Observe:
- ✅ Success message: "Mirror disabled successfully"
- ❌ BUG: "Update Mirror" button becomes enabled
- Click "Update Mirror" button
- Observe: Another API call is made, re-applying the same disabled state (redundant)
Expected Result (Step 5)
- ✅ Success message appears
- ✅ "Update Mirror" button remains disabled (no pending changes)
Root Cause
Code Location: /web/src/routes/RepositoryDetails/Mirroring/MirroringConfiguration.tsx:87-96
Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlcustomOnChange={async (checked, onChange) => { try { await toggleMirroring(namespace, repoName, checked); // ← Immediate API call ✓ onChange(checked); // ← BUG: This marks form as dirty! ✗ addAlert({ variant: AlertVariant.Success, title: `Mirror ${checked ? 'enabled' : 'disabled'} successfully`, }); } catch (err) { // ... } }}
The Bug:
- Line 90: onChange(checked) updates the form field value
- React Hook Form sees this as a field change
- Form is marked as "dirty" (isDirty: true)
- "Update Mirror" button enables per the !formHook.isDirty check
Code Location: /web/src/routes/RepositoryDetails/Mirroring/Mirroring.tsx:200-211
Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml<Button isDisabled={ (configHook.config && !formHook.isDirty) || // ← Becomes false when form dirty // ... other validations } > Update Mirror </Button>
Impact
- Redundant API Calls: Users might click "Update Mirror" after toggling "Enabled", sending duplicate requests
- User Confusion: "The change already applied, why is the button enabled?"
- Inconsistent State: Form shows pending changes when nothing is actually pending
- Poor UX: Users unsure if they need to click "Update Mirror" or not
Proposed Fix
Option 1: Don't call onChange (Recommended)
Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlcustomOnChange={async (checked, onChange) => { try { await toggleMirroring(namespace, repoName, checked); // DON'T call onChange(checked) - it marks form as dirty // Just update the checkbox visual state directly if needed addAlert({ variant: AlertVariant.Success, title: `Mirror ${checked ? 'enabled' : 'disabled'} successfully`, }); } catch (err) { // ... } }}
Option 2: Reset form dirty state after immediate action
Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yamlcustomOnChange={async (checked, onChange) => { try { await toggleMirroring(namespace, repoName, checked); onChange(checked); formHook.reset(formHook.formValues); // Reset dirty state addAlert({ ... }); } catch (err) { // ... } }}
Option 3: Separate immediate controls from form
Move "Enabled" toggle outside the form as a standalone control, not part of the form submission flow.
Additional Context
- This creates confusion about when "Update Mirror" button should be used
- Makes testing difficult (unclear what the expected button state should be)
Acceptance Criteria
- [ ] Toggling "Enabled" checkbox applies change immediately via API
- [ ] "Update Mirror" button remains disabled after toggling "Enabled"
- [ ] Form is NOT marked as dirty after toggling "Enabled"
- [ ] Clicking "Update Mirror" does not re-submit the same enabled/disabled state
- [ ] Test cases verify button stays disabled after immediate actions