From 4b87f97785f105888d1f6ec7465e9b48cb810fc7 Mon Sep 17 00:00:00 2001 From: arc53-machine <232052973+arc53-machine@users.noreply.github.com> Date: Tue, 29 Sep 2026 15:11:40 +0100 Subject: [PATCH] Apply only the latest connectors load Overlapping loadConnectors calls applied whichever answer arrived last, so a load started before a connect could replace the newer connection list. The slice now records the latest request and ignores older answers. --- .../src/connectors/connectorsSlice.test.ts | 59 +++++++++++++++++++ frontend/src/connectors/connectorsSlice.ts | 16 ++++- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 frontend/src/connectors/connectorsSlice.test.ts diff --git a/frontend/src/connectors/connectorsSlice.test.ts b/frontend/src/connectors/connectorsSlice.test.ts new file mode 100644 index 00000000..6ca27c5d --- /dev/null +++ b/frontend/src/connectors/connectorsSlice.test.ts @@ -0,0 +1,59 @@ +import reducer, { + loadConnectors, + type ConnectorsState, +} from './connectorsSlice'; +import type { Connection } from './types'; + +const arg = { token: null }; +const connection = (id: string) => ({ id }) as unknown as Connection; +const payload = (ids: string[]) => ({ + catalog: [], + connections: ids.map(connection), +}); + +const pending = (state: ConnectorsState | undefined, requestId: string) => + reducer(state, loadConnectors.pending(requestId, arg)); + +describe('connectorsSlice loadConnectors', () => { + it('keeps the newer response when an older one arrives last', () => { + let state = pending(undefined, 'old'); + state = pending(state, 'new'); + state = reducer( + state, + loadConnectors.fulfilled(payload(['after-connect']), 'new', arg), + ); + state = reducer( + state, + loadConnectors.fulfilled(payload(['before-connect']), 'old', arg), + ); + expect(state.connections.map((c) => c.id)).toEqual(['after-connect']); + expect(state.loading).toBe(false); + }); + + it('stays loading while the latest request is still running', () => { + let state = pending(undefined, 'old'); + state = pending(state, 'new'); + state = reducer( + state, + loadConnectors.fulfilled(payload(['stale']), 'old', arg), + ); + expect(state.loading).toBe(true); + expect(state.connections).toEqual([]); + }); + + it('ignores the failure of an older request', () => { + let state = pending(undefined, 'old'); + state = pending(state, 'new'); + state = reducer( + state, + loadConnectors.rejected(new Error('boom'), 'old', arg), + ); + expect(state.failed).toBe(false); + state = reducer( + state, + loadConnectors.rejected(new Error('boom'), 'new', arg), + ); + expect(state.failed).toBe(true); + expect(state.loading).toBe(false); + }); +}); diff --git a/frontend/src/connectors/connectorsSlice.ts b/frontend/src/connectors/connectorsSlice.ts index 1bd96d51..08547d34 100644 --- a/frontend/src/connectors/connectorsSlice.ts +++ b/frontend/src/connectors/connectorsSlice.ts @@ -15,6 +15,12 @@ export type ConnectorsState = { loading: boolean; loaded: boolean; failed: boolean; + /** + * The `loadConnectors` call started last. Loads overlap (several screens + * refresh at once), and only its answer is applied: an older one could + * hold connections from before a connect. + */ + latestRequestId?: string | null; }; const initialState: ConnectorsState = { @@ -24,6 +30,7 @@ const initialState: ConnectorsState = { loading: false, loaded: false, failed: false, + latestRequestId: null, }; /** @@ -58,17 +65,22 @@ const connectorsSlice = createSlice({ }, extraReducers: (builder) => { builder - .addCase(loadConnectors.pending, (state) => { + .addCase(loadConnectors.pending, (state, action) => { + state.latestRequestId = action.meta.requestId; state.loading = true; state.failed = false; }) .addCase(loadConnectors.fulfilled, (state, action) => { + if (action.meta.requestId !== state.latestRequestId) return; + state.latestRequestId = null; state.loading = false; state.loaded = true; state.catalog = action.payload.catalog; state.connections = action.payload.connections; }) - .addCase(loadConnectors.rejected, (state) => { + .addCase(loadConnectors.rejected, (state, action) => { + if (action.meta.requestId !== state.latestRequestId) return; + state.latestRequestId = null; state.loading = false; state.failed = true; });