diff --git a/CHANGELOG.md b/CHANGELOG.md index a1707c36..3d466aab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - (**Migration**) Add a search option to ignore outdated matches - (**Migration**) Add a search option to ignore matches with missing chapters - (**Migration**) Add "local source" as a possible destination source +- (**Migration**) Add option to abort entries that are searching or are waiting to get migrated ### Changed diff --git a/src/features/migration/Migration.constants.ts b/src/features/migration/Migration.constants.ts index 89657532..e2d545cd 100644 --- a/src/features/migration/Migration.constants.ts +++ b/src/features/migration/Migration.constants.ts @@ -8,8 +8,8 @@ import type { MessageDescriptor } from '@lingui/core'; import { msg } from '@lingui/core/macro'; -import { MigrationEntryStatus, MigrationPhase, SortBy, SortOrder } from '@/features/migration/Migration.types.ts'; import type { MigrationState } from '@/features/migration/Migration.types.ts'; +import { MigrationEntryStatus, MigrationPhase, SortBy, SortOrder } from '@/features/migration/Migration.types.ts'; export const sortByToTranslation: Record = { [SortBy.SOURCE_NAME]: msg`By source name`, @@ -48,21 +48,24 @@ export const DEFAULT_MIGRATION_STATE: MigrationState = { }; export const ENTRY_STATUS_TRANSLATION: Record = { - [MigrationEntryStatus.PENDING]: msg`Pending…`, + [MigrationEntryStatus.SEARCH_PENDING]: msg`Pending…`, [MigrationEntryStatus.SEARCHING]: msg`Searching…`, [MigrationEntryStatus.SEARCH_COMPLETE]: msg`Match found`, [MigrationEntryStatus.SEARCH_FAILED]: msg`Search failed`, [MigrationEntryStatus.NO_MATCH]: msg`No match found`, [MigrationEntryStatus.OUTDATED]: msg`Only outdated matches found`, + [MigrationEntryStatus.MIGRATION_PENDING]: msg`Pending…`, [MigrationEntryStatus.MIGRATING]: msg`Migrating…`, [MigrationEntryStatus.MIGRATION_COMPLETE]: msg`Successfully migrated`, [MigrationEntryStatus.MIGRATION_FAILED]: msg`Migration failed`, [MigrationEntryStatus.EXCLUDED]: msg`Excluded`, + [MigrationEntryStatus.ABORTED]: msg`Aborted`, }; export const MIGRATE_SEARCH_ENTRY_GROUPS = [ MigrationEntryStatus.SEARCHING, MigrationEntryStatus.SEARCH_FAILED, + MigrationEntryStatus.ABORTED, MigrationEntryStatus.NO_MATCH, MigrationEntryStatus.OUTDATED, MigrationEntryStatus.SEARCH_COMPLETE, @@ -74,6 +77,7 @@ export const MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record< > = { [MigrationEntryStatus.SEARCHING]: true, [MigrationEntryStatus.SEARCH_FAILED]: false, + [MigrationEntryStatus.ABORTED]: false, [MigrationEntryStatus.NO_MATCH]: false, [MigrationEntryStatus.OUTDATED]: false, [MigrationEntryStatus.SEARCH_COMPLETE]: false, @@ -82,6 +86,7 @@ export const MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record< export const MIGRATE_EXECUTE_ENTRY_GROUPS = [ MigrationEntryStatus.MIGRATING, MigrationEntryStatus.MIGRATION_FAILED, + MigrationEntryStatus.ABORTED, MigrationEntryStatus.NO_MATCH, MigrationEntryStatus.OUTDATED, MigrationEntryStatus.EXCLUDED, @@ -94,6 +99,7 @@ export const MIGRATE_EXECUTE_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record< > = { [MigrationEntryStatus.MIGRATING]: true, [MigrationEntryStatus.MIGRATION_FAILED]: false, + [MigrationEntryStatus.ABORTED]: false, [MigrationEntryStatus.NO_MATCH]: false, [MigrationEntryStatus.OUTDATED]: false, [MigrationEntryStatus.EXCLUDED]: false, diff --git a/src/features/migration/Migration.types.ts b/src/features/migration/Migration.types.ts index 9103003f..53d70981 100644 --- a/src/features/migration/Migration.types.ts +++ b/src/features/migration/Migration.types.ts @@ -87,16 +87,18 @@ export enum MigrationPhase { } export enum MigrationEntryStatus { - PENDING = 'pending', + SEARCH_PENDING = 'search_pending', SEARCHING = 'searching', SEARCH_COMPLETE = 'search_complete', SEARCH_FAILED = 'search_failed', OUTDATED = 'outdated', NO_MATCH = 'no_match', + MIGRATION_PENDING = 'migration_pending', MIGRATING = 'migrating', MIGRATION_COMPLETE = 'migration_complete', MIGRATION_FAILED = 'migration_failed', EXCLUDED = 'excluded', + ABORTED = 'aborted', } export interface MigrationMatch diff --git a/src/features/migration/MigrationEntries.ts b/src/features/migration/MigrationEntries.ts index 14038189..fddfc937 100644 --- a/src/features/migration/MigrationEntries.ts +++ b/src/features/migration/MigrationEntries.ts @@ -10,16 +10,35 @@ import type { MigratableEntry, TMigrationEntry } from '@/features/migration/Migr import { MigrationEntryStatus } from '@/features/migration/Migration.types.ts'; export class MigrationEntries { + public static isAbortable(entry: TMigrationEntry): boolean { + return ( + MigrationEntries.isSearching(entry) || + MigrationEntries.hasStatus(entry, MigrationEntryStatus.MIGRATION_PENDING) + ); + } + public static isSearching(entry: TMigrationEntry): boolean { - return [MigrationEntryStatus.PENDING, MigrationEntryStatus.SEARCHING].includes(entry.status); + return MigrationEntries.hasStatus(entry, MigrationEntryStatus.SEARCH_PENDING, MigrationEntryStatus.SEARCHING); + } + + public static isMigrating(entry: TMigrationEntry): boolean { + return MigrationEntries.hasStatus( + entry, + MigrationEntryStatus.MIGRATION_PENDING, + MigrationEntryStatus.MIGRATING, + ); } public static getExcluded(entries: TMigrationEntry[]): TMigrationEntry[] { return entries.filter((entry) => entry.isExcluded); } + public static hasStatus(entry: TMigrationEntry, ...statuses: MigrationEntryStatus[]): boolean { + return statuses.includes(entry.status); + } + public static getHaveStatus(entries: TMigrationEntry[], ...statuses: MigrationEntryStatus[]): TMigrationEntry[] { - return entries.filter((entry) => statuses.includes(entry.status)); + return entries.filter((entry) => MigrationEntries.hasStatus(entry, ...statuses)); } public static getHaveStatusSorted( @@ -55,6 +74,7 @@ export class MigrationEntries { MigrationEntries.hasStatus( entry, MigrationEntryStatus.SEARCH_COMPLETE, + MigrationEntryStatus.MIGRATION_PENDING, MigrationEntryStatus.MIGRATING, ) && !entry.isExcluded && diff --git a/src/features/migration/MigrationManager.ts b/src/features/migration/MigrationManager.ts index cee81db2..5c9f25e9 100644 --- a/src/features/migration/MigrationManager.ts +++ b/src/features/migration/MigrationManager.ts @@ -100,7 +100,7 @@ export class MigrationManager { private static mangaProcessQueue = pLimit(MAX_MANGAS_IN_PARALLEL); private static parallelSourcesQueue: LimitFunction | undefined; private static queueBySource = new Map(); - private static searchAbortControllerByManga = new Map(); + private static abortControllerByManga = new Map(); private static abortAndResetAbortController(reason: unknown): void { MigrationManager.abortController?.abort(reason); @@ -344,7 +344,7 @@ export class MigrationManager { mangaThumbnailUrl: manga.thumbnailUrl, sourceId: manga.sourceId, sourceTitle: manga.source?.displayName, - status: MigrationEntryStatus.PENDING, + status: MigrationEntryStatus.SEARCH_PENDING, searchMatches: [], manualMatches: [], selectedMatchMangaId: null, @@ -384,10 +384,16 @@ export class MigrationManager { private static async search(entries: TMigrationEntry[], options: MigrationBulkSearchSettings): Promise { const { signal } = MigrationManager.abortAndCreateAbortController('search'); + MigrationManager.updateState((draft) => + entries.forEach((entry) => { + draft.entries[entry.mangaId].status = MigrationEntryStatus.SEARCH_PENDING; + }), + ); + const searchPromises = entries.map((entry) => MigrationManager.mangaProcessQueue(async () => { if (signal.aborted) { - return; + throw new Error(signal.reason); } await MigrationManager.searchForManga(entry.mangaId, entry.mangaTitle, signal, options); @@ -447,13 +453,19 @@ export class MigrationManager { ): Promise { const { signal } = MigrationManager.abortAndCreateAbortController('migrate'); + MigrationManager.updateState((draft) => { + entries.forEach((entry) => { + draft.entries[entry.mangaId].status = MigrationEntryStatus.MIGRATION_PENDING; + }); + }); + const entriesBySource = Object.groupBy(entries, (entry) => entry.selectedMatchSourceId); const migrationPromises = Object.values(entriesBySource).map((sourceEntries = []) => MigrationManager.mangaProcessQueue(async () => { for (const entry of sourceEntries) { if (signal.aborted) { - return; + throw new Error(signal.reason); } // oxlint-disable-next-line no-await-in-loop @@ -530,7 +542,7 @@ export class MigrationManager { MigrationManager.updateState((draft) => { migratableEntries.forEach((entry) => { - draft.entries[entry.mangaId].status = MigrationEntryStatus.SEARCH_COMPLETE; + draft.entries[entry.mangaId].status = MigrationEntryStatus.MIGRATION_PENDING; }); }); @@ -543,13 +555,13 @@ export class MigrationManager { const pendingEntries = MigrationEntries.getHaveStatus( Object.values(entries), - MigrationEntryStatus.PENDING, + MigrationEntryStatus.SEARCH_PENDING, MigrationEntryStatus.SEARCHING, ); MigrationManager.updateState((draft) => { pendingEntries.forEach((entry) => { - draft.entries[entry.mangaId].status = MigrationEntryStatus.PENDING; + draft.entries[entry.mangaId].status = MigrationEntryStatus.SEARCH_PENDING; }); }); @@ -581,6 +593,28 @@ export class MigrationManager { }); } + static abortEntry(mangaId: MangaIdInfo['id']): void { + const { entries, phase } = MigrationManager.getState(); + const entry = entries[mangaId]; + + assertIsDefined(entry); + + MigrationManager.abortControllerByManga.get(mangaId)?.abort('User aborted'); + MigrationManager.updateState((draft) => { + const draftEntry = draft.entries[mangaId]; + + assertIsDefined(draftEntry); + + draftEntry.status = MigrationEntryStatus.ABORTED; + + if (phase === MigrationPhase.SEARCHING) { + draft.searchProgress.completed += 1; + } else { + draft.migrationProgress.completed += 1; + } + }); + } + static selectMatch( mangaId: MangaIdInfo['id'], targetMangaId: MangaIdInfo['id'], @@ -628,7 +662,7 @@ export class MigrationManager { if (isSearching) { draft.searchProgress.completed += 1; draft.searchProgress.success += 1; - MigrationManager.searchAbortControllerByManga.get(mangaId)?.abort('Manual match selected'); + MigrationManager.abortControllerByManga.get(mangaId)?.abort('Manual match selected'); } } }); @@ -792,6 +826,7 @@ export class MigrationManager { () => requestManager.refreshManga(match.id, { awaitRefetchQueries: true, + context: { fetchOptions: { signal } }, }).response, ); @@ -836,13 +871,13 @@ export class MigrationManager { const searchController = new AbortController(); const signal = AbortSignal.any([mainSignal, searchController.signal]); - MigrationManager.searchAbortControllerByManga.get(mangaId)?.abort('search'); - MigrationManager.searchAbortControllerByManga.set(mangaId, searchController); - - if (!entry) { + if (!entry || MigrationEntries.hasStatus(entry, MigrationEntryStatus.ABORTED)) { return; } + MigrationManager.abortControllerByManga.get(mangaId)?.abort('search'); + MigrationManager.abortControllerByManga.set(mangaId, searchController); + MigrationManager.updateState((draft) => { draft.entries[mangaId].status = MigrationEntryStatus.SEARCHING; }); @@ -853,7 +888,7 @@ export class MigrationManager { .map((destSourceId) => MigrationManager.getParallelSourceQueue()(async () => { if (signal.aborted) { - return null; + throw new Error(signal.reason); } if ( @@ -1017,35 +1052,48 @@ export class MigrationManager { MigrationManager.updateState((draft) => { const draftEntry = draft.entries[mangaId]; - draftEntry.status = MigrationEntryStatus.SEARCH_FAILED; - draftEntry.error = getErrorMessage(error); - draft.searchProgress.completed += 1; - draft.searchProgress.failed += 1; + if (!MigrationEntries.hasStatus(draftEntry, MigrationEntryStatus.ABORTED)) { + draftEntry.status = MigrationEntryStatus.SEARCH_FAILED; + draftEntry.error = getErrorMessage(error); + draft.searchProgress.completed += 1; + draft.searchProgress.failed += 1; + } }); } finally { - MigrationManager.searchAbortControllerByManga.delete(mangaId); + MigrationManager.abortControllerByManga.delete(mangaId); } } private static async migrateSingleEntry( mangaId: MangaIdInfo['id'], options: Omit, - signal: AbortSignal, + mainSignal: AbortSignal, ): Promise { const state = MigrationManager.getState(); const entry = state.entries[mangaId]; - if (!entry || !entry.selectedMatchSourceId || entry.selectedMatchMangaId == null) { + const migrateController = new AbortController(); + const signal = AbortSignal.any([mainSignal, migrateController.signal]); + + if ( + !entry || + !entry.selectedMatchSourceId || + entry.selectedMatchMangaId == null || + MigrationEntries.hasStatus(entry, MigrationEntryStatus.ABORTED) + ) { return; } + MigrationManager.abortControllerByManga.get(mangaId)?.abort('migrate'); + MigrationManager.abortControllerByManga.set(mangaId, migrateController); + MigrationManager.updateState((draft) => { draft.entries[mangaId].status = MigrationEntryStatus.MIGRATING; }); try { if (signal.aborted) { - return; + throw new Error(signal.reason); } await MigrationManager.getParallelSourceQueue()(() => { @@ -1053,7 +1101,7 @@ export class MigrationManager { return MigrationManager.getOrCreateSourceQueue(entry.selectedMatchSourceId)(async () => { if (signal.aborted) { - return; + throw new Error(signal.reason); } assertIsDefined(entry.selectedMatchMangaId); @@ -1063,21 +1111,33 @@ export class MigrationManager { }); MigrationManager.updateState((draft) => { + if (!MigrationEntries.isMigrating(entry)) { + return; + } + draft.entries[mangaId].status = MigrationEntryStatus.MIGRATION_COMPLETE; draft.migrationProgress.success += 1; draft.migrationProgress.completed += 1; }); } catch (error) { - if (signal.aborted) { + if (mainSignal.aborted) { return; } MigrationManager.updateState((draft) => { - draft.entries[mangaId].status = MigrationEntryStatus.MIGRATION_FAILED; - draft.entries[mangaId].error = error instanceof Error ? error.message : String(error); - draft.migrationProgress.failed += 1; - draft.migrationProgress.completed += 1; + const draftEntry = draft.entries[mangaId]; + + assertIsDefined(draftEntry); + + if (!MigrationEntries.hasStatus(draftEntry, MigrationEntryStatus.ABORTED)) { + draftEntry.status = MigrationEntryStatus.MIGRATION_FAILED; + draftEntry.error = getErrorMessage(error); + draft.migrationProgress.failed += 1; + draft.migrationProgress.completed += 1; + } }); + } finally { + MigrationManager.abortControllerByManga.delete(mangaId); } } diff --git a/src/features/migration/components/MIgrationEntryGroup.tsx b/src/features/migration/components/MIgrationEntryGroup.tsx index 0bd4857e..665f120a 100644 --- a/src/features/migration/components/MIgrationEntryGroup.tsx +++ b/src/features/migration/components/MIgrationEntryGroup.tsx @@ -24,12 +24,14 @@ export const MigrationEntryGroup = memo( entries, color, isMigrating = false, + isAborted = false, }: { status: MigrationEntryStatus; title: string; entries: TMigrationEntry[]; color: ButtonProps['color']; isMigrating?: boolean; + isAborted?: boolean; }) => { const isExpanded = MigrationManager.useGroupExpandState(status); @@ -60,7 +62,12 @@ export const MigrationEntryGroup = memo( {entries.map((entry) => ( - + ))} diff --git a/src/features/migration/components/migration-entry/MigrationEntry.tsx b/src/features/migration/components/migration-entry/MigrationEntry.tsx index ac808967..1871ed0f 100644 --- a/src/features/migration/components/migration-entry/MigrationEntry.tsx +++ b/src/features/migration/components/migration-entry/MigrationEntry.tsx @@ -25,6 +25,7 @@ import Collapse from '@mui/material/Collapse'; import Divider from '@mui/material/Divider'; import { MigrationEntryStatusIndicator } from '@/features/migration/components/migration-entry/MigrationEntryStatusIndicator.tsx'; import Box from '@mui/material/Box'; +import { MigrationEntries } from '@/features/migration/MigrationEntries.ts'; const MigrationEntryMobile = memo( ({ @@ -35,6 +36,7 @@ const MigrationEntryMobile = memo( isExpanded, setIsExpanded, isMigrating, + isAborted, }: { entry: TMigrationEntry; destinationEntry: MigrationMatch | undefined; @@ -42,6 +44,7 @@ const MigrationEntryMobile = memo( isExpanded: boolean; setIsExpanded: (expanded: boolean) => void; isMigrating: boolean; + isAborted: boolean; }) => { const { t } = useLingui(); @@ -89,17 +92,17 @@ const MigrationEntryMobile = memo( - {!isMigrating && ( - - )} + ); }, @@ -114,6 +117,7 @@ export const MigrationEntryDesktop = memo( isExpanded, setIsExpanded, isMigrating, + isAborted, }: { entry: TMigrationEntry; destinationEntry: MigrationMatch | undefined; @@ -121,6 +125,7 @@ export const MigrationEntryDesktop = memo( isExpanded: boolean; setIsExpanded: (expanded: boolean) => void; isMigrating: boolean; + isAborted: boolean; }) => { const { t } = useLingui(); @@ -151,17 +156,17 @@ export const MigrationEntryDesktop = memo( /> - {!isMigrating && ( - - )} + @@ -190,7 +195,15 @@ export const MigrationEntryDesktop = memo( ); export const MigrationEntry = memo( - ({ entry: propEntry, isMigrating }: { entry: TMigrationEntry; isMigrating: boolean }) => { + ({ + entry: propEntry, + isMigrating, + isAborted, + }: { + entry: TMigrationEntry; + isMigrating: boolean; + isAborted: boolean; + }) => { const isTabletWidth = MediaQuery.useIsTabletWidth(); const entry = useMemo(() => MigrationManager.getUpToDateMigrationEntry(propEntry), [propEntry]); @@ -255,6 +268,7 @@ export const MigrationEntry = memo( } otherSearchMatches={otherMatches} isMigrating={isMigrating} + isAborted={isAborted} /> ); diff --git a/src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx b/src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx index 28575bfc..f066b5a1 100644 --- a/src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx +++ b/src/features/migration/components/migration-entry/MigrationEntrySearchExcludeActions.tsx @@ -22,6 +22,7 @@ import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ButtonGroup from '@mui/material/ButtonGroup'; import RemoveCircleOutlineOutlinedIcon from '@mui/icons-material/RemoveCircleOutlineOutlined'; import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; +import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined'; export const MigrationEntrySearchExcludeActions = ({ hasSelectedMatch, @@ -31,6 +32,8 @@ export const MigrationEntrySearchExcludeActions = ({ isExcluded, mangaId, mangaTitle, + isAbortable, + isMigrating, }: { hasSelectedMatch: boolean; otherResultsCount: number; @@ -39,6 +42,8 @@ export const MigrationEntrySearchExcludeActions = ({ isExcluded: boolean; mangaId: MangaIdInfo['id']; mangaTitle: string; + isAbortable: boolean; + isMigrating: boolean; }) => { const { t } = useLingui(); const isTabletWidth = MediaQuery.useIsTabletWidth(); @@ -46,7 +51,7 @@ export const MigrationEntrySearchExcludeActions = ({ if (isTabletWidth) { return ( - {!!otherResultsCount && ( + {!isMigrating && !!otherResultsCount && ( )} - {!isExpanded && ( + {!isMigrating && !isExpanded && ( )} - {hasSelectedMatch && ( + {!isMigrating && hasSelectedMatch && ( )} + {isAbortable && ( + + MigrationManager.abortEntry(mangaId)} + > + + + + )} ); } return ( - {hasSelectedMatch && ( + {isAbortable && ( + + MigrationManager.abortEntry(mangaId)}> + + + + )} + {!isMigrating && hasSelectedMatch && ( @@ -105,15 +129,17 @@ export const MigrationEntrySearchExcludeActions = ({ )} - - { - MigrationManager.openManualSearch(mangaId, mangaTitle); - }} - > - - - + {!isMigrating && ( + + { + MigrationManager.openManualSearch(mangaId, mangaTitle); + }} + > + + + + )} ); }; diff --git a/src/features/migration/screens/MigrationExecute.tsx b/src/features/migration/screens/MigrationExecute.tsx index 72740d23..4772695c 100644 --- a/src/features/migration/screens/MigrationExecute.tsx +++ b/src/features/migration/screens/MigrationExecute.tsx @@ -11,7 +11,7 @@ import { MigrationProgressBar } from '@/features/migration/components/MigrationP import { useLingui } from '@lingui/react/macro'; import { useAppTitleAndAction } from '@/features/navigation-bar/hooks/useAppTitleAndAction.ts'; import { MigrationManager } from '@/features/migration/MigrationManager.ts'; -import { MigrationEntryStatus } from '@/features/migration/Migration.types.ts'; +import { MigrationEntryStatus, MigrationPhase } from '@/features/migration/Migration.types.ts'; import { useMemo } from 'react'; import { DEFAULT_FULL_FAB_HEIGHT } from '@/base/components/buttons/StyledFab.tsx'; import { MigrationContinueButton } from '@/features/migration/components/MigrationContinueButton.tsx'; @@ -21,6 +21,8 @@ import { MigrationEntries } from '@/features/migration/MigrationEntries.ts'; export const MigrationExecute = () => { const { t } = useLingui(); + + const phase = MigrationManager.usePhase(); const entries = MigrationManager.useEntries(); const progress = MigrationManager.useMigrationProgress(); @@ -40,13 +42,15 @@ export const MigrationExecute = () => { [MigrationManager.isPhaseComplete()], ); + const isMigrating = phase === MigrationPhase.MIGRATING; + const entryList = useMemo(() => Object.values(entries), [entries]); const migratingEntries = useMemo( () => MigrationEntries.getActiveEntriesSorted( entryList, MigrationEntryStatus.MIGRATING, - MigrationEntryStatus.SEARCH_COMPLETE, + MigrationEntryStatus.MIGRATION_PENDING, MigrationEntryStatus.MIGRATING, ), [entryList], @@ -59,6 +63,10 @@ export const MigrationExecute = () => { () => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.MIGRATION_FAILED), [entryList], ); + const abortedEntries = useMemo( + () => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.ABORTED), + [entryList], + ); const excludedEntries = useMemo(() => MigrationEntries.getExcluded(entryList), [entryList]); const noMatchEntries = useMemo( () => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.NO_MATCH), @@ -87,7 +95,8 @@ export const MigrationExecute = () => { other: '# migrating entries', })} entries={migratingEntries} - isMigrating + isMigrating={isMigrating} + isAborted={MigrationManager.getState().isAborted} color="info" /> { other: '# failed entries', })} entries={failedEntries} - isMigrating + isMigrating={isMigrating} + isAborted={MigrationManager.getState().isAborted} color="error" /> + { })} entries={noMatchEntries} color="warning" - isMigrating + isMigrating={isMigrating} + isAborted={MigrationManager.getState().isAborted} /> { other: '# entries with only outdated matches', })} entries={outdatedEntries} + isAborted={MigrationManager.getState().isAborted} color="warning" /> { })} entries={excludedEntries} color="info" - isMigrating + isMigrating={isMigrating} + isAborted={MigrationManager.getState().isAborted} /> { })} entries={migratedEntries} color="success" - isMigrating + isMigrating={isMigrating} + isAborted={MigrationManager.getState().isAborted} /> diff --git a/src/features/migration/screens/MigrationSearch.tsx b/src/features/migration/screens/MigrationSearch.tsx index 2c0bb948..ff9fa9f6 100644 --- a/src/features/migration/screens/MigrationSearch.tsx +++ b/src/features/migration/screens/MigrationSearch.tsx @@ -37,7 +37,7 @@ export const MigrationSearch = () => { MigrationEntries.getActiveEntriesSorted( entryList, MigrationEntryStatus.SEARCHING, - MigrationEntryStatus.PENDING, + MigrationEntryStatus.SEARCH_PENDING, MigrationEntryStatus.SEARCHING, ), [entryList], @@ -54,6 +54,10 @@ export const MigrationSearch = () => { () => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.OUTDATED), [entryList], ); + const abortedEntries = useMemo( + () => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.ABORTED), + [entryList], + ); const matchedEntries = useMemo( () => MigrationEntries.getHaveStatusSorted(entryList, MigrationEntryStatus.SEARCH_COMPLETE), [entryList], @@ -108,6 +112,15 @@ export const MigrationSearch = () => { entries={outdatedEntries} color="warning" /> +