From 1238ef4fbdfdb204c2a5ddb1a779bef32c73755d Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 21 Oct 2026 11:05:07 -0600 Subject: [PATCH 1/2] refactor: Remove Insights page and comparison beta gates --- src/CONST/index.ts | 2 - .../Navigation/AccountAvatarButton.tsx | 4 +- .../Navigation/NavigationTabBar/index.tsx | 30 ++++---------- .../SearchRouter/useNavigationSuggestions.tsx | 22 ++++------ .../index.native.ts | 9 +--- src/hooks/useSearchTypeMenuSections.ts | 8 ---- .../getTabWithoutBarItem.ts | 12 ------ .../useNativeTabNavigator.ts | 24 +---------- src/pages/Insights/InsightsDashboard.tsx | 9 +--- src/pages/Insights/InsightsPage.tsx | 4 +- .../controls/InsightsPageControls.tsx | 14 ++----- src/pages/Insights/insightsQueries.ts | 20 +++------ src/pages/Insights/useInsightsChartData.ts | 4 +- .../InsightsSectionContent.tsx | 16 +++----- src/pages/home/InsightsSection/index.tsx | 9 +--- .../InsightsSection/useHomeInsightConfigs.ts | 18 +------- tests/unit/SearchRouterNavigationTest.ts | 28 +++++++------ tests/unit/insightsQueriesTest.ts | 41 ++++++------------- 18 files changed, 69 insertions(+), 205 deletions(-) delete mode 100644 src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/getTabWithoutBarItem.ts diff --git a/src/CONST/index.ts b/src/CONST/index.ts index d39bbb758944..b3c726377b60 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1421,8 +1421,6 @@ const CONST = { ARCHIVE_POLICIES: 'archivePolicies', MERGE_ATS: 'mergeATSConnections', REPORT_MERGE: 'reportMerge', - INSIGHTS_PAGE: 'insightsPage', - INSIGHTS_COMPARE: 'insightsCompare', PAYMENT_HISTORY: 'paymentHistory', UNIFIED_CONNECTIONS: 'unifiedConnections', SPEND_FOOTER_SELECTORS: 'spendFooterSelectors', diff --git a/src/components/Navigation/AccountAvatarButton.tsx b/src/components/Navigation/AccountAvatarButton.tsx index 601ab69ee5fe..1abdd31f5667 100644 --- a/src/components/Navigation/AccountAvatarButton.tsx +++ b/src/components/Navigation/AccountAvatarButton.tsx @@ -7,7 +7,6 @@ import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useAccountTabIndicatorStatus from '@hooks/useAccountTabIndicatorStatus'; import useIsAnonymousUser from '@hooks/useIsAnonymousUser'; import useLocalize from '@hooks/useLocalize'; -import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -28,10 +27,9 @@ import React from 'react'; * the Account tab is not part of the navigation tab bar. */ function AccountAvatarButton() { - const {isBetaEnabled} = usePermissions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - if (!shouldUseNarrowLayout || !isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE)) { + if (!shouldUseNarrowLayout) { return null; } diff --git a/src/components/Navigation/NavigationTabBar/index.tsx b/src/components/Navigation/NavigationTabBar/index.tsx index 0f988d857280..ea261a4dec39 100644 --- a/src/components/Navigation/NavigationTabBar/index.tsx +++ b/src/components/Navigation/NavigationTabBar/index.tsx @@ -8,7 +8,6 @@ import {PressableWithFeedback} from '@components/Pressable'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -45,8 +44,6 @@ function NavigationTabBar({selectedTab, shouldShowFloatingButtons = true}: Navig const styles = useThemeStyles(); const {translate} = useLocalize(); const [isDebugModeEnabled] = useOnyx(ONYXKEYS.IS_DEBUG_MODE_ENABLED); - const {isBetaEnabled} = usePermissions(); - const isInsightsTabVisible = isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE); const expensifyIcons = useMemoizedLazyExpensifyIcons(['ExpensifyAppIcon', 'Home']); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -123,12 +120,10 @@ function NavigationTabBar({selectedTab, shouldShowFloatingButtons = true}: Navig selectedTab={selectedTab} isWideLayout /> - {isInsightsTabVisible && ( - - )} + - {isInsightsTabVisible && ( - - )} + - {!isInsightsTabVisible && ( - - )} {shouldShowFloatingButtons && ( diff --git a/src/components/Search/SearchRouter/useNavigationSuggestions.tsx b/src/components/Search/SearchRouter/useNavigationSuggestions.tsx index c51a7ff18f56..d297917686b8 100644 --- a/src/components/Search/SearchRouter/useNavigationSuggestions.tsx +++ b/src/components/Search/SearchRouter/useNavigationSuggestions.tsx @@ -111,7 +111,6 @@ type BuildTopLevelNavigationItemsParams = { account: string; }; icons: TopLevelNavigationIcons; - isInsightsPageBetaEnabled: boolean; getSpendRoute: () => Route; getDestinationText: (destination: string) => string; }; @@ -219,7 +218,7 @@ type BuildAccountNavigationItemsParams = { }; // Tab buttons own stateful navigation behavior and do not expose reusable descriptors, so Search Router keeps deterministic destination actions here. -function buildTopLevelNavigationItems({labels, icons, isInsightsPageBetaEnabled, getSpendRoute, getDestinationText}: BuildTopLevelNavigationItemsParams): NavigationSuggestionSourceItem[] { +function buildTopLevelNavigationItems({labels, icons, getSpendRoute, getDestinationText}: BuildTopLevelNavigationItemsParams): NavigationSuggestionSourceItem[] { return [ { text: getDestinationText(labels.home), @@ -242,17 +241,13 @@ function buildTopLevelNavigationItems({labels, icons, isInsightsPageBetaEnabled, keyForList: 'topLevelSpend', matchTerms: [labels.spend], }, - ...(isInsightsPageBetaEnabled - ? [ - { - text: getDestinationText(labels.insights), - singleIcon: icons.PieChart, - action: () => Navigation.navigate(ROUTES.INSIGHTS.getRoute(CONST.INSIGHTS.DASHBOARD.SPEND)), - keyForList: 'topLevelInsights', - matchTerms: [labels.insights], - }, - ] - : []), + { + text: getDestinationText(labels.insights), + singleIcon: icons.PieChart, + action: () => Navigation.navigate(ROUTES.INSIGHTS.getRoute(CONST.INSIGHTS.DASHBOARD.SPEND)), + keyForList: 'topLevelInsights', + matchTerms: [labels.insights], + }, { text: getDestinationText(labels.workspaces), singleIcon: icons.Building, @@ -434,7 +429,6 @@ function useNavigationSuggestions(query: string, shouldWatchForApprovals = true) account: translate('initialSettingsPage.account'), }, icons, - isInsightsPageBetaEnabled: isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE), getSpendRoute: () => getSearchTabRoute(navigationRef.getRootState(), lastSearchParams, lastExpensesSearchQuerySelector(searchFilters)), getDestinationText: (destination) => getGoToText(translate, destination), }); diff --git a/src/hooks/useIsSettingsDrawnOverTabs/index.native.ts b/src/hooks/useIsSettingsDrawnOverTabs/index.native.ts index ba0c059aafa2..0fe741461ba1 100644 --- a/src/hooks/useIsSettingsDrawnOverTabs/index.native.ts +++ b/src/hooks/useIsSettingsDrawnOverTabs/index.native.ts @@ -1,19 +1,12 @@ -import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import getTabWithoutBarItem from '@libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/getTabWithoutBarItem'; - -import CONST from '@src/CONST'; -import NAVIGATORS from '@src/NAVIGATORS'; - /** * When the native tab bar has no Account item, the Account tab is drawn over the other tabs * as a full screen with the bar hidden, and it needs its own way back. */ function useIsSettingsDrawnOverTabs() { const {shouldUseNarrowLayout} = useResponsiveLayout(); - const {isBetaEnabled} = usePermissions(); - return shouldUseNarrowLayout && getTabWithoutBarItem(isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE)) === NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR; + return shouldUseNarrowLayout; } export default useIsSettingsDrawnOverTabs; diff --git a/src/hooks/useSearchTypeMenuSections.ts b/src/hooks/useSearchTypeMenuSections.ts index 40d372f51e96..f72d0d84f802 100644 --- a/src/hooks/useSearchTypeMenuSections.ts +++ b/src/hooks/useSearchTypeMenuSections.ts @@ -1,6 +1,5 @@ import {createTypeMenuSections, omitInsightsPageMenuItems} from '@libs/SearchUIUtils'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {isTrackIntentUserSelector} from '@src/selectors/Onboarding'; import type {Policy, Session} from '@src/types/onyx'; @@ -17,7 +16,6 @@ import useHasReportAwaitingApproval from './useHasReportAwaitingApproval'; import useMappedPolicies from './useMappedPolicies'; import useNetwork from './useNetwork'; import useOnyx from './useOnyx'; -import usePermissions from './usePermissions'; const policyMapper = (policy: OnyxEntry): OnyxEntry => policy && { @@ -145,12 +143,6 @@ const useSearchTypeMenuSections = (isScreenFocused = true) => { const useSearchTypeMenuSectionsForNavigation = (isScreenFocused = true) => { const typeMenuSections = useSearchTypeMenuSections(isScreenFocused); - const {isBetaEnabled} = usePermissions(); - - if (!isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE)) { - return typeMenuSections; - } - return omitInsightsPageMenuItems(typeMenuSections); }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/getTabWithoutBarItem.ts b/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/getTabWithoutBarItem.ts deleted file mode 100644 index 502b8506ea4e..000000000000 --- a/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/getTabWithoutBarItem.ts +++ /dev/null @@ -1,12 +0,0 @@ -import NAVIGATORS from '@src/NAVIGATORS'; -import SCREENS from '@src/SCREENS'; - -/** - * The native bar shows at most five of the six tabs. With the Insights beta, Insights takes the Account tab's place - * and Account moves to the top bar; without it, Insights has no item. - */ -function getTabWithoutBarItem(isInsightsBetaEnabled: boolean) { - return isInsightsBetaEnabled ? NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR : SCREENS.INSIGHTS; -} - -export default getTabWithoutBarItem; diff --git a/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/useNativeTabNavigator.ts b/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/useNativeTabNavigator.ts index 1cd5ff3dcc68..058c6392ac76 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/useNativeTabNavigator.ts +++ b/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/useNativeTabNavigator.ts @@ -4,19 +4,15 @@ import useAccountTabIndicatorStatus from '@hooks/useAccountTabIndicatorStatus'; import useInboxTabIndicatorStatus from '@hooks/useInboxTabIndicatorStatus'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useWorkspacesTabIndicatorStatus from '@hooks/useWorkspacesTabIndicatorStatus'; import {getPreservedNavigatorState, setPreservedNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState'; import isTabRouteAtRoot from '@libs/Navigation/helpers/isTabRouteAtRoot'; -import Navigation from '@libs/Navigation/Navigation'; import type {TabNavigatorParamList} from '@libs/Navigation/types'; -import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type {NavigationAction, NavigationState, PartialState, Router, TabNavigationState} from '@react-navigation/native'; @@ -26,7 +22,6 @@ import {isAnonymousSessionSelector} from '@selectors/Session'; import {useEffect} from 'react'; import getActiveTabRoute from './getActiveTabRoute'; -import getTabWithoutBarItem from './getTabWithoutBarItem'; import {isInboxTabAtChatList, isNativeTabSelectionEnabled} from './tabScreenListeners'; import useIsWorkspacesTabRestored from './useIsWorkspacesTabRestored'; @@ -45,11 +40,9 @@ function isRealizedNavigationState(state: NavigationState | PartialState { - if (!isInsightsTabFocusedWithoutBeta) { - return; - } - Navigation.navigate(ROUTES.HOME, {forceReplace: true}); - }, [isInsightsTabFocusedWithoutBeta]); - // The slicing optimization in useCustomRootStackNavigatorState can unmount and later remount // this TAB_NAVIGATOR. Without restoration it would default to index 0. We restore the saved // state by overriding the bottom-tab router's getInitialState, the same pattern SplitRouter @@ -121,11 +103,9 @@ function useNativeTabNavigator() { }, }); - const isAccountAvatarShown = shouldUseNarrowLayout && tabWithoutBarItem !== NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR; - const isTabSelectionEnabled = (routeName: string) => isNativeTabSelectionEnabled(routeName, {isAnonymousUser, isInboxAtChatList, isWorkspacesTabRestored}); - return {shouldShowNativeTabBar, isAccountAvatarShown, dotColors, tabLabels, tabWithoutBarItem, tabRouterOverride, isTabSelectionEnabled}; + return {shouldShowNativeTabBar, isAccountAvatarShown: false, dotColors, tabLabels, tabWithoutBarItem, tabRouterOverride, isTabSelectionEnabled}; } export default useNativeTabNavigator; diff --git a/src/pages/Insights/InsightsDashboard.tsx b/src/pages/Insights/InsightsDashboard.tsx index e7ba7d3806e7..3350800cd307 100644 --- a/src/pages/Insights/InsightsDashboard.tsx +++ b/src/pages/Insights/InsightsDashboard.tsx @@ -15,7 +15,6 @@ import useLocalize from '@hooks/useLocalize'; import useMultipleSnapshots from '@hooks/useMultipleSnapshots'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; -import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useScrollEventEmitter from '@hooks/useScrollEventEmitter'; import useTheme from '@hooks/useTheme'; @@ -25,7 +24,6 @@ import {getInsights} from '@libs/actions/Insights'; import variables from '@styles/variables'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {InsightsDashboardID} from '@src/types/onyx'; @@ -193,10 +191,7 @@ function InsightsDashboard({dashboardID}: {dashboardID: InsightsDashboardID}) { const {login} = useCurrentUserPersonalDetails(); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const {filters, defaultFilters, isResolved, setFilters} = useInsightsFilters(dashboardID); - const {isBetaEnabled} = usePermissions(); - - const isCompareEnabled = isBetaEnabled(CONST.BETAS.INSIGHTS_COMPARE); - const query = isResolved ? buildInsightsJsonQuery(dashboardID, filters, isCompareEnabled) : undefined; + const query = isResolved ? buildInsightsJsonQuery(dashboardID, filters) : undefined; const hash = query?.hash; const requestDashboard = () => { @@ -216,7 +211,7 @@ function InsightsDashboard({dashboardID}: {dashboardID: InsightsDashboardID}) { } onRequestConditionsChanged(); // Leaves out jsonQuery so toggling Compare mode doesn't refetch, since every response covers it. Group by is listed because the hash leaves it out - }, [dashboardID, hash, filters.groupBy, isCompareEnabled, isFocused, isOffline]); + }, [dashboardID, hash, filters.groupBy, isFocused, isOffline]); const [dashboard] = useOnyx(`${ONYXKEYS.COLLECTION.INSIGHTS}${dashboardID}_${hash}`); const {headlineChart: headlineSpec, supportingCharts: supportingSpecs} = INSIGHTS_DASHBOARD_SPECS[dashboardID]; diff --git a/src/pages/Insights/InsightsPage.tsx b/src/pages/Insights/InsightsPage.tsx index 648699bd7dfa..aeaf3e748403 100644 --- a/src/pages/Insights/InsightsPage.tsx +++ b/src/pages/Insights/InsightsPage.tsx @@ -1,6 +1,5 @@ import useDocumentTitle from '@hooks/useDocumentTitle'; import useLocalize from '@hooks/useLocalize'; -import usePermissions from '@hooks/usePermissions'; import type {TabNavigatorParamList} from '@libs/Navigation/types'; @@ -19,12 +18,11 @@ type InsightsPageProps = BottomTabScreenProps id === route.params?.dashboardID); - if (!isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE) || !dashboardID) { + if (!dashboardID) { return ; } diff --git a/src/pages/Insights/controls/InsightsPageControls.tsx b/src/pages/Insights/controls/InsightsPageControls.tsx index a1eec0004a01..6aa4326fd0f3 100644 --- a/src/pages/Insights/controls/InsightsPageControls.tsx +++ b/src/pages/Insights/controls/InsightsPageControls.tsx @@ -1,14 +1,11 @@ import ScrollView from '@components/ScrollView'; import useLayoutSpacing from '@hooks/useLayoutSpacing'; -import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import type {InsightsFilters} from '@pages/Insights/insightsFilters'; -import CONST from '@src/CONST'; - import React from 'react'; import {View} from 'react-native'; @@ -31,7 +28,6 @@ function InsightsPageControls({filters, defaultFilters, onChange}: InsightsPageC const styles = useThemeStyles(); const {pageGutter} = useLayoutSpacing(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const {isBetaEnabled} = usePermissions(); const controls = ( <> @@ -39,12 +35,10 @@ function InsightsPageControls({filters, defaultFilters, onChange}: InsightsPageC value={filters.date} onChange={(date) => onChange({date})} /> - {isBetaEnabled(CONST.BETAS.INSIGHTS_COMPARE) && ( - onChange({compare})} - /> - )} + onChange({compare})} + /> onChange({policyIDs})} diff --git a/src/pages/Insights/insightsQueries.ts b/src/pages/Insights/insightsQueries.ts index a27f0e3ba234..43259d759d2e 100644 --- a/src/pages/Insights/insightsQueries.ts +++ b/src/pages/Insights/insightsQueries.ts @@ -63,15 +63,11 @@ type InsightsGraphHashes = { type InsightsGraphHashEntry = [InsightsGraphKey, InsightsGraphHashes]; /** Returns the chart's graph slot paired with the hashes its current period, previous period and average are stored under. */ -function buildSnapshotHashEntry({chart, queryJSON, previousPeriodQueryJSON, averageQueryJSON}: InsightsChartQuery, shouldIncludeComparisons: boolean): InsightsGraphHashEntry | undefined { +function buildSnapshotHashEntry({chart, queryJSON, previousPeriodQueryJSON, averageQueryJSON}: InsightsChartQuery): InsightsGraphHashEntry | undefined { const snapshotHash = queryJSON?.hash; if (!snapshotHash) { return undefined; } - if (!shouldIncludeComparisons) { - return [chart.graphKey, {snapshotHash}]; - } - const previousPeriodSnapshotHash = previousPeriodQueryJSON?.hash; const averageSnapshotHash = averageQueryJSON?.hash; if (!previousPeriodSnapshotHash || !averageSnapshotHash) { @@ -96,7 +92,7 @@ type InsightsQuery = { }; /** Builds one request for the whole dashboard, naming each graph's snapshots by its chart's query hashes, and returns those chart queries to read the snapshots back with. */ -function buildInsightsJsonQuery(dashboard: InsightsDashboardID, filters: InsightsFilters, shouldIncludeComparisons: boolean): InsightsQuery | undefined { +function buildInsightsJsonQuery(dashboard: InsightsDashboardID, filters: InsightsFilters): InsightsQuery | undefined { // The backend saves inputQuery as the dashboard's filters, so it keeps the Group by the user picked, not the one fitted to the date range const inputQuery = buildInsightsQueryString(filters); const queryJSON = buildSearchQueryJSON(buildInsightsQueryString({...filters, groupBy: getQueryGroupBy(filters), compare: undefined})); @@ -108,16 +104,12 @@ function buildInsightsJsonQuery(dashboard: InsightsDashboardID, filters: Insight const buildChartQuery = (chart: InsightsChartSpec): InsightsChartQuery => ({ chart, queryJSON: buildSearchQueryJSON(applyInsightsFilters(chart, filters)), - ...(shouldIncludeComparisons && { - previousPeriodQueryJSON: buildSearchQueryJSON(applyInsightsFilters(chart, filters, CONST.SEARCH.COMPARE.PREVIOUS_PERIOD)), - averageQueryJSON: buildSearchQueryJSON(applyInsightsFilters(chart, filters, CONST.SEARCH.COMPARE.AVERAGE)), - }), + previousPeriodQueryJSON: buildSearchQueryJSON(applyInsightsFilters(chart, filters, CONST.SEARCH.COMPARE.PREVIOUS_PERIOD)), + averageQueryJSON: buildSearchQueryJSON(applyInsightsFilters(chart, filters, CONST.SEARCH.COMPARE.AVERAGE)), }); const headlineChart = buildChartQuery(spec.headlineChart); const supportingCharts = spec.supportingCharts.map(buildChartQuery); - const graphEntries = [headlineChart, ...supportingCharts] - .map((chartQuery) => buildSnapshotHashEntry(chartQuery, shouldIncludeComparisons)) - .filter((entry): entry is InsightsGraphHashEntry => !!entry); + const graphEntries = [headlineChart, ...supportingCharts].map(buildSnapshotHashEntry).filter((entry): entry is InsightsGraphHashEntry => !!entry); const insightsHashes: InsightsDashboard['graphs'] = Object.fromEntries(graphEntries); return { @@ -128,7 +120,7 @@ function buildInsightsJsonQuery(dashboard: InsightsDashboardID, filters: Insight inputQuery, searchKey: spec.searchKey, insightsHashes, - numberOfPeriods: shouldIncludeComparisons ? COMPARE_AVERAGE_PERIOD_COUNT : undefined, + numberOfPeriods: COMPARE_AVERAGE_PERIOD_COUNT, }), hash: queryJSON.hash, snapshotHashes: graphEntries.flatMap(([, hashes]) => Object.values(hashes).filter((hash): hash is number => hash !== undefined)), diff --git a/src/pages/Insights/useInsightsChartData.ts b/src/pages/Insights/useInsightsChartData.ts index b81bd72e4fe2..1acaef81a8bc 100644 --- a/src/pages/Insights/useInsightsChartData.ts +++ b/src/pages/Insights/useInsightsChartData.ts @@ -6,7 +6,6 @@ import type {GroupedItem, SearchQueryJSON} from '@components/Search/types'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import usePermissions from '@hooks/usePermissions'; import useTheme from '@hooks/useTheme'; import {INSIGHTS_CHART_STATE, resolveInsightsChartData} from '@libs/resolveInsightsChartData'; @@ -63,12 +62,11 @@ function useInsightsChartData(dashboardChart: InsightsDashboardChart, filters: I const theme = useTheme(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const {isBetaEnabled} = usePermissions(); const sortedData = useGroupedItems(snapshot, queryJSON); const current = resolveInsightsChartData({snapshot, queryJSON, sortedData, isOffline}); - const comparedChart = isBetaEnabled(CONST.BETAS.INSIGHTS_COMPARE) ? getComparedChart(dashboardChart, filters, translate) : undefined; + const comparedChart = getComparedChart(dashboardChart, filters, translate); // Keeps every compared group, so each plotted group finds its own and not only the top ones. const comparedSortedData = useGroupedItems(comparedChart?.snapshot, queryJSON && {...queryJSON, limit: undefined}); diff --git a/src/pages/home/InsightsSection/InsightsSectionContent.tsx b/src/pages/home/InsightsSection/InsightsSectionContent.tsx index 9dbaeec3fa45..1da07f9a7dd6 100644 --- a/src/pages/home/InsightsSection/InsightsSectionContent.tsx +++ b/src/pages/home/InsightsSection/InsightsSectionContent.tsx @@ -10,7 +10,6 @@ import useLayoutSpacing from '@hooks/useLayoutSpacing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -37,8 +36,6 @@ function InsightsSectionContent() { const {translate} = useLocalize(); const icons = useMemoizedLazyExpensifyIcons(['Expand']); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const {isBetaEnabled} = usePermissions(); - const isInsightsPageEnabled = isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE); const {configs: insightConfigs, isResolved: isConfigResolved} = useHomeInsightConfigs(); const [selectedKey, selectedKeyMetadata] = useOnyx(ONYXKEYS.NVP_HOME_SELECTED_INSIGHT); @@ -58,10 +55,7 @@ function InsightsSectionContent() { return null; } - const navigateToInsights = () => - Navigation.navigate( - isInsightsPageEnabled ? ROUTES.INSIGHTS.getRoute(CONST.INSIGHTS.DASHBOARD.SPEND) : ROUTES.SEARCH_ROOT.getRoute({query: config.searchQuery, searchKey: config.key}), - ); + const navigateToInsights = () => Navigation.navigate(ROUTES.INSIGHTS.getRoute(CONST.INSIGHTS.DASHBOARD.SPEND)); const widget = ( )} ); - if (!isInsightsPageEnabled || canUseTouchScreen()) { + if (canUseTouchScreen()) { return widget; } diff --git a/src/pages/home/InsightsSection/index.tsx b/src/pages/home/InsightsSection/index.tsx index 1e3660a9243e..c5ee8150da74 100644 --- a/src/pages/home/InsightsSection/index.tsx +++ b/src/pages/home/InsightsSection/index.tsx @@ -1,11 +1,7 @@ -import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useOnyx from '@hooks/useOnyx'; -import usePermissions from '@hooks/usePermissions'; import {isGroupPolicy} from '@libs/PolicyUtils'; -import {isPolicyEligibleForSpendOverTime} from '@libs/SearchUIUtils'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import React from 'react'; @@ -13,11 +9,8 @@ import React from 'react'; import InsightsSectionContent from './InsightsSectionContent'; function InsightsSection() { - const {login} = useCurrentUserPersonalDetails(); - const {isBetaEnabled} = usePermissions(); - const isInsightsPageEnabled = isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE); const [shouldShowInsights] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { - selector: (policies) => Object.values(policies ?? {}).some((policy) => !!policy && (isInsightsPageEnabled ? isGroupPolicy(policy) : isPolicyEligibleForSpendOverTime(policy, login))), + selector: (policies) => Object.values(policies ?? {}).some((policy) => !!policy && isGroupPolicy(policy)), }); if (!shouldShowInsights) { diff --git a/src/pages/home/InsightsSection/useHomeInsightConfigs.ts b/src/pages/home/InsightsSection/useHomeInsightConfigs.ts index 43045f86e6e4..fdd58fa54b2d 100644 --- a/src/pages/home/InsightsSection/useHomeInsightConfigs.ts +++ b/src/pages/home/InsightsSection/useHomeInsightConfigs.ts @@ -1,10 +1,7 @@ -import useCardFeedsForDisplay from '@hooks/useCardFeedsForDisplay'; import useOnyx from '@hooks/useOnyx'; -import usePermissions from '@hooks/usePermissions'; import {buildSearchQueryJSON} from '@libs/SearchQueryUtils'; import type {SearchTypeMenuItem} from '@libs/SearchUIUtils'; -import {getSuggestedSearches, getSuggestedSearchesVisibility, SPEND_INSIGHT_KEYS} from '@libs/SearchUIUtils'; import type {InsightsChartSpec} from '@pages/Insights/dashboardSpecs'; import INSIGHTS_DASHBOARD_SPECS, {getVisibleCharts} from '@pages/Insights/dashboardSpecs'; @@ -16,9 +13,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {InsightsGraphKey} from '@src/types/onyx'; -import {defaultExpensifyCardSelector} from '@selectors/Card'; -import {isTrackIntentUserSelector} from '@selectors/Onboarding'; - const HOME_INSIGHT_ICONS: Record = { [CONST.INSIGHTS.GRAPH.SPEND_OVER_TIME]: 'CalendarSolid', [CONST.INSIGHTS.GRAPH.TOP_SPENDERS]: 'User', @@ -46,23 +40,13 @@ function buildInsightConfigFromChart(chart: InsightsChartSpec, filters: Insights /** * Builds the configs for the Home insights the current user should see, in display order. - * With the Insights page beta, the charts and their visibility match the Insights Spend dashboard, otherwise the Spend menu. + * The charts and their visibility match the Insights Spend dashboard. */ function useHomeInsightConfigs(): {configs: SearchTypeMenuItem[]; isResolved: boolean} { const [session] = useOnyx(ONYXKEYS.SESSION); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const [defaultExpensifyCard] = useOnyx(ONYXKEYS.DERIVED.NON_PERSONAL_AND_WORKSPACE_CARD_LIST, {selector: defaultExpensifyCardSelector}); - const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector}); - const {defaultCardFeed, cardFeedsByPolicy} = useCardFeedsForDisplay(); - const {isBetaEnabled} = usePermissions(); const {filters, isResolved: areFiltersResolved} = useInsightsFilters(CONST.INSIGHTS.DASHBOARD.SPEND); - if (!isBetaEnabled(CONST.BETAS.INSIGHTS_PAGE)) { - const {visibility, shouldShowExpensifyCard} = getSuggestedSearchesVisibility(session?.email, cardFeedsByPolicy, policies, defaultExpensifyCard, false, !!isTrackIntentUser); - const suggestedSearches = getSuggestedSearches(session?.accountID, (defaultCardFeed ?? defaultExpensifyCard)?.id, shouldShowExpensifyCard); - return {configs: SPEND_INSIGHT_KEYS.filter((key) => visibility[key]).map((key) => suggestedSearches[key]), isResolved: true}; - } - const {headlineChart, supportingCharts} = INSIGHTS_DASHBOARD_SPECS[CONST.INSIGHTS.DASHBOARD.SPEND]; const configs = getVisibleCharts([headlineChart, ...supportingCharts], policies, filters.policyIDs, session?.email).map((chart) => buildInsightConfigFromChart(chart, filters)); return {configs, isResolved: areFiltersResolved}; diff --git a/tests/unit/SearchRouterNavigationTest.ts b/tests/unit/SearchRouterNavigationTest.ts index 84e3c26eac38..68bcbe71a449 100644 --- a/tests/unit/SearchRouterNavigationTest.ts +++ b/tests/unit/SearchRouterNavigationTest.ts @@ -407,19 +407,24 @@ describe('top-level Search Router navigation source', () => { Globe: mockIcon, Gear: mockIcon, }, - isInsightsPageBetaEnabled: false, getSpendRoute: () => ROUTES.SEARCH_ROOT.getRoute({query: 'type:expense'}), getDestinationText: (destination) => `Go to ${destination}`, }); - expect(items.map((item) => item.text)).toEqual(['Go to Home', 'Go to Inbox', 'Go to Spend', 'Go to Workspaces', 'Go to Domains', 'Go to Account']); - expect(items.map((item) => item.keyForList)).toEqual(['topLevelHome', 'topLevelInbox', 'topLevelSpend', 'topLevelWorkspaces', 'topLevelDomains', 'topLevelAccount']); + expect(items.map((item) => item.text)).toEqual(['Go to Home', 'Go to Inbox', 'Go to Spend', 'Go to Insights', 'Go to Workspaces', 'Go to Domains', 'Go to Account']); + expect(items.map((item) => item.keyForList)).toEqual([ + 'topLevelHome', + 'topLevelInbox', + 'topLevelSpend', + 'topLevelInsights', + 'topLevelWorkspaces', + 'topLevelDomains', + 'topLevelAccount', + ]); }); - it('adds the Insights destination when the beta is enabled', () => { - // Given a user with the Insights beta - const isInsightsPageBetaEnabled = true; - + it('includes the Insights destination for every user', () => { + // Given Insights is available to every user // When the top-level destinations are built const items = buildTopLevelNavigationItems({ labels: { @@ -440,7 +445,6 @@ describe('top-level Search Router navigation source', () => { Globe: mockIcon, Gear: mockIcon, }, - isInsightsPageBetaEnabled, getSpendRoute: () => ROUTES.SEARCH_ROOT.getRoute({query: 'type:expense'}), getDestinationText: (destination) => `Go to ${destination}`, }); @@ -481,7 +485,6 @@ describe('top-level Search Router navigation source', () => { Globe: mockIcon, Gear: mockIcon, }, - isInsightsPageBetaEnabled: false, getSpendRoute, getDestinationText: (destination) => `Go to ${destination}`, }); @@ -494,9 +497,10 @@ describe('top-level Search Router navigation source', () => { expect(Navigation.navigate).toHaveBeenNthCalledWith(2, ROUTES.INBOX); expect(Navigation.navigate).toHaveBeenNthCalledWith(3, spendRoute); expect(getSpendRoute).toHaveBeenCalledTimes(1); - expect(Navigation.navigate).toHaveBeenNthCalledWith(4, ROUTES.WORKSPACES_LIST.route); - expect(Navigation.navigate).toHaveBeenNthCalledWith(5, ROUTES.DOMAINS_LIST.route); - expect(Navigation.navigate).toHaveBeenNthCalledWith(6, ROUTES.SETTINGS); + expect(Navigation.navigate).toHaveBeenNthCalledWith(4, ROUTES.INSIGHTS.getRoute(CONST.INSIGHTS.DASHBOARD.SPEND)); + expect(Navigation.navigate).toHaveBeenNthCalledWith(5, ROUTES.WORKSPACES_LIST.route); + expect(Navigation.navigate).toHaveBeenNthCalledWith(6, ROUTES.DOMAINS_LIST.route); + expect(Navigation.navigate).toHaveBeenNthCalledWith(7, ROUTES.SETTINGS); }); }); diff --git a/tests/unit/insightsQueriesTest.ts b/tests/unit/insightsQueriesTest.ts index eeca7d4fef29..4731fa8b9049 100644 --- a/tests/unit/insightsQueriesTest.ts +++ b/tests/unit/insightsQueriesTest.ts @@ -55,7 +55,7 @@ describe('insightsQueries', () => { const filters: InsightsFilters = {...FILTERS, groupBy: CONST.SEARCH.GROUP_BY.MONTH}; // When its request is built - const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, true); + const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters); // Then it is addressed to the spend dashboard and names the snapshot each chart's data belongs in const payload = parsePayload(request); @@ -73,11 +73,11 @@ describe('insightsQueries', () => { it('requests the same data whether or not the dashboard is compared, saving the Compare mode along', () => { // Given the spend dashboard shown on its own - const standalone = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, FILTERS, true); + const standalone = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, FILTERS); // When it is compared against the previous period const filters: InsightsFilters = {...FILTERS, compare: CONST.SEARCH.COMPARE.PREVIOUS_PERIOD}; - const compared = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, true); + const compared = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters); // Then every response carries all comparisons, so switching one never fetches again or stores the response apart expect(compared?.hash).toBe(standalone?.hash); @@ -88,29 +88,12 @@ describe('insightsQueries', () => { expect(parsePayload(compared)?.inputQuery).toContain(`compare:${CONST.SEARCH.COMPARE.PREVIOUS_PERIOD}`); }); - it('requests only the current period for users without the compare beta', () => { - // Given the spend dashboard for a user who can't compare it - const filters: InsightsFilters = {...FILTERS, groupBy: CONST.SEARCH.GROUP_BY.MONTH}; - - // When its request is built without comparisons - const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, false); - - // Then each chart names only its own snapshot, so the backend doesn't compute periods nobody sees - const payload = parsePayload(request); - const expectedHashes = Object.fromEntries( - [SPEND_SPEC.headlineChart, ...SPEND_SPEC.supportingCharts].map((chart) => [chart.graphKey, {snapshotHash: buildSearchQueryJSON(applyInsightsFilters(chart, filters))?.hash}]), - ); - expect(payload).toMatchObject({insightsHashes: expectedHashes}); - expect(payload).not.toHaveProperty('numberOfPeriods'); - expect(request?.snapshotHashes).toEqual(Object.values(expectedHashes).map(({snapshotHash}) => snapshotHash)); - }); - it('hashes every set of filters on its own', () => { // Given the spend dashboard grouped by month - const monthly = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, FILTERS, true); + const monthly = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, FILTERS); // When the same dashboard is grouped by quarter instead - const quarterly = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, {...FILTERS, groupBy: CONST.SEARCH.GROUP_BY.QUARTER}, true); + const quarterly = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, {...FILTERS, groupBy: CONST.SEARCH.GROUP_BY.QUARTER}); // Then each request carries the hash of the query it asks for, so the two responses are stored apart expect(monthly?.hash).toBe(parsePayload(monthly)?.hash); @@ -122,7 +105,7 @@ describe('insightsQueries', () => { const filters: InsightsFilters = {...FILTERS, policyIDs: ['A1'], groupBy: CONST.SEARCH.GROUP_BY.MONTH, groupCurrency: 'USD'}; // When its request is built - const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, true); + const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters); // Then the query carries those filters and nothing else, such as the sorting of a table no chart renders expect(parsePayload(request)?.inputQuery).toBe('groupBy:month groupCurrency:USD policyID:A1 date:year-to-date'); @@ -136,7 +119,7 @@ describe('insightsQueries', () => { }; // When its request is built - const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, true); + const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters); // Then the query is bounded by the range, both ends included, and the charts ask for snapshots of it expect(parsePayload(request)?.inputQuery).toBe('groupBy:month groupCurrency:USD date>=2026-01-01 date<=2026-03-31'); @@ -148,7 +131,7 @@ describe('insightsQueries', () => { const filters: InsightsFilters = {...FILTERS, date: {on: '2026-03-04'}}; // When its request is built - const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, true); + const request = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters); // Then the query names that day the same way it names a preset, and its one point is grouped by day, the only grouping a single date offers expect(parsePayload(request)?.inputQuery).toBe('groupBy:month groupCurrency:USD date:2026-03-04'); @@ -161,7 +144,7 @@ describe('insightsQueries', () => { const filters: InsightsFilters = {...FILTERS, date: {from: '2026-03-01', to: '2026-03-31'}, groupBy: CONST.SEARCH.GROUP_BY.QUARTER}; // When its request is built - const payload = parsePayload(buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, true)); + const payload = parsePayload(buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters)); // Then the backend saves quarter, so a longer range brings it back, while the charts are grouped by week expect(payload?.inputQuery).toBe('groupBy:quarter groupCurrency:USD date>=2026-03-01 date<=2026-03-31'); @@ -170,11 +153,11 @@ describe('insightsQueries', () => { it('leaves the workspaces out of the query until some are selected', () => { // Given a dashboard requested with no workspace selected - const allWorkspaces = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, FILTERS, true); + const allWorkspaces = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, FILTERS); // When two workspaces are selected const filters: InsightsFilters = {...FILTERS, policyIDs: ['A1', 'B2']}; - const twoWorkspaces = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, true); + const twoWorkspaces = buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters); // Then only the second query names them, and its charts point at different snapshots expect(parsePayload(allWorkspaces)?.inputQuery).not.toContain('policyID'); @@ -192,7 +175,7 @@ describe('insightsQueries', () => { const queryString = buildInsightsQueryString(filters); // Then it is the very query the request carries - expect(queryString).toBe(parsePayload(buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters, true))?.inputQuery); + expect(queryString).toBe(parsePayload(buildInsightsJsonQuery(CONST.INSIGHTS.DASHBOARD.SPEND, filters))?.inputQuery); }); }); From 99bf996007626fd1a94bcb7435c2d1fbf3d72c66 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Wed, 21 Oct 2026 13:41:12 -0600 Subject: [PATCH 2/2] fix: Remove unused spend insight keys export --- src/libs/SearchUIUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 728ff18197ba..f41c8b23d19d 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -7511,7 +7511,6 @@ export { getPrimaryAction, createTypeMenuSections, omitInsightsPageMenuItems, - SPEND_INSIGHT_KEYS, formatBadgeText, getSectionBadgeText, getItemBadgeText,