diff --git a/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts b/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts index ed29dffd8869e..8c3ed25e72253 100644 --- a/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts +++ b/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts @@ -1,13 +1,9 @@ import { kea } from 'kea' -import { objectsEqual, dateMapping } from 'lib/utils' import type { intervalFilterLogicType } from './intervalFilterLogicType' import { IntervalKeyType, Intervals, intervals } from 'lib/components/IntervalFilter/intervals' -import { BaseMathType, InsightLogicProps, IntervalType } from '~/types' +import { BaseMathType, InsightLogicProps } from '~/types' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' -import { dayjs } from 'lib/dayjs' -import { InsightQueryNode, TrendsQuery } from '~/queries/schema' -import { lemonToast } from 'lib/lemon-ui/lemonToast' -import { BASE_MATH_DEFINITIONS } from 'scenes/trends/mathsLogic' +import { InsightQueryNode } from '~/queries/schema' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' export const intervalFilterLogic = kea({ @@ -49,80 +45,11 @@ export const intervalFilterLogic = kea({ }, ], }), - listeners: ({ values, actions, selectors }) => ({ + listeners: ({ values, actions }) => ({ setInterval: ({ interval }) => { if (values.interval !== interval) { actions.updateQuerySource({ interval } as Partial) } }, - updateQuerySource: ({ querySource }, _, __, previousState) => { - const { date_from, date_to } = querySource.dateRange || {} - const previousDateRange = selectors.querySource(previousState)?.dateRange || {} - - // If the user just flipped an event action to use WAUs/MAUs math and their - // current interval is unsupported by the math type, switch their interval - // to an appropriate allowed interval and inform them of the change via a toast - if ( - values.activeUsersMath && - (values.querySource as TrendsQuery)?.interval && - values.enabledIntervals[(values.querySource as TrendsQuery).interval as IntervalType].disabledReason - ) { - if (values.interval === 'hour') { - lemonToast.info( - `Switched to grouping by day, because "${ - BASE_MATH_DEFINITIONS[values.activeUsersMath].name - }" does not support grouping by ${values.interval}.` - ) - actions.updateQuerySource({ interval: 'day' } as Partial) - } else { - lemonToast.info( - `Switched to grouping by week, because "${ - BASE_MATH_DEFINITIONS[values.activeUsersMath].name - }" does not support grouping by ${values.interval}.` - ) - actions.updateQuerySource({ interval: 'week' } as Partial) - } - return - } - - if ( - !date_from || - (objectsEqual(date_from, previousDateRange.date_from) && - objectsEqual(date_to, previousDateRange.date_to)) - ) { - return - } - - // automatically set an interval for fixed date ranges - if ( - date_from && - date_to && - dayjs(querySource.dateRange?.date_from).isValid() && - dayjs(querySource.dateRange?.date_to).isValid() - ) { - if (dayjs(date_to).diff(dayjs(date_from), 'day') <= 3) { - actions.updateQuerySource({ interval: 'hour' } as Partial) - } else if (dayjs(date_to).diff(dayjs(date_from), 'month') <= 3) { - actions.updateQuerySource({ interval: 'day' } as Partial) - } else { - actions.updateQuerySource({ interval: 'month' } as Partial) - } - return - } - // get a defaultInterval for dateOptions that have a default value - let interval: IntervalType = 'day' - for (const { key, values, defaultInterval } of dateMapping) { - if ( - values[0] === date_from && - values[1] === (date_to || undefined) && - key !== 'Custom' && - defaultInterval - ) { - interval = defaultInterval - break - } - } - actions.updateQuerySource({ interval } as Partial) - }, }), }) diff --git a/frontend/src/scenes/insights/insightVizDataLogic.ts b/frontend/src/scenes/insights/insightVizDataLogic.ts index 3032d59447d1f..b85f0dd12ef16 100644 --- a/frontend/src/scenes/insights/insightVizDataLogic.ts +++ b/frontend/src/scenes/insights/insightVizDataLogic.ts @@ -1,6 +1,6 @@ import posthog from 'posthog-js' import { actions, connect, kea, key, listeners, path, props, selectors, reducers } from 'kea' -import { BaseMathType, ChartDisplayType, InsightLogicProps } from '~/types' +import { BaseMathType, ChartDisplayType, InsightLogicProps, IntervalType } from '~/types' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { BreakdownFilter, @@ -48,9 +48,15 @@ import { sceneLogic } from 'scenes/sceneLogic' import type { insightVizDataLogicType } from './insightVizDataLogicType' import { parseProperties } from 'lib/components/PropertyFilters/utils' import { filterTestAccountsDefaultsLogic } from 'scenes/project/Settings/filterTestAccountDefaultsLogic' +import { BASE_MATH_DEFINITIONS } from 'scenes/trends/mathsLogic' +import { lemonToast } from '@posthog/lemon-ui' +import { dayjs } from 'lib/dayjs' +import { dateMapping } from 'lib/utils' const SHOW_TIMEOUT_MESSAGE_AFTER = 5000 +type QuerySourceUpdate = Omit, 'kind'> + export const insightVizDataLogic = kea([ props({} as InsightLogicProps), key(keyForInsightLogicProps('new')), @@ -73,7 +79,7 @@ export const insightVizDataLogic = kea([ actions({ saveInsight: (redirectToViewMode = true) => ({ redirectToViewMode }), - updateQuerySource: (querySource: Omit, 'kind'>) => ({ querySource }), + updateQuerySource: (querySource: QuerySourceUpdate) => ({ querySource }), updateInsightFilter: (insightFilter: InsightFilter) => ({ insightFilter }), updateDateRange: (dateRange: DateRange) => ({ dateRange }), updateBreakdown: (breakdown: BreakdownFilter) => ({ breakdown }), @@ -186,19 +192,8 @@ export const insightVizDataLogic = kea([ activeUsersMath: [ (s) => [s.series], - (series): BaseMathType.MonthlyActiveUsers | BaseMathType.WeeklyActiveUsers | null => { - for (const seriesItem of series || []) { - if (seriesItem.math === BaseMathType.WeeklyActiveUsers) { - return BaseMathType.WeeklyActiveUsers - } - - if (seriesItem.math === BaseMathType.MonthlyActiveUsers) { - return BaseMathType.MonthlyActiveUsers - } - } - - return null - }, + (series): BaseMathType.MonthlyActiveUsers | BaseMathType.WeeklyActiveUsers | null => + getActiveUsersMath(series), ], erroredQueryId: [ @@ -230,7 +225,10 @@ export const insightVizDataLogic = kea([ updateQuerySource: ({ querySource }) => { actions.setQuery({ ...values.query, - source: { ...values.querySource, ...querySource }, + source: { + ...values.querySource, + ...handleQuerySourceUpdateSideEffects(querySource, values.querySource as InsightQueryNode), + }, } as Node) }, setQuery: ({ query }) => { @@ -266,3 +264,83 @@ export const insightVizDataLogic = kea([ }, })), ]) + +const getActiveUsersMath = ( + series: TrendsQuery['series'] | null | undefined +): BaseMathType.WeeklyActiveUsers | BaseMathType.MonthlyActiveUsers | null => { + for (const seriesItem of series || []) { + if (seriesItem.math === BaseMathType.WeeklyActiveUsers) { + return BaseMathType.WeeklyActiveUsers + } + + if (seriesItem.math === BaseMathType.MonthlyActiveUsers) { + return BaseMathType.MonthlyActiveUsers + } + } + + return null +} + +const handleQuerySourceUpdateSideEffects = ( + update: QuerySourceUpdate, + currentState: InsightQueryNode +): QuerySourceUpdate => { + const mergedUpdate = { ...update } + + const maybeChangedSeries = (update as TrendsQuery).series || null + const maybeChangedActiveUsersMath = maybeChangedSeries ? getActiveUsersMath(maybeChangedSeries) : null + const interval = (currentState as TrendsQuery).interval + + // If the user just flipped an event action to use WAUs/MAUs math and their + // current interval is unsupported by the math type, switch their interval + // to an appropriate allowed interval and inform them of the change via a toast + if (maybeChangedActiveUsersMath !== null && (interval === 'hour' || interval === 'month')) { + if (interval === 'hour') { + lemonToast.info( + `Switched to grouping by day, because "${BASE_MATH_DEFINITIONS[maybeChangedActiveUsersMath].name}" does not support grouping by ${interval}.` + ) + ;(mergedUpdate as Partial).interval = 'day' + } else if (interval === 'month' && maybeChangedActiveUsersMath === BaseMathType.WeeklyActiveUsers) { + lemonToast.info( + `Switched to grouping by week, because "${BASE_MATH_DEFINITIONS[maybeChangedActiveUsersMath].name}" does not support grouping by ${interval}.` + ) + ;(mergedUpdate as Partial).interval = 'week' + } + } + + if ( + update.dateRange && + update.dateRange.date_from && + (update.dateRange.date_from !== currentState.dateRange?.date_from || + update.dateRange.date_to !== currentState.dateRange?.date_to) + ) { + const { date_from, date_to } = { ...currentState.dateRange, ...update.dateRange } + + if (date_from && date_to && dayjs(date_from).isValid() && dayjs(date_to).isValid()) { + if (dayjs(date_to).diff(dayjs(date_from), 'day') <= 3) { + ;(mergedUpdate as Partial).interval = 'hour' + } else if (dayjs(date_to).diff(dayjs(date_from), 'month') <= 3) { + ;(mergedUpdate as Partial).interval = 'day' + } else { + ;(mergedUpdate as Partial).interval = 'month' + } + } else { + // get a defaultInterval for dateOptions that have a default value + let newDefaultInterval: IntervalType = 'day' + for (const { key, values, defaultInterval } of dateMapping) { + if ( + values[0] === date_from && + values[1] === (date_to || undefined) && + key !== 'Custom' && + defaultInterval + ) { + newDefaultInterval = defaultInterval + break + } + } + ;(mergedUpdate as Partial).interval = newDefaultInterval + } + } + + return mergedUpdate +}