Skip to content

Commit

Permalink
implement SimpleFin batch sync (#3581)
Browse files Browse the repository at this point in the history
* initial

* remove incorrect automated imports

* fixes

* refactor to mark all transactions new

* clamp latestTransaction to current date

* refactor out temporary placeholder solution

* simplify bank syning logic

* stricter types

* note

* remove debug logging

* better logging

* error handling

* fix handling of SimpleFinBatchSync

* pass errors down

* fix

* another go!

* hopefully the last try...

* fix log

* Update packages/loot-core/src/server/accounts/sync.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* coderabbit: simplify promise construction

* Update packages/loot-core/src/client/actions/account.ts

Co-authored-by: Koen van Staveren <koenvanstaveren@hotmail.com>

* expand types

* month utils

* use aql over sql

* fix types

* fixes

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Koen van Staveren <koenvanstaveren@hotmail.com>
  • Loading branch information
3 people authored Nov 4, 2024
1 parent 83ceea4 commit 815f69a
Show file tree
Hide file tree
Showing 9 changed files with 512 additions and 186 deletions.
147 changes: 102 additions & 45 deletions packages/loot-core/src/client/actions/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,67 @@ export function linkAccountSimpleFin(
};
}

function handleSyncResponse(
accountId,
res,
dispatch,
resNewTransactions,
resMatchedTransactions,
resUpdatedAccounts,
) {
const { errors, newTransactions, matchedTransactions, updatedAccounts } = res;

// Mark the account as failed or succeeded (depending on sync output)
const [error] = errors;
if (error) {
// We only want to mark the account as having problem if it
// was a real syncing error.
if (error.type === 'SyncError') {
dispatch(markAccountFailed(accountId, error.category, error.code));
}
} else {
dispatch(markAccountSuccess(accountId));
}

// Dispatch errors (if any)
errors.forEach(error => {
if (error.type === 'SyncError') {
dispatch(
addNotification({
type: 'error',
message: error.message,
}),
);
} else {
dispatch(
addNotification({
type: 'error',
message: error.message,
internal: error.internal,
}),
);
}
});

resNewTransactions.push(...newTransactions);
resMatchedTransactions.push(...matchedTransactions);
resUpdatedAccounts.push(...updatedAccounts);

return newTransactions.length > 0 || matchedTransactions.length > 0;
}

export function syncAccounts(id?: string) {
return async (dispatch: Dispatch, getState: GetState) => {
// Disallow two parallel sync operations
if (getState().account.accountsSyncing.length > 0) {
return false;
}

const batchSync = !id;

// Build an array of IDs for accounts to sync.. if no `id` provided
// then we assume that all accounts should be synced
const accountIdsToSync = id
let accountIdsToSync = !batchSync
? [id]
: getState()
.queries.accounts.filter(
Expand All @@ -113,67 +164,73 @@ export function syncAccounts(id?: string) {

dispatch(setAccountsSyncing(accountIdsToSync));

const accountsData = await send('accounts-get');
const simpleFinAccounts = accountsData.filter(
a => a.account_sync_source === 'simpleFin',
);

let isSyncSuccess = false;
const newTransactions = [];
const matchedTransactions = [];
const updatedAccounts = [];

if (batchSync && simpleFinAccounts.length > 0) {
console.log('Using SimpleFin batch sync');

const res = await send('simplefin-batch-sync', {
ids: simpleFinAccounts.map(a => a.id),
});

for (const account of res) {
const success = handleSyncResponse(
account.accountId,
account.res,
dispatch,
newTransactions,
matchedTransactions,
updatedAccounts,
);
if (success) isSyncSuccess = true;
}

accountIdsToSync = accountIdsToSync.filter(
id => !simpleFinAccounts.find(sfa => sfa.id === id),
);
}

// Loop through the accounts and perform sync operation.. one by one
for (let idx = 0; idx < accountIdsToSync.length; idx++) {
const accountId = accountIdsToSync[idx];

// Perform sync operation
const { errors, newTransactions, matchedTransactions, updatedAccounts } =
await send('accounts-bank-sync', {
id: accountId,
});

// Mark the account as failed or succeeded (depending on sync output)
const [error] = errors;
if (error) {
// We only want to mark the account as having problem if it
// was a real syncing error.
if (error.type === 'SyncError') {
dispatch(markAccountFailed(accountId, error.category, error.code));
}
} else {
dispatch(markAccountSuccess(accountId));
}

// Dispatch errors (if any)
errors.forEach(error => {
if (error.type === 'SyncError') {
dispatch(
addNotification({
type: 'error',
message: error.message,
}),
);
} else {
dispatch(
addNotification({
type: 'error',
message: error.message,
internal: error.internal,
}),
);
}
const res = await send('accounts-bank-sync', {
id: accountId,
});

// Set new transactions
dispatch({
type: constants.SET_NEW_TRANSACTIONS,
const success = handleSyncResponse(
accountId,
res,
dispatch,
newTransactions,
matchedTransactions,
updatedAccounts,
});
);

if (success) isSyncSuccess = true;

// Dispatch the ids for the accounts that are yet to be synced
dispatch(setAccountsSyncing(accountIdsToSync.slice(idx + 1)));

if (newTransactions.length > 0 || matchedTransactions.length > 0) {
isSyncSuccess = true;
}
}

// Rest the sync state back to empty (fallback in case something breaks
// Set new transactions
dispatch({
type: constants.SET_NEW_TRANSACTIONS,
newTransactions,
matchedTransactions,
updatedAccounts,
});

// Reset the sync state back to empty (fallback in case something breaks
// in the logic above)
dispatch(setAccountsSyncing([]));
return isSyncSuccess;
Expand Down
Loading

0 comments on commit 815f69a

Please sign in to comment.