From 9045e5c2c50bbbc20e9daf2b6142e3957d5effd2 Mon Sep 17 00:00:00 2001 From: idpaterson Date: Thu, 14 Dec 2023 07:58:46 -0500 Subject: [PATCH] Get trend state from React not localStorage --- src/shared/lib/__tests__/accounts.test.ts | 27 -------- src/shared/lib/accounts.ts | 28 +-------- src/shared/lib/trends.ts | 76 +++++++++++++++++++---- 3 files changed, 67 insertions(+), 64 deletions(-) diff --git a/src/shared/lib/__tests__/accounts.test.ts b/src/shared/lib/__tests__/accounts.test.ts index 046edc8..c570fb8 100644 --- a/src/shared/lib/__tests__/accounts.test.ts +++ b/src/shared/lib/__tests__/accounts.test.ts @@ -7,7 +7,6 @@ import { fetchDailyBalancesForAllAccounts, fetchMonthlyBalances, fetchNetWorthBalances, - fetchTrendAccounts, formatBalancesAsCSV, getAccountTypeFilterForTrend, } from '../accounts'; @@ -47,32 +46,6 @@ describe('fetchAccounts', () => { }); }); -describe('fetchTrendAccounts', () => { - it('excludes deselected accounts', async () => { - const allDebtAccounts = await fetchTrendAccounts({ - trend: { - reportType: 'DEBTS_TIME', - deselectedAccountIds: [], - fixedFilter: 'CUSTOM', - fromDate: '2020-01-01', - toDate: '2020-01-01', - }, - overrideApiKey: TEST_MINT_API_KEY, - }); - const accounts = await fetchTrendAccounts({ - trend: { - reportType: 'DEBTS_TIME', - deselectedAccountIds: ['43237333_2630847'], // a debt account - fixedFilter: 'CUSTOM', - fromDate: '2020-01-01', - toDate: '2020-01-01', - }, - overrideApiKey: TEST_MINT_API_KEY, - }); - expect(accounts.length).toEqual(allDebtAccounts.length - 1); - }); -}); - describe('formatBalancesAsCSV', () => { it('includes account name if provied', () => { const result = formatBalancesAsCSV({ diff --git a/src/shared/lib/accounts.ts b/src/shared/lib/accounts.ts index 272076e..7896167 100644 --- a/src/shared/lib/accounts.ts +++ b/src/shared/lib/accounts.ts @@ -56,10 +56,10 @@ type TrendsResponse = { /** State of user selections on the Mint Trends page */ export type TrendState = { + /** Selected accounts */ + accountIds?: string[]; /** Use with {@link deselectedAccountIds } to figure out which accounts to include in the trend */ reportType: ReportType; - /** All accounts eligible for the {@link reportType} that are NOT selected */ - deselectedAccountIds?: string[]; /** Semantic representation of the {@link fromDate} {@link toDate} range */ fixedFilter: FixedDateFilter; /** ISO start date */ @@ -385,8 +385,7 @@ export const fetchDailyBalancesForTrend = async ({ onProgress?: TrendBalanceHistoryProgressCallback; overrideApiKey?: string; }) => { - const accounts = await withRetry(() => fetchTrendAccounts({ trend, overrideApiKey })); - const accountId = accounts.map(({ id }) => id); + const accountId = trend.accountIds; const { reportType, fromDate, toDate, fixedFilter } = trend; let interval: Interval; // ALL_TIME may report a fromDate that is inaccurate by several years (e.g. 2007 when the trend @@ -502,27 +501,6 @@ export const fetchAccounts = async ({ return accounts; }; -/** - * Make sense of the {@link TrendState} by determining which accounts are selected. - */ -export const fetchTrendAccounts = async ({ - trend, - ...options -}: { - trend: TrendState; -} & FetchAccountsOptions) => { - // Mint knows best which accounts are eligible for the trend if nothing is selected - if (!trend.deselectedAccountIds?.length) { - return []; - } - const allAccounts = await fetchAccounts({ offset: 0, ...options }); - const accountTypeFilter = getAccountTypeFilterForTrend(trend); - - return allAccounts.filter( - ({ id, type }) => accountTypeFilter(type) && !trend.deselectedAccountIds?.includes(id), - ); -}; - /** * Merges paired API response into a single amount/inverseAmount entry. * diff --git a/src/shared/lib/trends.ts b/src/shared/lib/trends.ts index 4f6c1a4..1d98925 100644 --- a/src/shared/lib/trends.ts +++ b/src/shared/lib/trends.ts @@ -1,7 +1,35 @@ -import { TrendType, TrendState, ReportType } from '../../shared/lib/accounts'; +import { TrendState, ReportType, FixedDateFilter } from '../../shared/lib/accounts'; + +type AccountFilterReactProps = { + children: { + props: { + /** Selected account IDs and categories */ + value: string[]; + }; + }; +}; + +type DatePickerReactProps = { + props: { + children: [ + unknown, + { + props: { + /** The ISO date string */ + value: string; + }; + }, + unknown, + ]; + }; +}; + +type TimeFilterReactProps = { + children: [DatePickerReactProps, DatePickerReactProps]; +}; /** - * State for the most recent trend according to localStorage, does not need to be visible + * State for the current visible trend. * * This function must be executed in the context of the Mint tab and therefore must be * self-contained. @@ -10,19 +38,43 @@ export const getCurrentTrendState = () => { if ( // disable when not viewing the Trends page window.location.pathname.startsWith('/trends') && - // disable when filtered by category, tag, etc. because this filter is not in the trend state + // disable when filtered by category, tag, etc. because the extension does not support these !document.querySelector('[data-automation-id="filter-chip"]') ) { try { - const CURRENT_TREND_TYPE_LOCAL_STORAGE_KEY = 'trends-state'; - const currentTrendType = localStorage.getItem( - CURRENT_TREND_TYPE_LOCAL_STORAGE_KEY, - ) as TrendType; - const trendState = JSON.parse( - localStorage.getItem(`${CURRENT_TREND_TYPE_LOCAL_STORAGE_KEY}-${currentTrendType}`) || - 'null', - ) as TrendState; - + // Return React data backing HTML elements + const getReactProps = (selector: string) => { + const el = document.querySelector(selector); + return el?.[Object.keys(el).find((key) => key.startsWith('__reactProps'))] as Props; + }; + const accountState = getReactProps( + '[data-automation-id="filter-accounts"]', + ); + // For ALL_TIME charts this time range may be inaccurate (e.g. 2007 when the data only begins + // in 2021) but the extension is better equipped to choose the correct date with the API. + const timeFilterState = getReactProps( + '[data-automation-id="filter-time-custom"]', + ); + // ReportType can also be found in react props but it does not update reliably + const reportType = document.querySelector('.trends-sidebar-report-selected-list-item a') + ?.id as ReportType; + const fixedFilter = (document.getElementById('select-timeframe') as HTMLSelectElement) + .value as FixedDateFilter; + const accountIds = accountState.children.props.value.filter( + // Only numeric account IDs (ignore selected categories like AllAccounts and BankAccounts + // that will evaluate to NaN) + (id) => +id[0] === +id[0], + ) as string[]; + // This is a bit much, but can't seem to get the value reliably from child elements + const fromDate = timeFilterState.children[0].props.children[1].props.value; + const toDate = timeFilterState.children[1].props.children[1].props.value; + const trendState: TrendState = { + accountIds, + reportType, + fixedFilter, + fromDate, + toDate, + }; return trendState; } catch (e) { // ignore