Skip to content

Commit 245441f

Browse files
authored
fix(dashboard): settle remote sync button state
1 parent e43a4d4 commit 245441f

5 files changed

Lines changed: 52 additions & 6 deletions

File tree

apps/dashboard/src/lib/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
useQuery,
1313
} from '@tanstack/react-query';
1414

15+
import { shouldPollRemoteStatus } from './project-sync-status';
1516
import type {
1617
CategoriesResponse,
1718
CombineDuplicateConflict,
@@ -232,6 +233,7 @@ export function remoteStatusOptions(projectId?: string) {
232233
queryKey: ['remote-status', projectId ?? ''],
233234
queryFn: () => fetchJson<RemoteStatusResponse>(url),
234235
staleTime: 5_000,
236+
refetchInterval: (query) => (shouldPollRemoteStatus(query.state.data) ? 1_000 : false),
235237
});
236238
}
237239

apps/dashboard/src/lib/project-sync-status.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
buildRemoteStatusItems,
77
formatRemoteRunCount,
88
getProjectSyncView,
9+
shouldPollRemoteStatus,
910
} from './project-sync-status';
1011

1112
describe('getProjectSyncView', () => {
@@ -50,6 +51,25 @@ describe('getProjectSyncView', () => {
5051
});
5152
});
5253

54+
it('returns to a clean sync action when a status refetch settles an overlapping sync', () => {
55+
const view = getProjectSyncView(
56+
{
57+
configured: true,
58+
available: true,
59+
sync_status: 'clean',
60+
run_count: 1,
61+
dirty_paths: [],
62+
},
63+
false,
64+
);
65+
66+
expect(view).toMatchObject({
67+
state: 'clean',
68+
actionLabel: 'Sync Project',
69+
canSync: true,
70+
});
71+
});
72+
5373
it('treats diverged history as a conflict-safe blocked state', () => {
5474
expect(
5575
getProjectSyncView({
@@ -154,3 +174,15 @@ describe('buildRemoteStatusItems', () => {
154174
expect(items.some((item) => item.startsWith('Last synced '))).toBe(true);
155175
});
156176
});
177+
178+
describe('shouldPollRemoteStatus', () => {
179+
it('polls only while the server reports an overlapping sync in progress', () => {
180+
expect(
181+
shouldPollRemoteStatus({ configured: true, available: true, sync_status: 'syncing' }),
182+
).toBe(true);
183+
expect(
184+
shouldPollRemoteStatus({ configured: true, available: true, sync_status: 'clean' }),
185+
).toBe(false);
186+
expect(shouldPollRemoteStatus(undefined)).toBe(false);
187+
});
188+
});

apps/dashboard/src/lib/project-sync-status.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ export function buildRemoteStatusItems(
6363
].filter((item): item is string => item !== undefined);
6464
}
6565

66+
export function shouldPollRemoteStatus(status: RemoteStatusResponse | undefined): boolean {
67+
return status?.sync_status === 'syncing';
68+
}
69+
6670
export function getProjectSyncView(
6771
status: RemoteStatusResponse | undefined,
6872
syncInFlight = false,

apps/dashboard/src/routes/index.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { type RunSourceFilter, RunSourceToolbar } from '~/components/RunSourceTo
1919
import { TargetsTab } from '~/components/TargetsTab';
2020
import {
2121
addProjectApi,
22+
remoteStatusOptions,
2223
syncRemoteResultsApi,
2324
useCompare,
2425
useEvalRuns,
@@ -248,17 +249,20 @@ function SingleProjectHome() {
248249
setSyncFeedback(null);
249250
try {
250251
const result = await syncRemoteResultsApi();
252+
queryClient.setQueryData(remoteStatusOptions().queryKey, result);
251253
setSyncFeedback(buildProjectSyncFeedback(result));
252-
await Promise.all([
254+
void Promise.all([
253255
queryClient.invalidateQueries({ queryKey: ['runs'] }),
254256
queryClient.invalidateQueries({ queryKey: ['experiments'] }),
255257
queryClient.invalidateQueries({ queryKey: ['compare'] }),
256258
queryClient.invalidateQueries({ queryKey: ['targets'] }),
257259
queryClient.invalidateQueries({ queryKey: ['remote-status', ''] }),
258-
]);
260+
]).catch(() => undefined);
259261
} catch (err) {
260262
setSyncFeedback(buildProjectSyncErrorFeedback(err, remoteStatus));
261-
await queryClient.invalidateQueries({ queryKey: ['remote-status', ''] });
263+
void queryClient
264+
.invalidateQueries({ queryKey: ['remote-status', ''] })
265+
.catch(() => undefined);
262266
} finally {
263267
setSyncInFlight(false);
264268
}

apps/dashboard/src/routes/projects/$projectId.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { type RunSourceFilter, RunSourceToolbar } from '~/components/RunSourceTo
1717
import { TargetsTab } from '~/components/TargetsTab';
1818
import {
1919
projectCompareOptions,
20+
remoteStatusOptions,
2021
syncRemoteResultsApi,
2122
useEvalRuns,
2223
useInfiniteProjectRunList,
@@ -150,18 +151,21 @@ function ProjectRunsTab({
150151
setSyncFeedback(null);
151152
try {
152153
const result = await syncRemoteResultsApi(projectId);
154+
queryClient.setQueryData(remoteStatusOptions(projectId).queryKey, result);
153155
const feedback = buildProjectSyncFeedback(result);
154156
setSyncFeedback(feedback);
155-
await Promise.all([
157+
void Promise.all([
156158
queryClient.invalidateQueries({ queryKey: ['projects', projectId, 'runs'] }),
157159
queryClient.invalidateQueries({ queryKey: ['projects', projectId, 'experiments'] }),
158160
queryClient.invalidateQueries({ queryKey: ['projects', projectId, 'compare'] }),
159161
queryClient.invalidateQueries({ queryKey: ['projects', projectId, 'targets'] }),
160162
queryClient.invalidateQueries({ queryKey: ['remote-status', projectId] }),
161-
]);
163+
]).catch(() => undefined);
162164
} catch (err) {
163165
setSyncFeedback(buildProjectSyncErrorFeedback(err, remoteStatus));
164-
await queryClient.invalidateQueries({ queryKey: ['remote-status', projectId] });
166+
void queryClient
167+
.invalidateQueries({ queryKey: ['remote-status', projectId] })
168+
.catch(() => undefined);
165169
} finally {
166170
setSyncInFlight(false);
167171
}

0 commit comments

Comments
 (0)