diff --git a/packages/desktop-client/e2e/accounts.test.ts b/packages/desktop-client/e2e/accounts.test.ts index 16dd2591f4..18c6c9b743 100644 --- a/packages/desktop-client/e2e/accounts.test.ts +++ b/packages/desktop-client/e2e/accounts.test.ts @@ -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/, diff --git a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.tsx b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.tsx index 8c2582b944..615d9100f7 100644 --- a/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.tsx +++ b/packages/desktop-client/src/components/modals/ImportTransactionsModal/ImportTransactionsModal.tsx @@ -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, - }), - ); }} > Use Memo as a fallback for empty Payees @@ -973,16 +918,6 @@ export function ImportTransactionsModal({ checked={importNotes} onChange={() => { setImportNotes(!importNotes); - parse( - filename, - getParseOptions(filetype, { - delimiter, - hasHeaderRow, - skipLines, - fallbackMissingPayeeToMemo, - importNotes: !importNotes, - }), - ); }} > Import notes from file @@ -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, - }), - ); }} > File has header row diff --git a/upcoming-release-notes/5980.md b/upcoming-release-notes/5980.md new file mode 100644 index 0000000000..b8b3da01e0 --- /dev/null +++ b/upcoming-release-notes/5980.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [matt-fidd] +--- + +Fix slow performance in import csv modal