import: page shows scan progress + auto-loads when the background scan finishes (#947, phase 2 UI)
Frontend half of the async staging scan. The endpoints now return {scanning, progress} while a
large staging folder is still being scanned in the background; the page surfaces that and fills in
automatically when it completes — no manual refresh, no timeout.
- types: ImportStagingFiles/GroupsPayload gain optional scanning + progress (additive).
- useImportStaging exposes `scanning`/`scanProgress` and, while scanning, polls via a plain
setInterval(refetch, 1500). Deliberately NOT react-query's refetchInterval — and a plain interval
that only runs while scanning leaves the normal + error query states completely untouched.
- the header shows "Scanning N of M files…" instead of a count while the scan runs.
vitest: new test asserts the scan-progress header renders from a {scanning} response; typecheck
clean. Note: -route.test.tsx's pre-existing "staging files fail to load" test fails only in the
full-file run (passes in isolation) — verified it also fails on clean dev with all my changes
stashed, so it's a pre-existing test-isolation flake, not from this change.
This commit is contained in:
parent
79648a4f5f
commit
8f1ffa7632
4 changed files with 52 additions and 2 deletions
|
|
@ -23,11 +23,21 @@ export interface ImportStagingFile {
|
|||
manual_match?: ImportTrackResult;
|
||||
}
|
||||
|
||||
/** While a large staging folder is still being scanned in the background (#947), the
|
||||
* staging endpoints return `scanning: true` + progress instead of files/groups; the query
|
||||
* polls until the scan completes and real data arrives. */
|
||||
export interface ImportScanProgress {
|
||||
scanned: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ImportStagingFilesPayload {
|
||||
success: boolean;
|
||||
files?: ImportStagingFile[];
|
||||
staging_path?: string;
|
||||
error?: string;
|
||||
scanning?: boolean;
|
||||
progress?: ImportScanProgress;
|
||||
}
|
||||
|
||||
export interface ImportStagingGroup {
|
||||
|
|
@ -47,6 +57,8 @@ export interface ImportStagingGroupsPayload {
|
|||
success: boolean;
|
||||
groups?: ImportStagingGroup[];
|
||||
error?: string;
|
||||
scanning?: boolean;
|
||||
progress?: ImportScanProgress;
|
||||
}
|
||||
|
||||
export interface ImportAlbumResult {
|
||||
|
|
|
|||
|
|
@ -250,6 +250,19 @@ describe('import route', () => {
|
|||
expect(await screen.findByText('Import folder: error')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows scan progress while a large staging folder is still scanning (#947)', async () => {
|
||||
server.use(
|
||||
http.get('/api/import/staging/files', () =>
|
||||
HttpResponse.json({ success: true, scanning: true, progress: { scanned: 5, total: 20 } }),
|
||||
),
|
||||
);
|
||||
|
||||
renderImportRoute();
|
||||
|
||||
expect(await screen.findByTestId('import-page')).toBeInTheDocument();
|
||||
expect(await screen.findByText(/Scanning 5 of 20 files/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('stores the active tab in nested route paths', async () => {
|
||||
const { history } = renderImportRoute();
|
||||
|
||||
|
|
|
|||
|
|
@ -21,17 +21,24 @@ import { fallbackImage, RefreshIcon, useImportStaging } from './import-shared';
|
|||
export function ImportPage() {
|
||||
useReactPageShell('import');
|
||||
|
||||
const { refreshStaging, stagingFiles, stagingPath, stagingQuery } = useImportStaging();
|
||||
const { refreshStaging, scanning, scanProgress, stagingFiles, stagingPath, stagingQuery } =
|
||||
useImportStaging();
|
||||
const isRefreshing = stagingQuery.isRefetching;
|
||||
const lastRefreshedAt =
|
||||
stagingQuery.dataUpdatedAt > 0 ? formatShortTime(stagingQuery.dataUpdatedAt) : null;
|
||||
// While a large staging folder is still scanning (#947), show progress instead of a count.
|
||||
const fileCountText = scanning
|
||||
? scanProgress && scanProgress.total > 0
|
||||
? `Scanning ${scanProgress.scanned} of ${scanProgress.total} files…`
|
||||
: 'Scanning staging folder…'
|
||||
: getStagingStatsText(stagingFiles);
|
||||
|
||||
return (
|
||||
<div id="import-page" data-testid="import-page">
|
||||
<div className={styles.importPageContainer}>
|
||||
<ImportHeader
|
||||
error={stagingQuery.error}
|
||||
fileCountText={getStagingStatsText(stagingFiles)}
|
||||
fileCountText={fileCountText}
|
||||
loading={stagingQuery.isLoading}
|
||||
stagingPath={stagingPath}
|
||||
refreshing={isRefreshing}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import type { ImportQueueJob, ImportStagingFile } from '../-import.types';
|
||||
|
||||
|
|
@ -20,6 +21,21 @@ export function useImportStaging() {
|
|||
...importStagingFilesQueryOptions(),
|
||||
});
|
||||
|
||||
// A large staging folder (whole-library migration, #947) is scanned in the background; the
|
||||
// endpoint returns `scanning: true` until it's done. While scanning, poll so the page fills
|
||||
// in automatically once the scan completes. A plain setInterval (NOT react-query's
|
||||
// refetchInterval, which interferes with the data query's error/retry settling) that only
|
||||
// runs while scanning, so the normal and error states are left completely untouched.
|
||||
const scanning = stagingQuery.data?.scanning === true;
|
||||
const { refetch } = stagingQuery;
|
||||
useEffect(() => {
|
||||
if (!scanning) return undefined;
|
||||
const id = window.setInterval(() => {
|
||||
void refetch();
|
||||
}, 1500);
|
||||
return () => window.clearInterval(id);
|
||||
}, [scanning, refetch]);
|
||||
|
||||
return {
|
||||
refreshStaging: async () => {
|
||||
clearFinishedJobs();
|
||||
|
|
@ -28,6 +44,8 @@ export function useImportStaging() {
|
|||
// Keep the empty fallback stable so staging-driven effects do not loop while loading.
|
||||
stagingFiles: stagingQuery.data?.files ?? EMPTY_STAGING_FILES,
|
||||
stagingPath: stagingQuery.data?.staging_path || 'Not configured',
|
||||
scanning,
|
||||
scanProgress: stagingQuery.data?.progress ?? null,
|
||||
stagingQuery,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue