Skip to content

Commit 07189e8

Browse files
kevincadorseferturan
authored andcommitted
feat(settings): add import action toggles
Co-authored-by: seferturan <seferturan85@gmail.com>
1 parent 05cdcfb commit 07189e8

6 files changed

Lines changed: 302 additions & 31 deletions

File tree

projects/client/src/lib/sections/settings/_internal/RawImport.svelte

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
type AmbiguousImportItem,
1919
DEFAULT_IMPORT_SOURCE,
2020
IMPORT_SOURCE_CONFIGS,
21+
type ImportAction,
22+
type ImportActionSelection,
2123
type ImportCounts,
2224
type ImportSource,
2325
type ImportSourceConfig,
2426
type ImportStatus,
2527
type UniversalImportItem,
2628
} from "../import/ImportTypes.ts";
29+
import { filterImportItemsByActionSelection } from "../import/filterImportItemsByActionSelection.ts";
2730
import { getParser } from "../import/parsers/getParser.ts";
2831
import { syncToTrakt } from "../import/syncToTrakt.ts";
2932
import type { SyncState } from "../sync/models/SyncState.ts";
@@ -40,6 +43,14 @@
4043
return DEFAULT_IMPORT_SOURCE;
4144
}
4245
46+
function defaultSelectedActions(): ImportActionSelection {
47+
return {
48+
history: true,
49+
watchlist: true,
50+
ratings: true,
51+
};
52+
}
53+
4354
const { importInProgress } = useImportInProgress();
4455
const { invalidate } = useInvalidator();
4556
const { record } = useAnalytics();
@@ -48,6 +59,7 @@
4859
4960
type ImportUIState = SyncState<ImportStatus> & {
5061
selectedSource: ImportSource;
62+
selectedActions: ImportActionSelection;
5163
parsedItems: ReadonlyArray<UniversalImportItem>;
5264
errorCount: number;
5365
unresolved: ReadonlyArray<UniversalImportItem>;
@@ -59,6 +71,7 @@
5971
6072
const state = $state<ImportUIState>({
6173
selectedSource: DEFAULT_IMPORT_SOURCE,
74+
selectedActions: defaultSelectedActions(),
6275
status: "idle",
6376
parsedItems: [],
6477
processedCount: 0,
@@ -76,6 +89,18 @@
7689
watchlist: state.parsedItems.filter((i) => i.action === "watchlist").length,
7790
ratings: state.parsedItems.filter((i) => i.action === "ratings").length,
7891
});
92+
const selectedItems = $derived(
93+
filterImportItemsByActionSelection({
94+
items: state.parsedItems,
95+
selectedActions: state.selectedActions,
96+
}),
97+
);
98+
const selectedCounts = $derived<ImportCounts>({
99+
history: state.selectedActions.history ? counts.history : 0,
100+
watchlist: state.selectedActions.watchlist ? counts.watchlist : 0,
101+
ratings: state.selectedActions.ratings ? counts.ratings : 0,
102+
});
103+
const selectedTotalCount = $derived(selectedItems.length);
79104
80105
const sourceConfig = $derived(IMPORT_SOURCE_CONFIGS[state.selectedSource]);
81106
const isGuideCollapsed = $derived(
@@ -94,6 +119,7 @@
94119
const parser = getParser(state.selectedSource);
95120
96121
state.parseError = null;
122+
state.selectedActions = defaultSelectedActions();
97123
state.status = "parsing";
98124
99125
try {
@@ -126,6 +152,11 @@
126152
}
127153
128154
async function startImport() {
155+
const itemsToImport = selectedItems;
156+
const countsToImport = selectedCounts;
157+
const totalToImport = selectedTotalCount;
158+
if (totalToImport === 0) return;
159+
129160
abortController = new AbortController();
130161
const startTime = Date.now();
131162
state.processedCount = 0;
@@ -136,7 +167,7 @@
136167
137168
try {
138169
const { errorCount, unresolved, ambiguous } = await syncToTrakt(
139-
state.parsedItems,
170+
itemsToImport,
140171
{
141172
signal: abortController.signal,
142173
onMatchProgress: (processed, total) => {
@@ -160,7 +191,7 @@
160191
);
161192
162193
const duration = Date.now() - startTime;
163-
const successCount = state.totalCount - errorCount - unresolved.length -
194+
const successCount = totalToImport - errorCount - unresolved.length -
164195
ambiguous.length;
165196
166197
state.errorCount = errorCount;
@@ -169,10 +200,10 @@
169200
170201
record(AnalyticsEvent.ImportCompleted, {
171202
source: state.selectedSource,
172-
totalItems: state.totalCount,
173-
historyCount: counts.history,
174-
watchlistCount: counts.watchlist,
175-
ratingsCount: counts.ratings,
203+
totalItems: totalToImport,
204+
historyCount: countsToImport.history,
205+
watchlistCount: countsToImport.watchlist,
206+
ratingsCount: countsToImport.ratings,
176207
successCount,
177208
failedCount: errorCount,
178209
unresolvedCount: unresolved.length,
@@ -222,6 +253,7 @@
222253
function reset() {
223254
abortController?.abort();
224255
state.status = "idle";
256+
state.selectedActions = defaultSelectedActions();
225257
state.parsedItems = [];
226258
state.processedCount = 0;
227259
state.errorCount = 0;
@@ -258,6 +290,13 @@
258290
return config.name;
259291
}
260292
};
293+
294+
function onActionChange(action: ImportAction, isIncluded: boolean) {
295+
state.selectedActions = {
296+
...state.selectedActions,
297+
[action]: isIncluded,
298+
};
299+
}
261300
</script>
262301

263302
{#snippet importRow()}
@@ -283,8 +322,9 @@
283322
{:else if state.status === "review"}
284323
<ImportSummary
285324
{counts}
286-
totalItems={state.totalCount}
325+
selectedActions={state.selectedActions}
287326
source={state.selectedSource}
327+
onactionchange={onActionChange}
288328
onstart={startImport}
289329
onreset={reset}
290330
/>
@@ -300,10 +340,10 @@
300340
{:else if state.status === "syncing"}
301341
<SyncProgress
302342
processedCount={state.processedCount}
303-
totalCount={state.totalCount}
343+
totalCount={selectedTotalCount}
304344
label={m.import_status_syncing({
305345
processed: state.processedCount,
306-
total: state.totalCount,
346+
total: selectedTotalCount,
307347
})}
308348
/>
309349
{:else if state.status === "complete"}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { fireEvent, render, screen } from '@testing-library/svelte';
2+
import { of } from 'rxjs';
3+
import { describe, expect, it, vi } from 'vitest';
4+
import ImportSummary from './ImportSummary.svelte';
5+
6+
vi.mock('$lib/features/auth/stores/useUser.ts', () => ({
7+
useUser: () => ({
8+
user: of({ isVip: true }),
9+
limits: of(null),
10+
}),
11+
}));
12+
13+
describe('ImportSummary', () => {
14+
const counts = {
15+
history: 2,
16+
watchlist: 3,
17+
ratings: 1,
18+
};
19+
20+
it('should show an enabled switch for every import action with items', () => {
21+
render(ImportSummary, {
22+
counts,
23+
selectedActions: {
24+
history: true,
25+
watchlist: true,
26+
ratings: true,
27+
},
28+
source: 'tvtime',
29+
onactionchange: vi.fn(),
30+
onstart: vi.fn(),
31+
onreset: vi.fn(),
32+
});
33+
34+
expect(screen.getByRole('switch', { name: '2 items for History' }))
35+
.toBeChecked();
36+
expect(screen.getByRole('switch', { name: '3 items for Watchlist' }))
37+
.toBeChecked();
38+
expect(screen.getByRole('switch', { name: '1 ratings' })).toBeChecked();
39+
});
40+
41+
it('should request skipping an import action when its switch is clicked', async () => {
42+
const onactionchange = vi.fn();
43+
44+
render(ImportSummary, {
45+
counts,
46+
selectedActions: {
47+
history: true,
48+
watchlist: true,
49+
ratings: true,
50+
},
51+
source: 'tvtime',
52+
onactionchange,
53+
onstart: vi.fn(),
54+
onreset: vi.fn(),
55+
});
56+
57+
await fireEvent.click(
58+
screen.getByRole('switch', { name: '3 items for Watchlist' }),
59+
);
60+
61+
expect(onactionchange).toHaveBeenCalledWith('watchlist', false);
62+
});
63+
64+
it('should disable start import when all import actions are skipped', () => {
65+
render(ImportSummary, {
66+
counts,
67+
selectedActions: {
68+
history: false,
69+
watchlist: false,
70+
ratings: false,
71+
},
72+
source: 'tvtime',
73+
onactionchange: vi.fn(),
74+
onstart: vi.fn(),
75+
onreset: vi.fn(),
76+
});
77+
78+
expect(
79+
screen.getByRole('button', {
80+
name: 'Start importing your data into Trakt.',
81+
}),
82+
).toBeDisabled();
83+
});
84+
});

0 commit comments

Comments
 (0)