fix(ui): stop a superseded Test Connection run from clobbering a newer result - #31136
fix(ui): stop a superseded Test Connection run from clobbering a newer result#31136IceS2 wants to merge 6 commits into
Conversation
…r result testConnection() kept its interval and expiry ids in an object allocated per call, so a second run never cancelled the first. The first run's two-minute timer stayed armed and, when it fired, reported the connection as untested over a result that had already succeeded. Nothing cleared either timer on unmount either, so closing the dialog mid-test left the poll hitting the API and setting state on an unmounted component. Hold both ids in a ref cleared on every new run and on unmount, and tag each run so a callback from a superseded one becomes a no-op.
The guard at the top of the interval tick is not enough: the callback then awaits getWorkflowData, and the run's 2-minute expiry can fire during that await, re-enabling the button. A newer run started from there was clobbered by the stale callback, which wrote its own result and cleared the *new* run's timers, leaving it polling nothing. Reported by gitar-bot on #31136.
|
Good catch — fixed in 9567a26. The reachable path is the run's own expiry: the poll callback awaits Covered by a test that drives exactly that sequence; verified to fail with the new guard removed. |
❌ PR checklist incompleteThis PR cannot be merged until the following are addressed on its linked issue:
The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically. Maintainers can bypass this check by adding the |
✅ Playwright Results — workflow succeededValidated commit ✅ 582 passed · ❌ 0 failed · 🟡 0 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky PerformanceBlocking targets: ✅ met · Optimization targets: 🟡 in progress Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting. 🕒 Full workflow signal wall (to summary) 55m 30s ⏱️ Max setup 3m 0s · max shard execution 18m 18s · max shard-job elapsed before upload 21m 41s · reporting 5s 🌐 201.69 requests/attempt · 2.80 app boots/UI scenario · 25.87% common-shard skew Optimization targets still in progress:
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
PR #31136 changed components/common/TestConnection/TestConnection.tsx but no targeted Playwright coverage ran, since the impact map had no mapping for that source path — it fell through as "unmapped" and only picked up generic canaries. Add mappings so future changes there trigger the specs that actually drive the component: TestConnectionModal.spec.ts (Basic), ConnectionConfigLayout.spec.ts (chromium), and ServiceForm.spec.ts / ApiServiceRest.spec.ts / AutoPilot.spec.ts (Ingestion), found by grepping for test-connection-btn clicks, the testConnection(page) helper, and ServiceBaseClass.createService() callers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
| Count | Rule |
|---|---|
| 8 | sonarjs/no-duplicate-string |
| 4 | i18next/no-literal-string |
| 2 | sonarjs/no-hardcoded-ip |
| 2 | react-hooks/exhaustive-deps |
| 1 | sonarjs/no-extra-arguments |
| 1 | sonarjs/cyclomatic-complexity |
| 1 | sonarjs/expression-complexity |
| 1 | jsx-a11y/control-has-associated-label |
| 1 | jsx-a11y/anchor-has-content |
All findings
| Location | Rule | Message | |
|---|---|---|---|
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:81:52 |
i18next/no-literal-string |
disallow literal string: Modal {connectionDisplayName} {errorMess |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:94:40 |
i18next/no-literal-string |
disallow literal string: Cancel Modal |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:95:41 |
i18next/no-literal-string |
disallow literal string: Confirm Modal |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:96:48 |
i18next/no-literal-string |
disallow literal string: Retry Modal |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:160:53 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 42 times. |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:236:31 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 6 times. |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:258:31 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 5 times. |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:500:24 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:537:24 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:622:31 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 6 times. |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:1100:52 |
sonarjs/no-hardcoded-ip |
Make sure using a hardcoded IP address 10.0.0.1 is safe here. |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:1113:25 |
sonarjs/no-hardcoded-ip |
Make sure using a hardcoded IP address 10.0.0.1 is safe here. |
| 🟡 | src/components/common/TestConnection/TestConnection.test.tsx:1278:7 |
sonarjs/no-extra-arguments |
This function expects no arguments, but 1 was provided. |
| 🟡 | src/components/common/TestConnection/TestConnection.tsx:130:4 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 12 which is greater than 10 authorized.","cost":2,"secondaryLocations":[{"line":130,"column":3,"endLine":130,"endColumn |
| 🟡 | src/components/common/TestConnection/TestConnection.tsx:193:5 |
sonarjs/expression-complexity |
Reduce the number of conditional operators (4) used in the expression (maximum allowed 3). |
| 🟡 | src/components/common/TestConnection/TestConnection.tsx:560:19 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/common/TestConnection/TestConnection.tsx:560:52 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/common/TestConnection/TestConnection.tsx:567:6 |
react-hooks/exhaustive-deps |
React Hook useMemo has a missing dependency: 't'. Either include it or remove the dependency array. |
| 🟡 | src/components/common/TestConnection/TestConnection.tsx:606:13 |
jsx-a11y/control-has-associated-label |
A control must be associated with a text label. |
| 🟡 | src/components/common/TestConnection/TestConnection.tsx:606:13 |
jsx-a11y/anchor-has-content |
Anchors must have content and the content must be accessible by a screen reader. |
| 🟡 | src/components/common/TestConnection/TestConnection.tsx:712:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has a missing dependency: 'clearTimers'. Either include it or remove the dependency array. |
Fix locally (fast - only checks files changed in this branch):
make ui-checkstyle-changed
Code Review ✅ Approved 1 resolved / 1 findingsAdds run ID tagging and timer cleanup to TestConnection to prevent superseded runs from clobbering newer results, addressing the stale-run guard finding. ✅ 1 resolved✅ Bug: Stale-run guard not re-checked after async getWorkflowData await
OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
|



Hitting Test Connection again while a run is still in flight leaves the first run's
timers alive. When they fire they overwrite the newer run's state, so the dialog can show
a stale success for a connection that has since been changed and now fails — or the
reverse.
TestConnectionnow keeps its timers in a ref and tags each run with an id. Callbacksfrom a superseded run are ignored, and every timer is cleared on unmount, which also stops
the component polling a workflow after the dialog is closed.