fix slow performance in import csv modal (#5980)

This commit is contained in:
Matt Fiddaman
2025-10-22 13:43:56 +01:00
committed by GitHub
parent 002f74a8fa
commit dcc879294c
3 changed files with 24 additions and 105 deletions

View File

@@ -123,11 +123,14 @@ test.describe('Accounts', () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(join(__dirname, 'data/test.csv'));
if (screenshot) await expect(page).toMatchThemeScreenshots();
const importButton = accountPage.page.getByRole('button', {
name: /Import \d+ transactions/,
});
await importButton.waitFor({ state: 'visible' });
if (screenshot) await expect(page).toMatchThemeScreenshots();
await importButton.click();
await expect(importButton).not.toBeVisible();
@@ -146,12 +149,14 @@ test.describe('Accounts', () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(join(__dirname, 'data/test.csv'));
await expect(page).toMatchThemeScreenshots();
const importButton = accountPage.page.getByRole('button', {
name: /Import \d+ transactions/,
});
await importButton.waitFor({ state: 'visible' });
await expect(page).toMatchThemeScreenshots();
await expect(importButton).toBeDisabled();
await expect(await importButton.innerText()).toMatch(
/Import 0 transactions/,

View File

@@ -381,7 +381,6 @@ export function ImportTransactionsModal({
return trans;
});
setLoadingState(null);
setError(null);
/// Do fine grained reporting between the old and new OFX importers.
@@ -391,13 +390,8 @@ export function ImportTransactionsModal({
message: errors[0].message || 'Internal error',
});
} else {
let flipAmount = false;
let fieldMappings = null;
let splitMode = false;
let parseDateFormat: string | null = null;
if (filetype === 'csv' || filetype === 'qif') {
flipAmount =
const flipAmount =
String(prefs[`flip-amount-${accountId}-${filetype}`]) === 'true';
setFlipAmount(flipAmount);
}
@@ -408,23 +402,22 @@ export function ImportTransactionsModal({
? JSON.parse(mappings)
: getInitialMappings(transactions);
fieldMappings = mappings;
// @ts-expect-error - mappings might not have outflow/inflow properties
setFieldMappings(mappings);
// Set initial split mode based on any saved mapping
// @ts-expect-error - mappings might not have outflow/inflow properties
splitMode = !!(mappings.outflow || mappings.inflow);
const splitMode = !!(mappings.outflow || mappings.inflow);
setSplitMode(splitMode);
parseDateFormat =
const parseDateFormat =
prefs[`parse-date-${accountId}-${filetype}`] ||
getInitialDateFormat(transactions, mappings);
setParseDateFormat(
isDateFormat(parseDateFormat) ? parseDateFormat : null,
);
} else if (filetype === 'qif') {
parseDateFormat =
const parseDateFormat =
prefs[`parse-date-${accountId}-${filetype}`] ||
getInitialDateFormat(transactions, { date: 'date' });
setParseDateFormat(
@@ -441,24 +434,12 @@ export function ImportTransactionsModal({
const reversedTransactions =
transactions.reverse() as ImportTransaction[];
setParsedTransactions(reversedTransactions);
const transactionPreview = await getImportPreview(
reversedTransactions,
filetype,
flipAmount,
fieldMappings,
splitMode,
isDateFormat(parseDateFormat) ? parseDateFormat : null,
inOutMode,
outValue,
multiplierAmount,
);
setTransactions(transactionPreview);
}
setLoadingState(null);
},
// We use some state variables from the component, but do not want to re-parse when they change
// eslint-disable-next-line react-hooks/exhaustive-deps
[accountId, getImportPreview, prefs],
[accountId, prefs],
);
function onMultiplierChange(e) {
@@ -723,17 +704,6 @@ export function ImportTransactionsModal({
}
const runImportPreview = useCallback(async () => {
// preserve user's selection choices before re-running preview
const selectionMap = new Map();
transactions.forEach(trans => {
if (!trans.isMatchedTransaction) {
selectionMap.set(trans.trx_id, {
selected: trans.selected,
selected_merge: trans.selected_merge,
});
}
});
// always start from the original parsed transactions, not the previewed ones to ensure rules run
const transactionPreview = await getImportPreview(
parsedTransactions,
@@ -746,23 +716,7 @@ export function ImportTransactionsModal({
outValue,
multiplierAmount,
);
// restore selections to the new preview results
const transactionPreviewWithSelections = transactionPreview.map(trans => {
if (!trans.isMatchedTransaction && selectionMap.has(trans.trx_id)) {
const saved = selectionMap.get(trans.trx_id);
return {
...trans,
selected: saved.selected,
selected_merge: saved.selected_merge,
};
}
return trans;
});
setTransactions(transactionPreviewWithSelections);
// intentionally exclude transactions from dependencies to avoid infinite rerenders
// eslint-disable-next-line react-hooks/exhaustive-deps
setTransactions(transactionPreview);
}, [
getImportPreview,
parsedTransactions,
@@ -781,9 +735,7 @@ export function ImportTransactionsModal({
return;
}
if (filetype === 'csv' || filetype === 'qif') {
runImportPreview();
}
runImportPreview();
// intentionally exclude runImportPreview from dependencies to avoid infinite rerenders
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
@@ -954,13 +906,6 @@ export function ImportTransactionsModal({
checked={fallbackMissingPayeeToMemo}
onChange={() => {
setFallbackMissingPayeeToMemo(state => !state);
parse(
filename,
getParseOptions('ofx', {
fallbackMissingPayeeToMemo: !fallbackMissingPayeeToMemo,
importNotes,
}),
);
}}
>
<Trans>Use Memo as a fallback for empty Payees</Trans>
@@ -973,16 +918,6 @@ export function ImportTransactionsModal({
checked={importNotes}
onChange={() => {
setImportNotes(!importNotes);
parse(
filename,
getParseOptions(filetype, {
delimiter,
hasHeaderRow,
skipLines,
fallbackMissingPayeeToMemo,
importNotes: !importNotes,
}),
);
}}
>
<Trans>Import notes from file</Trans>
@@ -1047,15 +982,6 @@ export function ImportTransactionsModal({
value={delimiter}
onChange={value => {
setDelimiter(value);
parse(
filename,
getParseOptions('csv', {
delimiter: value,
hasHeaderRow,
skipLines,
importNotes,
}),
);
}}
style={{ width: 50 }}
/>
@@ -1075,15 +1001,6 @@ export function ImportTransactionsModal({
min="0"
onChangeValue={value => {
setSkipLines(+value);
parse(
filename,
getParseOptions('csv', {
delimiter,
hasHeaderRow,
skipLines: +value,
importNotes,
}),
);
}}
style={{ width: 50 }}
/>
@@ -1093,15 +1010,6 @@ export function ImportTransactionsModal({
checked={hasHeaderRow}
onChange={() => {
setHasHeaderRow(!hasHeaderRow);
parse(
filename,
getParseOptions('csv', {
delimiter,
hasHeaderRow: !hasHeaderRow,
skipLines,
importNotes,
}),
);
}}
>
<Trans>File has header row</Trans>

View File

@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [matt-fidd]
---
Fix slow performance in import csv modal