diff --git a/frontend/src/lib/colors.ts b/frontend/src/lib/colors.ts index 8dfaa08b76f53..da73a760eace7 100644 --- a/frontend/src/lib/colors.ts +++ b/frontend/src/lib/colors.ts @@ -52,7 +52,7 @@ export function getColorVar(variable: string): string { */ export function getSeriesColor( index: number | undefined = 0, - comparePrevious: boolean = false, + comparePrevious: boolean | null = false, asBackgroundHighlight?: boolean ): string { const adjustedIndex = (comparePrevious ? Math.floor(index / 2) : index) % dataColorVars.length diff --git a/frontend/src/lib/components/ChartFilter/ChartFilter.tsx b/frontend/src/lib/components/ChartFilter/ChartFilter.tsx index 07c2e16662ad4..723bdca50ae2c 100644 --- a/frontend/src/lib/components/ChartFilter/ChartFilter.tsx +++ b/frontend/src/lib/components/ChartFilter/ChartFilter.tsx @@ -11,21 +11,18 @@ import { IconPublic, } from 'lib/lemon-ui/icons' -import { ChartDisplayType, FilterType, TrendsFilterType } from '~/types' +import { ChartDisplayType } from '~/types' import { insightLogic } from 'scenes/insights/insightLogic' import { LemonSelect, LemonSelectOptions } from '@posthog/lemon-ui' -import { isTrendsFilter } from 'scenes/insights/sharedUtils' +import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' -interface ChartFilterProps { - filters: FilterType -} - -export function ChartFilter({ filters }: ChartFilterProps): JSX.Element { - const { insightProps, isSingleSeries } = useValues(insightLogic) +export function ChartFilter(): JSX.Element { + const { insightProps } = useValues(insightLogic) const { chartFilter } = useValues(chartFilterLogic(insightProps)) const { setChartFilter } = useActions(chartFilterLogic(insightProps)) - const isTrends = isTrendsFilter(filters) + const { isTrends, isSingleSeries, formula, breakdown } = useValues(insightVizDataLogic(insightProps)) + const trendsOnlyDisabledReason = !isTrends ? 'This type is only available in Trends.' : undefined const singleSeriesOnlyDisabledReason = !isSingleSeries ? 'This type currently only supports insights with one series, and this insight has multiple series.' @@ -96,11 +93,11 @@ export function ChartFilter({ filters }: ChartFilterProps): JSX.Element { tooltip: 'Visualize data by country.', disabledReason: trendsOnlyDisabledReason || - ((filters as TrendsFilterType).formula + (formula ? "This type isn't available, because it doesn't support formulas." - : !!filters.breakdown && - filters.breakdown !== '$geoip_country_code' && - filters.breakdown !== '$geoip_country_name' + : !!breakdown?.breakdown && + breakdown.breakdown !== '$geoip_country_code' && + breakdown.breakdown !== '$geoip_country_name' ? "This type isn't available, because there's a breakdown other than by Country Code or Country Name properties." : undefined), }, diff --git a/frontend/src/lib/components/ChartFilter/chartFilterLogic.ts b/frontend/src/lib/components/ChartFilter/chartFilterLogic.ts index 945f2652afe8b..a94e57dcc76cd 100644 --- a/frontend/src/lib/components/ChartFilter/chartFilterLogic.ts +++ b/frontend/src/lib/components/ChartFilter/chartFilterLogic.ts @@ -1,14 +1,7 @@ import { kea } from 'kea' -import { objectsEqual } from 'lib/utils' import type { chartFilterLogicType } from './chartFilterLogicType' -import { ChartDisplayType, InsightLogicProps, TrendsFilterType } from '~/types' -import { - isFilterWithDisplay, - isStickinessFilter, - isTrendsFilter, - keyForInsightLogicProps, -} from 'scenes/insights/sharedUtils' -import { insightLogic } from 'scenes/insights/insightLogic' +import { ChartDisplayType, InsightLogicProps } from '~/types' +import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' export const chartFilterLogic = kea({ @@ -16,18 +9,8 @@ export const chartFilterLogic = kea({ key: keyForInsightLogicProps('new'), path: (key) => ['lib', 'components', 'ChartFilter', 'chartFilterLogic', key], connect: (props: InsightLogicProps) => ({ - actions: [ - insightLogic(props), - ['setFilters'], - insightVizDataLogic(props), - ['updateInsightFilter', 'updateBreakdown'], - ], - values: [ - insightLogic(props), - ['filters'], - insightVizDataLogic(props), - ['isTrends', 'isStickiness', 'display', 'series'], - ], + actions: [insightVizDataLogic(props), ['updateInsightFilter', 'updateBreakdown']], + values: [insightVizDataLogic(props), ['isTrends', 'isStickiness', 'display', 'series']], }), actions: () => ({ @@ -35,23 +18,11 @@ export const chartFilterLogic = kea({ }), selectors: { - chartFilter: [ - (s) => [s.filters], - (filters): ChartDisplayType | null => { - return (isFilterWithDisplay(filters) ? filters.display : null) || null - }, - ], + chartFilter: [(s) => [s.display], (display): ChartDisplayType | null | undefined => display], }, listeners: ({ actions, values }) => ({ setChartFilter: ({ chartFilter }) => { - if (isTrendsFilter(values.filters) || isStickinessFilter(values.filters)) { - if (!objectsEqual(values.filters.display, chartFilter)) { - const newFilteres: Partial = { ...values.filters, display: chartFilter } - actions.setFilters(newFilteres) - } - } - const { isTrends, isStickiness, display, series } = values const newDisplay = chartFilter as ChartDisplayType diff --git a/frontend/src/lib/components/CompareFilter/CompareFilter.tsx b/frontend/src/lib/components/CompareFilter/CompareFilter.tsx index 5b79f3abf0d34..0fe903e0357f3 100644 --- a/frontend/src/lib/components/CompareFilter/CompareFilter.tsx +++ b/frontend/src/lib/components/CompareFilter/CompareFilter.tsx @@ -16,7 +16,7 @@ export function CompareFilter(): JSX.Element | null { return ( Compare to previous time period} bordered diff --git a/frontend/src/lib/components/CompareFilter/compareFilterLogic.ts b/frontend/src/lib/components/CompareFilter/compareFilterLogic.ts index f225a228b2a7d..c3e2d1b65dcbe 100644 --- a/frontend/src/lib/components/CompareFilter/compareFilterLogic.ts +++ b/frontend/src/lib/components/CompareFilter/compareFilterLogic.ts @@ -1,12 +1,8 @@ import { kea } from 'kea' -import { objectsEqual } from 'lib/utils' -import { ChartDisplayType, InsightLogicProps, InsightType, TrendsFilterType } from '~/types' +import { ChartDisplayType, InsightLogicProps } from '~/types' import type { compareFilterLogicType } from './compareFilterLogicType' -import { isStickinessFilter, keyForInsightLogicProps } from 'scenes/insights/sharedUtils' +import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { insightLogic } from 'scenes/insights/insightLogic' -import { isTrendsFilter } from 'scenes/insights/sharedUtils' -import { StickinessFilter, TrendsFilter } from '~/queries/schema' -import { filterForQuery, isInsightQueryNode } from '~/queries/utils' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' export const compareFilterLogic = kea({ @@ -14,13 +10,13 @@ export const compareFilterLogic = kea({ key: keyForInsightLogicProps('new'), path: (key) => ['lib', 'components', 'CompareFilter', 'compareFilterLogic', key], connect: (props: InsightLogicProps) => ({ - actions: [insightLogic(props), ['setFilters'], insightVizDataLogic(props), ['updateInsightFilter']], values: [ insightLogic(props), - ['filters as inflightFilters', 'canEditInsight'], + ['canEditInsight'], insightVizDataLogic(props), - ['querySource'], + ['compare', 'display', 'insightFilter', 'isLifecycle', 'dateRange'], ], + actions: [insightVizDataLogic(props), ['updateInsightFilter']], }), actions: () => ({ @@ -29,42 +25,19 @@ export const compareFilterLogic = kea({ }), selectors: { - filters: [ - (s) => [s.inflightFilters], - (inflightFilters): Partial => - inflightFilters && (isTrendsFilter(inflightFilters) || isStickinessFilter(inflightFilters)) - ? inflightFilters - : {}, - ], - compare: [ - (s) => [s.filters], - (filters) => filters && (isTrendsFilter(filters) || isStickinessFilter(filters)) && !!filters.compare, - ], disabled: [ - (s) => [s.filters, s.canEditInsight], - ({ insight, date_from, display }, canEditInsight) => + (s) => [s.canEditInsight, s.isLifecycle, s.display, s.dateRange], + (canEditInsight, isLifecycle, display, dateRange) => !canEditInsight || - insight === InsightType.LIFECYCLE || + isLifecycle || display === ChartDisplayType.WorldMap || - date_from === 'all', + dateRange?.date_from === 'all', ], }, listeners: ({ values, actions }) => ({ setCompare: ({ compare }) => { - if (!objectsEqual(compare, values.compare)) { - const newFilters: Partial = { ...values.filters, compare } - actions.setFilters(newFilters) - } - - if (isInsightQueryNode(values.querySource)) { - const currentCompare = ( - filterForQuery(values.querySource) as TrendsFilter | StickinessFilter | undefined - )?.compare - if (currentCompare !== compare) { - actions.updateInsightFilter({ compare }) - } - } + actions.updateInsightFilter({ compare }) }, toggleCompare: () => { actions.setCompare(!values.compare) diff --git a/frontend/src/lib/components/InsightLegend/InsightLegend.tsx b/frontend/src/lib/components/InsightLegend/InsightLegend.tsx index 6cbb487877703..ef82a062c8b95 100644 --- a/frontend/src/lib/components/InsightLegend/InsightLegend.tsx +++ b/frontend/src/lib/components/InsightLegend/InsightLegend.tsx @@ -1,10 +1,10 @@ import './InsightLegend.scss' import { useActions, useValues } from 'kea' import { insightLogic } from 'scenes/insights/insightLogic' -import { trendsLogic } from 'scenes/trends/trendsLogic' import clsx from 'clsx' import { InsightLegendRow } from './InsightLegendRow' -import { shouldShowLegend, shouldHighlightThisRow } from './utils' +import { shouldHighlightThisRow } from './utils' +import { trendsDataLogic } from 'scenes/trends/trendsDataLogic' export interface InsightLegendProps { readOnly?: boolean @@ -13,12 +13,13 @@ export interface InsightLegendProps { } export function InsightLegend({ horizontal, inCardView, readOnly = false }: InsightLegendProps): JSX.Element | null { - const { insightProps, filters, highlightedSeries, isSingleSeries } = useValues(insightLogic) - const logic = trendsLogic(insightProps) - const { indexedResults, hiddenLegendKeys } = useValues(logic) - const { toggleVisibility } = useActions(logic) + const { insightProps, highlightedSeries, hiddenLegendKeys } = useValues(insightLogic) + const { toggleVisibility } = useActions(insightLogic) + const { indexedResults, compare, display, trendsFilter, hasLegend, isSingleSeries } = useValues( + trendsDataLogic(insightProps) + ) - return shouldShowLegend(filters) ? ( + return hasLegend ? (
))}
diff --git a/frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx b/frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx index 5a7b3e22620c1..900dec1969031 100644 --- a/frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx +++ b/frontend/src/lib/components/InsightLegend/InsightLegendRow.tsx @@ -2,11 +2,23 @@ import { InsightLabel } from 'lib/components/InsightLabel' import { getSeriesColor } from 'lib/colors' import { LemonCheckbox } from 'lib/lemon-ui/LemonCheckbox' import { formatCompareLabel } from 'scenes/insights/views/InsightsTable/columns/SeriesColumn' -import { ChartDisplayType, FilterType } from '~/types' +import { ChartDisplayType } from '~/types' import { formatAggregationAxisValue } from 'scenes/insights/aggregationAxisFormat' import { IndexedTrendResult } from 'scenes/trends/types' import { useEffect, useRef } from 'react' -import { isTrendsFilter } from 'scenes/insights/sharedUtils' +import { TrendsFilter } from '~/queries/schema' + +type InsightLegendRowProps = { + hiddenLegendKeys: Record + rowIndex: number + item: IndexedTrendResult + hasMultipleSeries: boolean + toggleVisibility: (index: number) => void + compare?: boolean | null + display?: ChartDisplayType | null + trendsFilter?: TrendsFilter | null + highlighted: boolean +} export function InsightLegendRow({ hiddenLegendKeys, @@ -14,17 +26,11 @@ export function InsightLegendRow({ item, hasMultipleSeries, toggleVisibility, - filters, + compare, + display, + trendsFilter, highlighted, -}: { - hiddenLegendKeys: Record - rowIndex: number - item: IndexedTrendResult - hasMultipleSeries: boolean - toggleVisibility: (index: number) => void - filters: Partial - highlighted: boolean -}): JSX.Element { +}: InsightLegendRowProps): JSX.Element { const highlightStyle: Record = highlighted ? { style: { backgroundColor: getSeriesColor(item.seriesIndex, false, true) }, @@ -38,8 +44,6 @@ export function InsightLegendRow({ } }, [highlighted]) - const compare = isTrendsFilter(filters) && !!filters.compare - return (
@@ -64,8 +68,10 @@ export function InsightLegendRow({ } />
- {isTrendsFilter(filters) && filters.display === ChartDisplayType.ActionsPie && ( -
{formatAggregationAxisValue(filters, item.aggregated_value)}
+ {display === ChartDisplayType.ActionsPie && ( +
+ {formatAggregationAxisValue(trendsFilter, item.aggregated_value)} +
)}
) diff --git a/frontend/src/lib/components/InsightLegend/utils.ts b/frontend/src/lib/components/InsightLegend/utils.ts index 25b041830a236..edc6bd788b4ac 100644 --- a/frontend/src/lib/components/InsightLegend/utils.ts +++ b/frontend/src/lib/components/InsightLegend/utils.ts @@ -1,16 +1,12 @@ -import { ChartDisplayType, FilterType } from '~/types' -import { isFilterWithDisplay } from 'scenes/insights/sharedUtils' +import { ChartDisplayType } from '~/types' -export const displayTypesWithoutLegend = [ +export const DISPLAY_TYPES_WITHOUT_LEGEND = [ ChartDisplayType.WorldMap, ChartDisplayType.ActionsTable, ChartDisplayType.BoldNumber, ChartDisplayType.ActionsBarValue, ] -export const shouldShowLegend = (filters: Partial): boolean => - isFilterWithDisplay(filters) && !!filters.display && !displayTypesWithoutLegend.includes(filters.display) - export function shouldHighlightThisRow( hiddenLegendKeys: Record, rowIndex: number, diff --git a/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.test.ts b/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.test.ts deleted file mode 100644 index 54a6f05499404..0000000000000 --- a/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { initKeaTests } from '~/test/init' -import { expectLogic } from 'kea-test-utils' -import { insightLogic } from 'scenes/insights/insightLogic' -import { InsightShortId } from '~/types' -import { intervalFilterLogic } from 'lib/components/IntervalFilter/intervalFilterLogic' -import { useMocks } from '~/mocks/jest' - -describe('intervalFilterLogic', () => { - let logic: ReturnType - const props = { dashboardItemId: 'test' as InsightShortId } - - beforeEach(() => { - useMocks({ - get: { - '/api/projects/:team/insights/path': { result: ['result from api'] }, - '/api/projects/:team/insights/paths/': { result: ['result from api'] }, - '/api/projects/:team/insights/trend/': { result: ['result from api'] }, - '/api/projects/${MOCK_TEAM_ID}/insights': { results: ['result from api'] }, - }, - }) - initKeaTests() - insightLogic(props).mount() - logic = intervalFilterLogic(props) - logic.mount() - }) - - describe('core assumptions', () => { - it('mounts all sorts of logics', async () => { - await expectLogic(logic).toMount([insightLogic(logic.props)]) - }) - }) - - describe('syncs with insightLogic', () => { - it('setInterval updates insightLogic filters', async () => { - await expectLogic(logic, () => { - logic.actions.setInterval('month') - }) - .toDispatchActions([insightLogic(logic.props).actionCreators.setFilters({ interval: 'month' })]) - .toMatchValues({ - interval: 'month', - }) - }) - }) -}) diff --git a/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts b/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts index 265599aec58eb..a2ee9404353d3 100644 --- a/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts +++ b/frontend/src/lib/components/IntervalFilter/intervalFilterLogic.ts @@ -2,11 +2,10 @@ 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 { insightLogic } from 'scenes/insights/insightLogic' import { BaseMathType, InsightLogicProps, IntervalType } from '~/types' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { dayjs } from 'lib/dayjs' -import { FunnelsQuery, InsightQueryNode, StickinessQuery, TrendsQuery } from '~/queries/schema' +import { InsightQueryNode, TrendsQuery } from '~/queries/schema' import { lemonToast } from 'lib/lemon-ui/lemonToast' import { BASE_MATH_DEFINITIONS } from 'scenes/trends/mathsLogic' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' @@ -16,8 +15,8 @@ export const intervalFilterLogic = kea({ key: keyForInsightLogicProps('new'), path: (key) => ['lib', 'components', 'IntervalFilter', 'intervalFilterLogic', key], connect: (props: InsightLogicProps) => ({ - actions: [insightLogic(props), ['setFilters'], insightVizDataLogic(props), ['updateQuerySource']], - values: [insightLogic(props), ['filters'], insightVizDataLogic(props), ['querySource']], + actions: [insightVizDataLogic(props), ['updateQuerySource']], + values: [insightVizDataLogic(props), ['interval', 'querySource']], }), actions: () => ({ setInterval: (interval: IntervalKeyType) => ({ interval }), @@ -33,14 +32,7 @@ export const intervalFilterLogic = kea({ }), listeners: ({ values, actions, selectors }) => ({ setInterval: ({ interval }) => { - if (!objectsEqual(interval, values.filters.interval)) { - actions.setFilters({ ...values.filters, interval }) - } - - if ( - !values.querySource || - (values.querySource as FunnelsQuery | StickinessQuery | TrendsQuery).interval !== interval - ) { + if (values.interval !== interval) { actions.updateQuerySource({ interval } as Partial) } }, @@ -149,7 +141,4 @@ export const intervalFilterLogic = kea({ actions.updateQuerySource({ interval } as Partial) }, }), - selectors: { - interval: [(s) => [s.filters], (filters) => filters?.interval], - }, }) diff --git a/frontend/src/lib/components/SmoothingFilter/SmoothingFilter.tsx b/frontend/src/lib/components/SmoothingFilter/SmoothingFilter.tsx index 3ee43dbab355e..961fa82e2ef84 100644 --- a/frontend/src/lib/components/SmoothingFilter/SmoothingFilter.tsx +++ b/frontend/src/lib/components/SmoothingFilter/SmoothingFilter.tsx @@ -3,21 +3,19 @@ import { FundOutlined } from '@ant-design/icons' import { smoothingOptions } from './smoothings' import { useActions, useValues } from 'kea' import { insightLogic } from 'scenes/insights/insightLogic' -import { trendsLogic } from 'scenes/trends/trendsLogic' -import { isTrendsFilter } from 'scenes/insights/sharedUtils' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' +import { trendsDataLogic } from 'scenes/trends/trendsDataLogic' export function SmoothingFilter(): JSX.Element | null { const { insightProps } = useValues(insightLogic) - const { filters } = useValues(trendsLogic(insightProps)) - const { setFilters } = useActions(trendsLogic(insightProps)) + const { isTrends, interval, trendsFilter } = useValues(trendsDataLogic(insightProps)) const { updateInsightFilter } = useActions(insightVizDataLogic(insightProps)) - if (!filters.interval || !isTrendsFilter(filters)) { + if (!isTrends || !interval) { return null } - const { interval, smoothing_intervals } = filters + const { smoothing_intervals } = trendsFilter || {} // Put a little icon next to the selected item const options = smoothingOptions[interval].map(({ value, label }) => ({ @@ -39,7 +37,6 @@ export function SmoothingFilter(): JSX.Element | null { value={smoothing_intervals || 1} dropdownMatchSelectWidth={false} onChange={(key) => { - setFilters({ ...filters, smoothing_intervals: key }) updateInsightFilter({ smoothing_intervals: key, }) diff --git a/frontend/src/lib/components/UnitPicker/CustomUnitModal.tsx b/frontend/src/lib/components/UnitPicker/CustomUnitModal.tsx index 7474172d54ed1..35f30b91f3241 100644 --- a/frontend/src/lib/components/UnitPicker/CustomUnitModal.tsx +++ b/frontend/src/lib/components/UnitPicker/CustomUnitModal.tsx @@ -1,4 +1,3 @@ -import { FilterType, TrendsFilterType } from '~/types' import { RefCallback, useEffect, useState } from 'react' import { LemonModal } from 'lib/lemon-ui/LemonModal' import { LemonButton } from 'lib/lemon-ui/LemonButton' @@ -6,42 +5,45 @@ import { PureField } from 'lib/forms/Field' import { capitalizeFirstLetter } from 'lib/utils' import { LemonInput } from 'lib/lemon-ui/LemonInput/LemonInput' import { HandleUnitChange } from 'lib/components/UnitPicker/UnitPicker' +import { TrendsFilter } from '~/queries/schema' function chooseFormativeElementValue( formativeElement: 'prefix' | 'postfix' | null, - filters: Partial + trendsFilter: TrendsFilter | null | undefined ): string { if (formativeElement === 'prefix') { - return filters.aggregation_axis_prefix || '' + return trendsFilter?.aggregation_axis_prefix || '' } if (formativeElement === 'postfix') { - return filters.aggregation_axis_postfix || '' + return trendsFilter?.aggregation_axis_postfix || '' } return '' } +type CustomUnitModalProps = { + isOpen: boolean + onSave: (hx: HandleUnitChange) => void + formativeElement: 'prefix' | 'postfix' | null + trendsFilter: TrendsFilter | null | undefined + onClose: () => void + overlayRef: RefCallback +} + export function CustomUnitModal({ isOpen, onSave, formativeElement, - filters, + trendsFilter, onClose, overlayRef, -}: { - isOpen: boolean - onSave: (hx: HandleUnitChange) => void - formativeElement: 'prefix' | 'postfix' | null - filters: Partial - onClose: () => void - overlayRef: RefCallback -}): JSX.Element | null { +}: CustomUnitModalProps): JSX.Element | null { const [localFormativeElementValue, setLocalFormativeElementValue] = useState( - chooseFormativeElementValue(formativeElement, filters) + chooseFormativeElementValue(formativeElement, trendsFilter) ) useEffect(() => { - setLocalFormativeElementValue(chooseFormativeElementValue(formativeElement, filters)) + setLocalFormativeElementValue(chooseFormativeElementValue(formativeElement, trendsFilter)) }, [formativeElement]) if (formativeElement === null) { diff --git a/frontend/src/lib/components/UnitPicker/UnitPicker.tsx b/frontend/src/lib/components/UnitPicker/UnitPicker.tsx index d960cac873657..85da8532fc7f1 100644 --- a/frontend/src/lib/components/UnitPicker/UnitPicker.tsx +++ b/frontend/src/lib/components/UnitPicker/UnitPicker.tsx @@ -2,7 +2,6 @@ import { AggregationAxisFormat, INSIGHT_UNIT_OPTIONS, axisLabel } from 'scenes/i import { LemonButton, LemonButtonWithDropdown } from 'lib/lemon-ui/LemonButton' import { LemonDivider } from 'lib/lemon-ui/LemonDivider' import { useMemo, useRef, useState } from 'react' -import { ItemMode, TrendsFilterType } from '~/types' import { useActions, useValues } from 'kea' import { useKeyboardHotkeys } from 'lib/hooks/useKeyboardHotkeys' import { eventUsageLogic } from 'lib/utils/eventUsageLogic' @@ -10,11 +9,6 @@ import { CustomUnitModal } from 'lib/components/UnitPicker/CustomUnitModal' import { insightLogic } from 'scenes/insights/insightLogic' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' -interface UnitPickerProps { - filters: TrendsFilterType - setFilters: (filters: Partial, insightMode?: ItemMode | undefined) => void -} - const aggregationDisplayMap = INSIGHT_UNIT_OPTIONS.reduce((acc, option) => { acc[option.value] = option.label return acc @@ -27,12 +21,15 @@ export interface HandleUnitChange { close?: boolean } -export function UnitPicker({ filters, setFilters }: UnitPickerProps): JSX.Element { +export function UnitPicker(): JSX.Element { const { insightProps } = useValues(insightLogic) + const { trendsFilter, display } = useValues(insightVizDataLogic(insightProps)) const { updateInsightFilter } = useActions(insightVizDataLogic(insightProps)) + const { reportAxisUnitsChanged } = useActions(eventUsageLogic) + const [isVisible, setIsVisible] = useState(false) - const [localAxisFormat, setLocalAxisFormat] = useState(filters.aggregation_axis_format || undefined) + const [localAxisFormat, setLocalAxisFormat] = useState(trendsFilter?.aggregation_axis_format || undefined) const [customUnitModal, setCustomUnitModal] = useState<'prefix' | 'postfix' | null>(null) const customUnitModalRef = useRef(null) @@ -52,13 +49,6 @@ export function UnitPicker({ filters, setFilters }: UnitPickerProps): JSX.Elemen const handleChange = ({ format, prefix, postfix }: HandleUnitChange): void => { setLocalAxisFormat(format) - setFilters({ - ...filters, - aggregation_axis_format: format, - aggregation_axis_prefix: prefix, - aggregation_axis_postfix: postfix, - }) - updateInsightFilter({ aggregation_axis_format: format, aggregation_axis_prefix: prefix, @@ -69,7 +59,7 @@ export function UnitPicker({ filters, setFilters }: UnitPickerProps): JSX.Elemen format, prefix, postfix, - display: filters.display, + display, unitIsSet: !!prefix || !!postfix || (format && format !== 'numeric'), }) @@ -77,28 +67,28 @@ export function UnitPicker({ filters, setFilters }: UnitPickerProps): JSX.Elemen setCustomUnitModal(null) } - const display = useMemo(() => { + const displayValue = useMemo(() => { let displayValue = 'None' if (localAxisFormat) { displayValue = aggregationDisplayMap[localAxisFormat] } - if (filters.aggregation_axis_prefix?.length) { - displayValue = `Prefix: ${filters.aggregation_axis_prefix}` + if (trendsFilter?.aggregation_axis_prefix?.length) { + displayValue = `Prefix: ${trendsFilter?.aggregation_axis_prefix}` } - if (filters.aggregation_axis_postfix?.length) { - displayValue = `Postfix: ${filters.aggregation_axis_postfix}` + if (trendsFilter?.aggregation_axis_postfix?.length) { + displayValue = `Postfix: ${trendsFilter?.aggregation_axis_postfix}` } return displayValue - }, [localAxisFormat, filters]) + }, [localAxisFormat, trendsFilter]) return ( <> - {axisLabel(filters.display)} + {axisLabel(display)} setCustomUnitModal(null)} overlayRef={(ref) => (customUnitModalRef.current = ref)} /> @@ -131,23 +121,23 @@ export function UnitPicker({ filters, setFilters }: UnitPickerProps): JSX.Elemen setCustomUnitModal('prefix')} status="stealth" - active={!!filters.aggregation_axis_prefix} + active={!!trendsFilter?.aggregation_axis_prefix} fullWidth > Custom prefix - {!!filters.aggregation_axis_prefix - ? `: ${filters.aggregation_axis_prefix}...` + {!!trendsFilter?.aggregation_axis_prefix + ? `: ${trendsFilter?.aggregation_axis_prefix}...` : '...'} setCustomUnitModal('postfix')} status="stealth" - active={!!filters.aggregation_axis_postfix} + active={!!trendsFilter?.aggregation_axis_postfix} fullWidth > Custom postfix - {!!filters.aggregation_axis_postfix - ? `: ${filters.aggregation_axis_postfix}...` + {!!trendsFilter?.aggregation_axis_postfix + ? `: ${trendsFilter?.aggregation_axis_postfix}...` : '...'} @@ -158,7 +148,7 @@ export function UnitPicker({ filters, setFilters }: UnitPickerProps): JSX.Elemen closeOnClickInside: false, }} > - {display} + {displayValue} ) diff --git a/frontend/src/lib/utils.tsx b/frontend/src/lib/utils.tsx index 23684ddbc832c..98cef3185a1f5 100644 --- a/frontend/src/lib/utils.tsx +++ b/frontend/src/lib/utils.tsx @@ -1420,7 +1420,7 @@ export function ensureStringIsNotBlank(s?: string | null): string | null { return typeof s === 'string' && s.trim() !== '' ? s : null } -export function isMultiSeriesFormula(formula?: string): boolean { +export function isMultiSeriesFormula(formula?: string | null): boolean { if (!formula) { return false } diff --git a/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts b/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts index 9b8a5c7d310ef..755ffb0d71795 100644 --- a/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts +++ b/frontend/src/queries/nodes/DataNode/dataNodeLogic.ts @@ -45,7 +45,7 @@ export const dataNodeLogic = kea([ connect({ values: [userLogic, ['user'], teamLogic, ['currentTeamId']], }), - props({} as DataNodeLogicProps), + props({ query: {} } as DataNodeLogicProps), key((props) => props.key), propsChanged(({ actions, props, values }, oldProps) => { if (props.query?.kind && oldProps.query?.kind && props.query.kind !== oldProps.query.kind) { @@ -96,7 +96,9 @@ export const dataNodeLogic = kea([ ) { const url = `api/projects/${values.currentTeamId}/insights/${props.cachedResults['id']}?refresh=true` const fetchResponse = await api.getResponse(url) - return await getJSONOrThrow(fetchResponse) + const data = await getJSONOrThrow(fetchResponse) + breakpoint() + return data } if (props.cachedResults && !refresh) { @@ -108,7 +110,7 @@ export const dataNodeLogic = kea([ return null } - if (Object.keys(props.query).length === 0) { + if (props.query === undefined || Object.keys(props.query).length === 0) { // no need to try and load a query before properly initialized return null } @@ -233,7 +235,7 @@ export const dataNodeLogic = kea([ // store the 'autoload toggle' state in localstorage, separately for each data node kind { persist: true, - storageKey: clsx('queries.nodes.dataNodeLogic.autoLoadToggled', props.query.kind, { + storageKey: clsx('queries.nodes.dataNodeLogic.autoLoadToggled', props.query?.kind, { action: isEventsQuery(props.query) && props.query.actionId, person: isEventsQuery(props.query) && props.query.personId, }), @@ -456,8 +458,10 @@ export const dataNodeLogic = kea([ } }, })), - afterMount(({ actions }) => { - actions.loadData() + afterMount(({ actions, props }) => { + if (Object.keys(props.query || {}).length > 0) { + actions.loadData() + } }), beforeUnmount(({ actions, values }) => { if (values.autoLoadRunning) { diff --git a/frontend/src/queries/nodes/InsightViz/ComputationTimeWithRefresh.tsx b/frontend/src/queries/nodes/InsightViz/ComputationTimeWithRefresh.tsx index 01aa4223c52e3..4f48bcc86ac4d 100644 --- a/frontend/src/queries/nodes/InsightViz/ComputationTimeWithRefresh.tsx +++ b/frontend/src/queries/nodes/InsightViz/ComputationTimeWithRefresh.tsx @@ -11,7 +11,7 @@ import { Link } from '@posthog/lemon-ui' export function ComputationTimeWithRefresh({ disableRefresh }: { disableRefresh?: boolean }): JSX.Element | null { const showRefreshOnInsight = !disableRefresh - const { lastRefresh } = useValues(dataNodeLogic) + const { lastRefresh, response } = useValues(dataNodeLogic) const { insightProps } = useValues(insightLogic) const { getInsightRefreshButtonDisabledReason } = useValues(insightDataLogic(insightProps)) @@ -19,6 +19,10 @@ export function ComputationTimeWithRefresh({ disableRefresh }: { disableRefresh? usePeriodicRerender(15000) // Re-render every 15 seconds for up-to-date `insightRefreshButtonDisabledReason` + if (!response || !response.result) { + return null + } + return (
Computed {lastRefresh ? dayjs(lastRefresh).fromNow() : 'a while ago'} diff --git a/frontend/src/queries/nodes/InsightViz/InsightDisplayConfig.tsx b/frontend/src/queries/nodes/InsightViz/InsightDisplayConfig.tsx index 03bd78cdf6199..2dcdaa776d665 100644 --- a/frontend/src/queries/nodes/InsightViz/InsightDisplayConfig.tsx +++ b/frontend/src/queries/nodes/InsightViz/InsightDisplayConfig.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, ReactNode } from 'react' -import { useActions, useValues } from 'kea' +import { useValues } from 'kea' import { insightLogic } from 'scenes/insights/insightLogic' import { insightDisplayConfigLogic } from './insightDisplayConfigLogic' @@ -22,8 +22,7 @@ interface InsightDisplayConfigProps { } export function InsightDisplayConfig({ disableTable }: InsightDisplayConfigProps): JSX.Element { - const { insightProps, filters } = useValues(insightLogic) - const { setFilters } = useActions(insightLogic) + const { insightProps } = useValues(insightLogic) const { showDateRange, disableDateRange, @@ -88,13 +87,13 @@ export function InsightDisplayConfig({ disableTable }: InsightDisplayConfigProps
{showUnit && ( - + )} {showChart && ( - + )} diff --git a/frontend/src/queries/nodes/InsightViz/TrendsFormula.tsx b/frontend/src/queries/nodes/InsightViz/TrendsFormula.tsx index 39396488b0df0..a2053afdd8aff 100644 --- a/frontend/src/queries/nodes/InsightViz/TrendsFormula.tsx +++ b/frontend/src/queries/nodes/InsightViz/TrendsFormula.tsx @@ -37,7 +37,7 @@ export function TrendsFormula({ insightProps }: EditorFilterProps): JSX.Element setValue(changedValue) }} onBlur={(e) => { - // Ignore TrendsFormulaLabel switch click to prevent conflicting setFilters calls + // Ignore TrendsFormulaLabel switch click to prevent conflicting updateInsightFilter calls // Type assertion is needed because for some React relatedTarget isn't defined as an element // in React types - and it is in reality if ( diff --git a/frontend/src/queries/nodes/InsightViz/utils.ts b/frontend/src/queries/nodes/InsightViz/utils.ts index 3d8395a3256c3..e130cfcf1e4c9 100644 --- a/frontend/src/queries/nodes/InsightViz/utils.ts +++ b/frontend/src/queries/nodes/InsightViz/utils.ts @@ -5,11 +5,13 @@ import { getEventNamesForAction } from 'lib/utils' import { isInsightQueryWithBreakdown, isInsightQueryWithSeries, + isLifecycleQuery, isStickinessQuery, isTrendsQuery, } from '~/queries/utils' import { filtersToQueryNode } from '../InsightQuery/utils/filtersToQueryNode' import equal from 'fast-deep-equal' +import { ShownAsValue } from 'lib/constants' export const getAllEventNames = (query: InsightQueryNode, allActions: ActionType[]): string[] => { const { actions, events } = seriesToActionsAndEvents((query as TrendsQuery).series || []) @@ -80,6 +82,30 @@ export const getBreakdown = (query: InsightQueryNode): BreakdownFilter | undefin } } +export const getShownAs = (query: InsightQueryNode): ShownAsValue | undefined => { + if (isLifecycleQuery(query)) { + return query.lifecycleFilter?.shown_as + } else if (isStickinessQuery(query)) { + return query.stickinessFilter?.shown_as + } else if (isTrendsQuery(query)) { + return query.trendsFilter?.shown_as + } else { + return undefined + } +} + +export const getShowValueOnSeries = (query: InsightQueryNode): boolean | undefined => { + if (isLifecycleQuery(query)) { + return query.lifecycleFilter?.show_values_on_series + } else if (isStickinessQuery(query)) { + return query.stickinessFilter?.show_values_on_series + } else if (isTrendsQuery(query)) { + return query.trendsFilter?.show_values_on_series + } else { + return undefined + } +} + export const getCachedResults = ( cachedInsight: Partial | undefined | null, query: InsightQueryNode diff --git a/frontend/src/scenes/feature-flags/featureFlagLogic.ts b/frontend/src/scenes/feature-flags/featureFlagLogic.ts index 759476c8eca2b..e2d33edc4641e 100644 --- a/frontend/src/scenes/feature-flags/featureFlagLogic.ts +++ b/frontend/src/scenes/feature-flags/featureFlagLogic.ts @@ -236,7 +236,6 @@ export const featureFlagLogic = kea([ removeVariant: (index: number) => ({ index }), editFeatureFlag: (editing: boolean) => ({ editing }), distributeVariantsEqually: true, - setFilters: (filters) => ({ filters }), loadInsightAtIndex: (index: number, filters: Partial) => ({ index, filters }), setInsightResultAtIndex: (index: number, average: number) => ({ index, average }), loadAllInsightsForFlag: true, diff --git a/frontend/src/scenes/funnels/FunnelLineGraph.tsx b/frontend/src/scenes/funnels/FunnelLineGraph.tsx index 3c3cbf9914dae..544dd942bb066 100644 --- a/frontend/src/scenes/funnels/FunnelLineGraph.tsx +++ b/frontend/src/scenes/funnels/FunnelLineGraph.tsx @@ -10,6 +10,7 @@ import { useValues } from 'kea' import { funnelDataLogic } from './funnelDataLogic' import { queryNodeToFilter } from '~/queries/nodes/InsightQuery/utils/queryNodeToFilter' import { isInsightQueryNode } from '~/queries/utils' +import { TrendsFilter } from '~/queries/schema' export function FunnelLineGraph({ inSharedMode, @@ -51,7 +52,7 @@ export function FunnelLineGraph({ return `${count}%` }, }} - filters={{ aggregation_axis_format: 'percentage' }} + trendsFilter={{ aggregation_axis_format: 'percentage' } as TrendsFilter} labelGroupType={aggregationGroupTypeIndex ?? 'people'} incompletenessOffsetFromEnd={incompletenessOffsetFromEnd} onClick={ diff --git a/frontend/src/scenes/insights/aggregationAxisFormat.ts b/frontend/src/scenes/insights/aggregationAxisFormat.ts index fd57379514c72..b7f46ebf71c9e 100644 --- a/frontend/src/scenes/insights/aggregationAxisFormat.ts +++ b/frontend/src/scenes/insights/aggregationAxisFormat.ts @@ -44,7 +44,7 @@ export const formatAggregationAxisValue = ( }` } -export const axisLabel = (chartDisplayType: ChartDisplayType | undefined): string => { +export const axisLabel = (chartDisplayType: ChartDisplayType | null | undefined): string => { switch (chartDisplayType) { case ChartDisplayType.ActionsLineGraph: case ChartDisplayType.ActionsLineGraphCumulative: diff --git a/frontend/src/scenes/insights/filters/InsightDateFilter/insightDateFilterLogic.ts b/frontend/src/scenes/insights/filters/InsightDateFilter/insightDateFilterLogic.ts index 8795a75f176f4..76c8b4da0321c 100644 --- a/frontend/src/scenes/insights/filters/InsightDateFilter/insightDateFilterLogic.ts +++ b/frontend/src/scenes/insights/filters/InsightDateFilter/insightDateFilterLogic.ts @@ -1,7 +1,6 @@ import { kea, props, key, path, connect, actions, selectors, listeners } from 'kea' import type { insightDateFilterLogicType } from './insightDateFilterLogicType' import { InsightLogicProps } from '~/types' -import { insightLogic } from 'scenes/insights/insightLogic' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' @@ -10,8 +9,8 @@ export const insightDateFilterLogic = kea([ key(keyForInsightLogicProps('new')), path((key) => ['scenes', 'insights', 'InsightDateFilter', 'insightDateFilterLogic', key]), connect((props: InsightLogicProps) => ({ - actions: [insightLogic(props), ['setFilters'], insightVizDataLogic(props), ['updateQuerySource']], - values: [insightLogic(props), ['filters']], + actions: [insightVizDataLogic(props), ['updateQuerySource']], + values: [insightVizDataLogic(props), ['dateRange']], })), actions(() => ({ setDates: (dateFrom: string | undefined | null, dateTo: string | undefined | null) => ({ @@ -21,17 +20,12 @@ export const insightDateFilterLogic = kea([ })), selectors({ dates: [ - (s) => [s.filters], - (filters) => ({ dateFrom: filters?.date_from || null, dateTo: filters?.date_to || null }), + (s) => [s.dateRange], + (dateRange) => ({ dateFrom: dateRange?.date_from || null, dateTo: dateRange?.date_to || null }), ], }), - listeners(({ actions, values }) => ({ + listeners(({ actions }) => ({ setDates: ({ dateFrom, dateTo }) => { - actions.setFilters({ - ...values.filters, - date_from: dateFrom || null, - date_to: dateTo || null, - }) actions.updateQuerySource({ dateRange: { date_from: dateFrom || null, diff --git a/frontend/src/scenes/insights/insightDataLogic.ts b/frontend/src/scenes/insights/insightDataLogic.ts index a83f01ce44e35..7a464035b0730 100644 --- a/frontend/src/scenes/insights/insightDataLogic.ts +++ b/frontend/src/scenes/insights/insightDataLogic.ts @@ -1,7 +1,7 @@ import { actions, connect, kea, key, listeners, path, props, propsChanged, reducers, selectors } from 'kea' import { FilterType, InsightLogicProps, InsightType } from '~/types' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' -import { DataNode, InsightNodeKind, InsightVizNode, Node, NodeKind } from '~/queries/schema' +import { InsightNodeKind, InsightVizNode, Node, NodeKind } from '~/queries/schema' import type { insightDataLogicType } from './insightDataLogicType' import { insightLogic } from './insightLogic' @@ -10,7 +10,7 @@ import { filtersToQueryNode } from '~/queries/nodes/InsightQuery/utils/filtersTo import { isInsightVizNode } from '~/queries/utils' import { cleanFilters } from './utils/cleanFilters' import { insightTypeToDefaultQuery, nodeKindToDefaultQuery } from '~/queries/nodes/InsightQuery/defaults' -import { dataNodeLogic } from '~/queries/nodes/DataNode/dataNodeLogic' +import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/dataNodeLogic' import { insightVizDataNodeKey } from '~/queries/nodes/InsightViz/InsightViz' import { queryExportContext } from '~/queries/query' import { objectsEqual } from 'lib/utils' @@ -37,9 +37,10 @@ export const insightDataLogic = kea([ values: [ insightLogic, ['filters', 'insight', 'savedInsight'], - // TODO: need to pass empty query here, as otherwise dataNodeLogic will throw - dataNodeLogic({ key: insightVizDataNodeKey(props), query: {} as DataNode }), + dataNodeLogic({ key: insightVizDataNodeKey(props) } as DataNodeLogicProps), [ + 'query as insightQuery', + 'response as insightData', 'dataLoading as insightDataLoading', 'responseErrorObject as insightDataError', 'getInsightRefreshButtonDisabledReason', @@ -50,9 +51,8 @@ export const insightDataLogic = kea([ actions: [ insightLogic, ['setInsight', 'loadInsightSuccess', 'saveInsight as insightLogicSaveInsight'], - // TODO: need to pass empty query here, as otherwise dataNodeLogic will throw - dataNodeLogic({ key: insightVizDataNodeKey(props), query: {} as DataNode }), - ['loadData'], + dataNodeLogic({ key: insightVizDataNodeKey(props) } as DataNodeLogicProps), + ['loadData', 'loadDataSuccess', 'loadDataFailure', 'setResponse as setInsightData'], ], logic: [insightDataTimingLogic(props)], })), @@ -130,12 +130,16 @@ export const insightDataLogic = kea([ }), listeners(({ actions, values }) => ({ - setInsight: ({ insight: { filters, query }, options: { overrideFilter } }) => { + setInsight: ({ insight: { filters, query, result }, options: { overrideFilter } }) => { if (overrideFilter && query == null) { actions.setQuery(queryFromFilters(cleanFilters(filters || {}))) } else if (query) { actions.setQuery(query) } + + if (result) { + actions.setInsightData({ ...values.insightData, result }) + } }, loadInsightSuccess: ({ insight }) => { if (!!insight.query) { diff --git a/frontend/src/scenes/insights/insightDataTimingLogic.ts b/frontend/src/scenes/insights/insightDataTimingLogic.ts index c319652f87f7d..d84ce7b8e240f 100644 --- a/frontend/src/scenes/insights/insightDataTimingLogic.ts +++ b/frontend/src/scenes/insights/insightDataTimingLogic.ts @@ -1,7 +1,6 @@ import { kea, props, key, path, connect, listeners, reducers, actions } from 'kea' -import { dataNodeLogic } from '~/queries/nodes/DataNode/dataNodeLogic' +import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/dataNodeLogic' import { insightVizDataNodeKey } from '~/queries/nodes/InsightViz/InsightViz' -import { DataNode } from '~/queries/schema' import { InsightLogicProps } from '~/types' import { keyForInsightLogicProps } from './sharedUtils' @@ -17,12 +16,11 @@ export const insightDataTimingLogic = kea([ values: [ teamLogic, ['currentTeamId'], - dataNodeLogic({ key: insightVizDataNodeKey(props), query: {} as DataNode }), + dataNodeLogic({ key: insightVizDataNodeKey(props) } as DataNodeLogicProps), ['query', 'response'], ], actions: [ - // TODO: need to pass empty query here, as otherwise dataNodeLogic will throw - dataNodeLogic({ key: insightVizDataNodeKey(props), query: {} as DataNode }), + dataNodeLogic({ key: insightVizDataNodeKey(props) } as DataNodeLogicProps), ['loadData', 'loadDataSuccess', 'loadDataFailure', 'abortQuery as loadDataCancellation'], ], })), diff --git a/frontend/src/scenes/insights/insightLogic.ts b/frontend/src/scenes/insights/insightLogic.ts index cc1fb50a46d97..51960903cdebf 100644 --- a/frontend/src/scenes/insights/insightLogic.ts +++ b/frontend/src/scenes/insights/insightLogic.ts @@ -471,16 +471,7 @@ export const insightLogic = kea([ return toLocalFilters(filters) }, ], - isSingleSeries: [ - (s) => [s.filters, s.localFilters], - (filters, localFilters): boolean => { - return ( - ((isTrendsFilter(filters) && !!filters.formula) || localFilters.length <= 1) && !filters.breakdown - ) - }, - ], intervalUnit: [(s) => [s.filters], (filters) => filters?.interval || 'day'], - timezone: [(s) => [s.insight], (insight) => insight?.timezone || 'UTC'], exporterResourceParams: [ (s) => [s.filters, s.currentTeamId, s.insight], ( diff --git a/frontend/src/scenes/insights/insightVizDataLogic.ts b/frontend/src/scenes/insights/insightVizDataLogic.ts index 190b608e11d32..b33e8fd5a7fed 100644 --- a/frontend/src/scenes/insights/insightVizDataLogic.ts +++ b/frontend/src/scenes/insights/insightVizDataLogic.ts @@ -4,7 +4,6 @@ import { ChartDisplayType, InsightLogicProps } from '~/types' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' import { BreakdownFilter, - DataNode, DateRange, InsightFilter, InsightQueryNode, @@ -37,11 +36,10 @@ import { getFormula, getInterval, getSeries, + getShownAs, + getShowValueOnSeries, } from '~/queries/nodes/InsightViz/utils' -import { dataNodeLogic } from '~/queries/nodes/DataNode/dataNodeLogic' -import { insightVizDataNodeKey } from '~/queries/nodes/InsightViz/InsightViz' -import { subscriptions } from 'kea-subscriptions' -import { displayTypesWithoutLegend } from 'lib/components/InsightLegend/utils' +import { DISPLAY_TYPES_WITHOUT_LEGEND } from 'lib/components/InsightLegend/utils' import { insightDataLogic, queryFromKind } from 'scenes/insights/insightDataLogic' import { sceneLogic } from 'scenes/sceneLogic' @@ -57,31 +55,18 @@ export const insightVizDataLogic = kea([ key(keyForInsightLogicProps('new')), path((key) => ['scenes', 'insights', 'insightVizDataLogic', key]), - connect((props: InsightLogicProps) => ({ + connect(() => ({ values: [ - insightLogic, - ['insight'], insightDataLogic, - ['query'], - // TODO: need to pass empty query here, as otherwise dataNodeLogic will throw - dataNodeLogic({ key: insightVizDataNodeKey(props), query: {} as DataNode }), - [ - 'response as insightData', - 'dataLoading as insightDataLoading', - 'responseErrorObject as insightDataError', - 'query as insightQuery', - ], + ['query', 'insightQuery', 'insightData', 'insightDataLoading', 'insightDataError'], filterTestAccountsDefaultsLogic, ['filterTestAccountsDefault'], ], actions: [ insightLogic, - ['setFilters', 'setInsight'], + ['setFilters'], insightDataLogic, - ['setQuery'], - // TODO: need to pass empty query here, as otherwise dataNodeLogic will throw - dataNodeLogic({ key: insightVizDataNodeKey(props), query: {} as DataNode }), - ['loadData', 'loadDataSuccess', 'loadDataFailure'], + ['setQuery', 'setInsightData', 'loadData', 'loadDataSuccess', 'loadDataFailure'], ], })), @@ -129,6 +114,8 @@ export const insightVizDataLogic = kea([ interval: [(s) => [s.querySource], (q) => (q ? getInterval(q) : null)], properties: [(s) => [s.querySource], (q) => (q ? q.properties : null)], samplingFactor: [(s) => [s.querySource], (q) => (q ? q.samplingFactor : null)], + shownAs: [(s) => [s.querySource], (q) => (q ? getShownAs(q) : null)], + showValueOnSeries: [(s) => [s.querySource], (q) => (q ? getShowValueOnSeries(q) : null)], insightFilter: [(s) => [s.querySource], (q) => (q ? filterForQuery(q) : null)], trendsFilter: [(s) => [s.querySource], (q) => (isTrendsQuery(q) ? q.trendsFilter : null)], @@ -168,10 +155,17 @@ export const insightVizDataLogic = kea([ (display) => !!display && NON_TIME_SERIES_DISPLAY_TYPES.includes(display), ], + isSingleSeries: [ + (s) => [s.isTrends, s.formula, s.series, s.breakdown], + (isTrends, formula, series, breakdown): boolean => { + return ((isTrends && !!formula) || (series || []).length <= 1) && !breakdown?.breakdown + }, + ], + hasLegend: [ (s) => [s.isTrends, s.isStickiness, s.display], (isTrends, isStickiness, display) => - (isTrends || isStickiness) && !!display && !displayTypesWithoutLegend.includes(display), + (isTrends || isStickiness) && !!display && !DISPLAY_TYPES_WITHOUT_LEGEND.includes(display), ], hasFormula: [(s) => [s.formula], (formula) => formula !== undefined], @@ -182,6 +176,8 @@ export const insightVizDataLogic = kea([ return insightDataError?.queryId || null }, ], + + timezone: [(s) => [s.insightData], (insightData) => insightData?.timezone || 'UTC'], }), listeners(({ actions, values }) => ({ @@ -262,27 +258,4 @@ export const insightVizDataLogic = kea([ actions.setTimedOutQueryId(null) }, })), - subscriptions(({ values, actions }) => ({ - /** - * This subscription updates the insight for all visualizations - * that haven't been refactored to use the data exploration yet. - */ - insightData: (insightData: Record | null) => { - if (insightData === null) { - return - } - if (isInsightVizNode(values.query)) { - const updatedInsight = { - ...values.insight, - result: insightData?.result, - next: insightData?.next, - } - - updatedInsight.filters = queryNodeToFilter(values.query.source) - updatedInsight.query = undefined - - actions.setInsight(updatedInsight, {}) - } - }, - })), ]) diff --git a/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.stories.tsx b/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.stories.tsx index 2cbfa8156513f..cbd7192b5ddf2 100644 --- a/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.stories.tsx +++ b/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.stories.tsx @@ -3,9 +3,15 @@ import { BindLogic } from 'kea' import { ComponentMeta, ComponentStory } from '@storybook/react' import { insightLogic } from 'scenes/insights/insightLogic' -import { InsightsTable } from './InsightsTable' +import { insightVizDataNodeKey } from '~/queries/nodes/InsightViz/InsightViz' +import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/dataNodeLogic' +import { filtersToQueryNode } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode' + import { BaseMathType, InsightLogicProps } from '~/types' +import { InsightsTable } from './InsightsTable' +import { getCachedResults } from '~/queries/nodes/InsightViz/utils' + export default { title: 'Insights/InsightsTable', component: InsightsTable, @@ -18,19 +24,24 @@ const Template: ComponentStory = (props, { parameters }) = // eslint-disable-next-line @typescript-eslint/no-var-requires const insight = require('../../__mocks__/trendsLineBreakdown.json') + const filters = { ...insight.filters, ...parameters.mergeFilters } + const cachedInsight = { ...insight, short_id: dashboardItemId, filters } + + const insightProps = { dashboardItemId, doNotLoad: true, cachedInsight } as InsightLogicProps + const querySource = filtersToQueryNode(filters) - const insightProps = { - dashboardItemId, - cachedInsight: { - ...insight, - short_id: dashboardItemId, - filters: { ...insight.filters, ...parameters.mergeFilters }, - }, - } as InsightLogicProps + const dataNodeLogicProps: DataNodeLogicProps = { + query: querySource, + key: insightVizDataNodeKey(insightProps), + cachedResults: getCachedResults(insightProps.cachedInsight, querySource), + doNotLoad: insightProps.doNotLoad, + } return ( - + + + ) } diff --git a/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx b/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx index 5139a088eb172..770b7f2ae87b3 100644 --- a/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx +++ b/frontend/src/scenes/insights/views/InsightsTable/InsightsTable.tsx @@ -1,5 +1,4 @@ import { useActions, useValues } from 'kea' -import { trendsLogic } from 'scenes/trends/trendsLogic' import { cohortsModel } from '~/models/cohortsModel' import { ChartDisplayType, ItemMode } from '~/types' import { CalcColumnState } from './insightsTableLogic' @@ -21,7 +20,7 @@ import { WorldMapColumnTitle, WorldMapColumnItem } from './columns/WorldMapColum import { AggregationColumnItem, AggregationColumnTitle } from './columns/AggregationColumn' import { ValueColumnItem, ValueColumnTitle } from './columns/ValueColumn' import { AggregationType, insightsTableDataLogic } from './insightsTableDataLogic' -import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' +import { trendsDataLogic } from 'scenes/trends/trendsDataLogic' export interface InsightsTableProps { /** Whether this is just a legend instead of standalone insight viz. Default: false. */ @@ -45,11 +44,21 @@ export function InsightsTable({ canCheckUncheckSeries = true, isMainInsightView = false, }: InsightsTableProps): JSX.Element { - const { insightProps, isInDashboardContext, insight, isSingleSeries } = useValues(insightLogic) const { insightMode } = useValues(insightSceneLogic) - const { isNonTimeSeriesDisplay, compare, isTrends, display, interval, breakdown, trendsFilter } = useValues( - insightVizDataLogic(insightProps) - ) + const { insightProps, isInDashboardContext, insight, hiddenLegendKeys } = useValues(insightLogic) + const { toggleVisibility } = useActions(insightLogic) + const { + insightDataLoading, + indexedResults, + isNonTimeSeriesDisplay, + compare, + isTrends, + display, + interval, + breakdown, + trendsFilter, + isSingleSeries, + } = useValues(trendsDataLogic(insightProps)) const { aggregation, allowAggregation } = useValues(insightsTableDataLogic(insightProps)) const { setAggregationType } = useActions(insightsTableDataLogic(insightProps)) @@ -67,9 +76,6 @@ export function InsightsTable({ const { cohorts } = useValues(cohortsModel) const { formatPropertyValueForDisplay } = useValues(propertyDefinitionsModel) - const { indexedResults, hiddenLegendKeys, resultsLoading } = useValues(trendsLogic(insightProps)) - const { toggleVisibility } = useActions(trendsLogic(insightProps)) - // Build up columns to include. Order matters. const columns: LemonTableColumn[] = [] @@ -216,7 +222,7 @@ export function InsightsTable({ columns={columns} rowKey="id" pagination={{ pageSize: 100, hideOnSinglePage: true }} - loading={resultsLoading} + loading={insightDataLoading} emptyState="No insight results" data-attr="insights-table-graph" className="insights-table" diff --git a/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx b/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx index cd7806d248557..b56fc869f68e8 100644 --- a/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx +++ b/frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx @@ -21,7 +21,7 @@ import 'chartjs-adapter-dayjs-3' import { areObjectValuesEmpty, lightenDarkenColor, hexToRGBA } from '~/lib/utils' import { getBarColorFromStatus, getGraphColors, getSeriesColor } from 'lib/colors' import { AnnotationsOverlay } from 'lib/components/AnnotationsOverlay' -import { GraphDataset, GraphPoint, GraphPointPayload, GraphType, InsightType, TrendsFilterType } from '~/types' +import { GraphDataset, GraphPoint, GraphPointPayload, GraphType } from '~/types' import { InsightTooltip } from 'scenes/insights/InsightTooltip/InsightTooltip' import { lineGraphLogic } from 'scenes/insights/views/LineGraph/lineGraphLogic' import { TooltipConfig } from 'scenes/insights/InsightTooltip/insightTooltipUtils' @@ -33,25 +33,8 @@ import { useResizeObserver } from 'lib/hooks/useResizeObserver' import { PieChart } from 'scenes/insights/views/LineGraph/PieChart' import { themeLogic } from '~/layout/navigation-3000/themeLogic' import { SeriesLetter } from 'lib/components/SeriesGlyph' - -export interface LineGraphProps { - datasets: GraphDataset[] - hiddenLegendKeys?: Record - labels: string[] - type: GraphType - isInProgress?: boolean - onClick?: (payload: GraphPointPayload) => void - ['data-attr']: string - inSharedMode?: boolean - showPersonsModal?: boolean - tooltip?: TooltipConfig - isCompare?: boolean - inCardView?: boolean - isArea?: boolean - incompletenessOffsetFromEnd?: number // Number of data points at end of dataset to replace with a dotted line. Only used in line graphs. - labelGroupType: number | 'people' | 'none' - filters?: Partial -} +import { TrendsFilter } from '~/queries/schema' +import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' export function ensureTooltipElement(): HTMLElement { let tooltipEl = document.getElementById('InsightTooltipWrapper') @@ -139,14 +122,6 @@ export function onChartClick( }) } -export const LineGraph = (props: LineGraphProps): JSX.Element => { - return ( - - {props.type === GraphType.Pie ? : } - - ) -} - export function onChartHover( event: ChartEvent, chart: Chart, @@ -217,6 +192,35 @@ function createPinstripePattern(color: string): CanvasPattern { return pattern } +export interface LineGraphProps { + datasets: GraphDataset[] + hiddenLegendKeys?: Record + labels: string[] + type: GraphType + isInProgress?: boolean + onClick?: (payload: GraphPointPayload) => void + ['data-attr']: string + inSharedMode?: boolean + showPersonsModal?: boolean + tooltip?: TooltipConfig + inCardView?: boolean + isArea?: boolean + incompletenessOffsetFromEnd?: number // Number of data points at end of dataset to replace with a dotted line. Only used in line graphs. + labelGroupType: number | 'people' | 'none' + trendsFilter?: TrendsFilter | null + formula?: string | null + compare?: boolean | null + showValueOnSeries?: boolean | null +} + +export const LineGraph = (props: LineGraphProps): JSX.Element => { + return ( + + {props.type === GraphType.Pie ? : } + + ) +} + export function LineGraph_({ datasets: _datasets, hiddenLegendKeys, @@ -226,21 +230,26 @@ export function LineGraph_({ onClick, ['data-attr']: dataAttr, showPersonsModal = true, - isCompare = false, + compare = false, inCardView, isArea = false, incompletenessOffsetFromEnd = -1, tooltip: tooltipConfig, labelGroupType, - filters, + trendsFilter, + formula, + showValueOnSeries, }: LineGraphProps): JSX.Element { let datasets = _datasets - const { createTooltipData } = useValues(lineGraphLogic) - const { insight, timezone } = useValues(insightLogic) const { aggregationLabel } = useValues(groupsModel) const { isDarkModeOn } = useValues(themeLogic) + const { insightProps, insight } = useValues(insightLogic) + const { timezone, isTrends } = useValues(insightVizDataLogic(insightProps)) + + const { createTooltipData } = useValues(lineGraphLogic) + const canvasRef = useRef(null) const [myLineChart, setMyLineChart] = useState>() @@ -248,7 +257,6 @@ export function LineGraph_({ const { width: chartWidth, height: chartHeight } = useResizeObserver({ ref: canvasRef }) const colors = getGraphColors(isDarkModeOn) - const insightType = insight.filters?.insight const isHorizontal = type === GraphType.HorizontalBar const isPie = type === GraphType.Pie if (isPie) { @@ -257,7 +265,7 @@ export function LineGraph_({ const isBar = [GraphType.Bar, GraphType.HorizontalBar, GraphType.Histogram].includes(type) const isBackgroundBasedGraphType = [GraphType.Bar, GraphType.HorizontalBar].includes(type) - const showAnnotations = (!insightType || insightType === InsightType.TRENDS) && !isHorizontal + const showAnnotations = isTrends && !isHorizontal const shouldAutoResize = isHorizontal && !inCardView // Remove tooltip element on unmount @@ -271,7 +279,7 @@ export function LineGraph_({ function processDataset(dataset: ChartDataset): ChartDataset { const mainColor = dataset?.status ? getBarColorFromStatus(dataset.status) - : getSeriesColor(dataset.id, isCompare && !isArea) + : getSeriesColor(dataset.id, compare && !isArea) const hoverColor = dataset?.status ? getBarColorFromStatus(dataset.status, true) : mainColor const areaBackgroundColor = hexToRGBA(mainColor, 0.5) const areaIncompletePattern = createPinstripePattern(areaBackgroundColor) @@ -374,11 +382,9 @@ export function LineGraph_({ }, display: (context) => { const datum = context.dataset.data[context.dataIndex] - return filters?.show_values_on_series === true && typeof datum === 'number' && datum !== 0 - ? 'auto' - : false + return showValueOnSeries === true && typeof datum === 'number' && datum !== 0 ? 'auto' : false }, - formatter: (value: number) => formatAggregationAxisValue(filters, value), + formatter: (value: number) => formatAggregationAxisValue(trendsFilter, value), borderWidth: 2, borderRadius: 4, borderColor: 'white', @@ -429,7 +435,7 @@ export function LineGraph_({ datum.breakdown_value !== undefined && !!datum.breakdown_value return (
- {!filters?.formula && ( + {!formula && ( formatAggregationAxisValue(filters, value)) + ((value: number): string => formatAggregationAxisValue(trendsFilter, value)) } - entitiesAsColumnsOverride={filters?.formula ? false : undefined} + entitiesAsColumnsOverride={formula ? false : undefined} hideInspectActorsSection={!onClick || !showPersonsModal} groupTypeLabel={ labelGroupType === 'people' @@ -528,7 +534,7 @@ export function LineGraph_({ precision, color: colors.axisLabel as string, callback: (value) => { - return formatAggregationAxisValue(filters, value) + return formatAggregationAxisValue(trendsFilter, value) }, }, }, @@ -554,7 +560,7 @@ export function LineGraph_({ precision, ...tickOptions, callback: (value) => { - return formatAggregationAxisValue(filters, value) + return formatAggregationAxisValue(trendsFilter, value) }, }, grid: { @@ -571,7 +577,7 @@ export function LineGraph_({ ...tickOptions, precision, callback: (value) => { - return formatAggregationAxisValue(filters, value) + return formatAggregationAxisValue(trendsFilter, value) }, }, }, diff --git a/frontend/src/scenes/insights/views/LineGraph/PieChart.tsx b/frontend/src/scenes/insights/views/LineGraph/PieChart.tsx index d41f6913ccb11..12a316f2ea423 100644 --- a/frontend/src/scenes/insights/views/LineGraph/PieChart.tsx +++ b/frontend/src/scenes/insights/views/LineGraph/PieChart.tsx @@ -53,7 +53,9 @@ export function PieChart({ type, onClick, ['data-attr']: dataAttr, - filters, + trendsFilter, + formula, + showValueOnSeries, tooltip: tooltipConfig, showPersonsModal = true, labelGroupType, @@ -130,7 +132,7 @@ export function PieChart({ 0 ) as number const percentage = ((context.dataset.data[context.dataIndex] as number) / total) * 100 - return filters?.show_values_on_series !== false && // show if true or unset + return showValueOnSeries !== false && // show if true or unset context.dataset.data.length > 1 && percentage > 5 ? 'auto' @@ -143,7 +145,7 @@ export function PieChart({ const paddingX = value < 10 ? 5 : 4 return { top: paddingY, bottom: paddingY, left: paddingX, right: paddingX } }, - formatter: (value: number) => formatAggregationAxisValue(filters, value), + formatter: (value: number) => formatAggregationAxisValue(trendsFilter, value), font: { weight: 500, }, @@ -167,7 +169,7 @@ export function PieChart({ const tooltipEl = ensureTooltipElement() if (tooltip.opacity === 0) { // remove highlight from the legend - if (filters?.show_legend) { + if (trendsFilter?.show_legend) { highlightSeries(null) } tooltipEl.style.opacity = '0' @@ -200,7 +202,7 @@ export function PieChart({ datum.breakdown_value !== undefined && !!datum.breakdown_value return (
- {!filters?.formula && ( + {!formula && ( )}
- {hasBreakdown && !filters?.formula && datum.breakdown_value} + {hasBreakdown && !formula && datum.breakdown_value} {value}
@@ -222,7 +224,7 @@ export function PieChart({ ((value / total) * 100).toFixed(1) ) return `${formatAggregationAxisValue( - filters, + trendsFilter, value )} (${percentageLabel}%)` }) diff --git a/frontend/src/scenes/insights/views/Trends/FunnelsCue.tsx b/frontend/src/scenes/insights/views/Trends/FunnelsCue.tsx index 354973f632015..263a633226135 100644 --- a/frontend/src/scenes/insights/views/Trends/FunnelsCue.tsx +++ b/frontend/src/scenes/insights/views/Trends/FunnelsCue.tsx @@ -1,14 +1,12 @@ import { useActions, useValues } from 'kea' import { funnelsCueLogic } from 'scenes/insights/views/Trends/funnelsCueLogic' import { insightLogic } from 'scenes/insights/insightLogic' -import { urls } from 'scenes/urls' -import { InsightType } from '~/types' import { LemonBanner } from 'lib/lemon-ui/LemonBanner' export function FunnelsCue(): JSX.Element | null { const { insightProps } = useValues(insightLogic) - const { optOut } = useActions(funnelsCueLogic(insightProps)) const { shown } = useValues(funnelsCueLogic(insightProps)) + const { optOut, displayAsFunnel } = useActions(funnelsCueLogic(insightProps)) if (!shown) { return null @@ -18,7 +16,7 @@ export function FunnelsCue(): JSX.Element | null { ([ props({} as InsightLogicProps), key(keyForInsightLogicProps('new')), path((key) => ['scenes', 'insights', 'InsightTabs', 'TrendTab', 'FunnelsCue', key]), connect((props: InsightLogicProps) => ({ - values: [insightLogic(props), ['filters', 'isFirstLoad'], featureFlagLogic, ['featureFlags']], - actions: [insightLogic(props), ['setFilters'], featureFlagLogic, ['setFeatureFlags']], + values: [ + insightLogic(props), + ['isFirstLoad'], + insightVizDataLogic(props), + ['query'], + featureFlagLogic, + ['featureFlags'], + ], + actions: [insightVizDataLogic(props), ['setQuery'], featureFlagLogic, ['setFeatureFlags']], })), actions({ optOut: (userOptedOut: boolean) => ({ userOptedOut }), setShouldShow: (show: boolean) => ({ show }), setPermanentOptOut: true, + displayAsFunnel: true, }), reducers({ _shouldShow: [ @@ -41,12 +52,16 @@ export const funnelsCueLogic = kea([ // funnels_cue_3701_opt_out -> will add the user to a FF that will permanently exclude the user actions.setPermanentOptOut() }, - setFilters: async ({ filters }) => { - const step_count = (filters.events?.length ?? 0) + (filters.actions?.length ?? 0) - if (!values.isFirstLoad && filters.insight === InsightType.TRENDS && step_count >= 3) { + setQuery: ({ query }) => { + if (!isInsightVizNode(query)) { + return + } + + if (!values.isFirstLoad && isTrendsQuery(query?.source) && (query.source.series || []).length >= 3) { actions.setShouldShow(true) - !values.permanentOptOut && posthog.capture('funnel cue 7301 - shown', { step_count }) - } else if (values.shown && filters.insight === InsightType.FUNNELS) { + !values.permanentOptOut && + posthog.capture('funnel cue 7301 - shown', { step_count: query.source.series.length }) + } else if (values.shown && isFunnelsQuery(query?.source)) { actions.optOut(false) } else { actions.setShouldShow(false) @@ -57,6 +72,15 @@ export const funnelsCueLogic = kea([ actions.setPermanentOptOut() } }, + displayAsFunnel: () => { + if (!isInsightVizNode(values.query) || !isTrendsQuery(values.query?.source)) { + return + } + + const query = JSON.parse(JSON.stringify(values.query)) as InsightVizNode + query.source.kind = NodeKind.FunnelsQuery + actions.setQuery(query) + }, })), selectors({ shown: [ diff --git a/frontend/src/scenes/retention/RetentionLineGraph.tsx b/frontend/src/scenes/retention/RetentionLineGraph.tsx index d100e6b208a65..c241d7cefc2da 100644 --- a/frontend/src/scenes/retention/RetentionLineGraph.tsx +++ b/frontend/src/scenes/retention/RetentionLineGraph.tsx @@ -8,6 +8,7 @@ import { GraphType, GraphDataset } from '~/types' import { roundToDecimal } from 'lib/utils' import { LineGraph } from '../insights/views/LineGraph/LineGraph' import { InsightEmptyState } from '../insights/EmptyStates' +import { TrendsFilter } from '~/queries/schema' interface RetentionLineGraphProps { inSharedMode?: boolean @@ -34,7 +35,7 @@ export function RetentionLineGraph({ inSharedMode = false }: RetentionLineGraphP inSharedMode={!!inSharedMode} showPersonsModal={false} labelGroupType={aggregationGroupTypeIndex} - filters={{ aggregation_axis_format: 'percentage' }} + trendsFilter={{ aggregation_axis_format: 'percentage' } as TrendsFilter} tooltip={{ rowCutoff: 11, // 11 time units is hardcoded into retention insights renderSeries: function _renderCohortPrefix(value) { diff --git a/frontend/src/scenes/retention/retentionPeopleLogic.ts b/frontend/src/scenes/retention/retentionPeopleLogic.ts index 06b2fae8c10e5..886795974a813 100644 --- a/frontend/src/scenes/retention/retentionPeopleLogic.ts +++ b/frontend/src/scenes/retention/retentionPeopleLogic.ts @@ -52,7 +52,7 @@ export const retentionPeopleLogic = kea({ ], }, listeners: ({ actions, values }) => ({ - loadDataSuccess: async () => { + loadDataSuccess: () => { // clear people when changing the insight filters actions.clearPeople() }, diff --git a/frontend/src/scenes/trends/Trends.tsx b/frontend/src/scenes/trends/Trends.tsx index 2ec2663474955..b48af11f1ede3 100644 --- a/frontend/src/scenes/trends/Trends.tsx +++ b/frontend/src/scenes/trends/Trends.tsx @@ -1,6 +1,5 @@ import { useActions, useValues } from 'kea' import { ActionsPie, ActionsLineGraph, ActionsHorizontalBar } from './viz' -import { trendsLogic } from './trendsLogic' import { ChartDisplayType, InsightType, ItemMode } from '~/types' import { InsightsTable } from 'scenes/insights/views/InsightsTable/InsightsTable' import { insightLogic } from 'scenes/insights/insightLogic' @@ -8,7 +7,7 @@ import { insightSceneLogic } from 'scenes/insights/insightSceneLogic' import { WorldMap } from 'scenes/insights/views/WorldMap' import { BoldNumber } from 'scenes/insights/views/BoldNumber' import { LemonButton } from '@posthog/lemon-ui' -import { isStickinessFilter, isTrendsFilter } from 'scenes/insights/sharedUtils' +import { trendsDataLogic } from './trendsDataLogic' interface Props { view: InsightType @@ -17,9 +16,11 @@ interface Props { export function TrendInsight({ view }: Props): JSX.Element { const { insightMode } = useValues(insightSceneLogic) const { insightProps } = useValues(insightLogic) - const { filters: _filters, loadMoreBreakdownUrl, breakdownValuesLoading } = useValues(trendsLogic(insightProps)) - const { loadMoreBreakdownValues } = useActions(trendsLogic(insightProps)) - const display = isTrendsFilter(_filters) || isStickinessFilter(_filters) ? _filters.display : null + + const { display, series, breakdown, loadMoreBreakdownUrl, breakdownValuesLoading } = useValues( + trendsDataLogic(insightProps) + ) + const { loadMoreBreakdownValues } = useActions(trendsDataLogic(insightProps)) const renderViz = (): JSX.Element | undefined => { if ( @@ -58,7 +59,7 @@ export function TrendInsight({ view }: Props): JSX.Element { return ( <> - {(_filters.actions || _filters.events) && ( + {series && (
)} - {_filters.breakdown && loadMoreBreakdownUrl && ( + {breakdown && loadMoreBreakdownUrl && (
For readability, not all breakdown values are displayed. Click below to load them. diff --git a/frontend/src/scenes/trends/trendsDataLogic.test.ts b/frontend/src/scenes/trends/trendsDataLogic.test.ts index 7b548d2d2c668..5da842ecedb29 100644 --- a/frontend/src/scenes/trends/trendsDataLogic.test.ts +++ b/frontend/src/scenes/trends/trendsDataLogic.test.ts @@ -38,13 +38,6 @@ describe('trendsDataLogic', () => { describe('based on insightDataLogic', () => { describe('results', () => { - it.skip('with non-trends insight', async () => { - await expectLogic(logic).toMatchValues({ - insight: expect.objectContaining({ filters: {} }), - results: [], - }) - }) - it('for standard trend', async () => { const insight: Partial = { result: trendResult.result, @@ -125,25 +118,19 @@ describe('trendsDataLogic', () => { }).toMatchValues({ indexedResults: [ expect.objectContaining({ - // count: 35346.0, + count: 35346.0, status: 'new', id: 0, seriesIndex: 0, }), expect.objectContaining({ - // count: -50255.0, + count: -50255.0, status: 'dormant', id: 1, seriesIndex: 1, }), - // expect.objectContaining({ - // count: 9814.0, - // status: 'returning', - // id: 0, - // seriesIndex: 2, - // }), expect.objectContaining({ - // count: 11612.0, + count: 11612.0, status: 'resurrecting', id: 2, seriesIndex: 3, diff --git a/frontend/src/scenes/trends/trendsDataLogic.ts b/frontend/src/scenes/trends/trendsDataLogic.ts index bd560a5270f7e..aa86128d99dd1 100644 --- a/frontend/src/scenes/trends/trendsDataLogic.ts +++ b/frontend/src/scenes/trends/trendsDataLogic.ts @@ -1,10 +1,12 @@ -import { kea, props, key, path, connect, selectors } from 'kea' +import { kea, props, key, path, connect, selectors, actions, reducers, listeners } from 'kea' import { ChartDisplayType, InsightLogicProps, LifecycleToggle, TrendAPIResponse, TrendResult } from '~/types' import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils' +import api from 'lib/api' import type { trendsDataLogicType } from './trendsDataLogicType' import { IndexedTrendResult } from './types' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' +import { dayjs } from 'lib/dayjs' export const trendsDataLogic = kea([ props({} as InsightLogicProps), @@ -12,9 +14,46 @@ export const trendsDataLogic = kea([ path((key) => ['scenes', 'trends', 'trendsDataLogic', key]), connect((props: InsightLogicProps) => ({ - values: [insightVizDataLogic(props), ['insightData', 'display', 'lifecycleFilter']], + values: [ + insightVizDataLogic(props), + [ + 'insightData', + 'insightDataLoading', + 'series', + 'formula', + 'display', + 'compare', + 'interval', + 'breakdown', + 'shownAs', + 'showValueOnSeries', + 'trendsFilter', + 'lifecycleFilter', + 'isTrends', + 'isLifecycle', + 'isStickiness', + 'isNonTimeSeriesDisplay', + 'isSingleSeries', + 'hasLegend', + ], + ], + actions: [insightVizDataLogic(props), ['setInsightData']], })), + actions({ + loadMoreBreakdownValues: true, + setBreakdownValuesLoading: (loading: boolean) => ({ loading }), + }), + + reducers({ + breakdownValuesLoading: [ + false, + { + setBreakdownValuesLoading: (_, { loading }) => loading, + }, + ], + }), + selectors({ results: [ (s) => [s.insightData], @@ -27,6 +66,13 @@ export const trendsDataLogic = kea([ }, ], + loadMoreBreakdownUrl: [ + (s) => [s.insightData, s.isTrends], + (insightData, isTrends) => { + return isTrends ? insightData?.next : null + }, + ], + indexedResults: [ (s) => [s.results, s.display, s.lifecycleFilter], (results, display, lifecycleFilter): IndexedTrendResult[] => { @@ -45,5 +91,53 @@ export const trendsDataLogic = kea([ return indexedResults.map((result, index) => ({ ...result, id: index })) }, ], + + labelGroupType: [ + (s) => [s.series], + (series): 'people' | 'none' | number => { + // Find the commonly shared aggregation group index if there is one. + const firstAggregationGroupTypeIndex = series?.[0]?.math_group_type_index + return series?.every((eOrA) => eOrA?.math_group_type_index === firstAggregationGroupTypeIndex) + ? firstAggregationGroupTypeIndex ?? 'people' // if undefined, will resolve to 'people' label + : 'none' // mixed group types + }, + ], + + incompletenessOffsetFromEnd: [ + (s) => [s.results, s.interval], + (results, interval) => { + // Returns negative number of points to paint over starting from end of array + if (results[0]?.days === undefined) { + return 0 + } + const startDate = dayjs().startOf(interval ?? 'd') + const startIndex = results[0].days.findIndex((day: string) => dayjs(day) >= startDate) + + if (startIndex !== undefined && startIndex !== -1) { + return startIndex - results[0].days.length + } else { + return 0 + } + }, + ], }), + + listeners(({ actions, values }) => ({ + loadMoreBreakdownValues: async () => { + if (!values.loadMoreBreakdownUrl) { + return + } + actions.setBreakdownValuesLoading(true) + + const response = await api.get(values.loadMoreBreakdownUrl) + + actions.setInsightData({ + ...values.insightData, + result: [...values.insightData?.result, ...(response.result ? response.result : [])], + next: response.next, + }) + + actions.setBreakdownValuesLoading(false) + }, + })), ]) diff --git a/frontend/src/scenes/trends/trendsLogic.test.ts b/frontend/src/scenes/trends/trendsLogic.test.ts deleted file mode 100644 index ec234fa0a4fa5..0000000000000 --- a/frontend/src/scenes/trends/trendsLogic.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { expectLogic } from 'kea-test-utils' -import { trendsLogic } from 'scenes/trends/trendsLogic' -import { InsightShortId } from '~/types' -import { insightLogic } from 'scenes/insights/insightLogic' -import { useMocks } from '~/mocks/jest' -import { initKeaTests } from '~/test/init' - -const Insight123 = '123' as InsightShortId - -describe('trendsLogic', () => { - let logic: ReturnType - - beforeEach(() => { - useMocks({ - get: { - '/api/projects/:team/insights': { results: ['result from api'] }, - '/api/projects/:team/insights/123': { result: ['result from api'] }, - '/api/projects/:team/insights/trend/': { result: ['result from api'] }, - }, - }) - initKeaTests() - }) - - describe('syncs with insightLogic', () => { - const props = { dashboardItemId: Insight123 } - beforeEach(() => { - logic = trendsLogic(props) - logic.mount() - }) - - it('setFilters calls insightLogic.setFilters', async () => { - await expectLogic(logic, () => { - logic.actions.setFilters({ events: [{ id: 42 }] }) - }) - .toDispatchActions([ - (action) => - action.type === insightLogic(props).actionTypes.setFilters && - action.payload.filters?.events?.[0]?.id === 42, - ]) - .toMatchValues(logic, { - filters: expect.objectContaining({ - events: [{ id: 42 }], - }), - }) - .toMatchValues(insightLogic(props), { - filters: expect.objectContaining({ - events: [{ id: 42 }], - }), - }) - }) - - it('insightLogic.setFilters updates filters', async () => { - await expectLogic(logic, () => { - insightLogic(props).actions.setFilters({ events: [{ id: 42 }] }) - }) - .toMatchValues(logic, { - filters: expect.objectContaining({ - events: [{ id: 42 }], - }), - }) - .toMatchValues(insightLogic(props), { - filters: expect.objectContaining({ - events: [{ id: 42 }], - }), - }) - }) - }) -}) diff --git a/frontend/src/scenes/trends/trendsLogic.ts b/frontend/src/scenes/trends/trendsLogic.ts deleted file mode 100644 index bc2bd69b2026c..0000000000000 --- a/frontend/src/scenes/trends/trendsLogic.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { actions, connect, kea, key, listeners, path, props, reducers, selectors } from 'kea' -import { dayjs } from 'lib/dayjs' -import api from 'lib/api' -import { insightLogic } from '../insights/insightLogic' -import { - InsightLogicProps, - TrendResult, - ActionFilter, - ChartDisplayType, - TrendsFilterType, - LifecycleFilterType, - StickinessFilterType, - LifecycleToggle, -} from '~/types' -import type { trendsLogicType } from './trendsLogicType' -import { IndexedTrendResult } from 'scenes/trends/types' -import { - isFilterWithDisplay, - isLifecycleFilter, - isStickinessFilter, - isTrendsInsight, - keyForInsightLogicProps, -} from 'scenes/insights/sharedUtils' -import { Noun, groupsModel } from '~/models/groupsModel' -import { isTrendsFilter } from 'scenes/insights/sharedUtils' - -export const trendsLogic = kea([ - props({} as InsightLogicProps), - key(keyForInsightLogicProps('all_trends')), - path((key) => ['scenes', 'trends', 'trendsLogic', key]), - - connect((props: InsightLogicProps) => ({ - values: [ - insightLogic(props), - ['filters as inflightFilters', 'insight', 'insightLoading', 'hiddenLegendKeys', 'localFilters'], - groupsModel, - ['aggregationLabel'], - ], - actions: [insightLogic(props), ['toggleVisibility']], - })), - - actions(() => ({ - setFilters: (filters: Partial, mergeFilters = true) => ({ filters, mergeFilters }), - setDisplay: (display) => ({ display }), - loadMoreBreakdownValues: true, - setBreakdownValuesLoading: (loading: boolean) => ({ loading }), - toggleLifecycle: (lifecycleName: LifecycleToggle) => ({ lifecycleName }), - setTargetAction: (action: ActionFilter) => ({ action }), - setIsFormulaOn: (enabled: boolean) => ({ enabled }), - })), - - reducers({ - targetAction: [ - {} as ActionFilter, - { - setTargetAction: (_, { action }) => action ?? {}, - }, - ], - breakdownValuesLoading: [ - false, - { - setBreakdownValuesLoading: (_, { loading }) => loading, - }, - ], - }), - - selectors({ - filters: [ - (s) => [s.inflightFilters], - ( - inflightFilters - ): Partial | Partial | Partial => - inflightFilters && - (isTrendsFilter(inflightFilters) || - isStickinessFilter(inflightFilters) || - isLifecycleFilter(inflightFilters)) - ? inflightFilters - : {}, - ], - loadedFilters: [ - (s) => [s.insight], - ({ filters }): Partial => - filters && (isFilterWithDisplay(filters) || isLifecycleFilter(filters)) ? filters : {}, - ], - results: [ - (s) => [s.insight], - ({ filters, result }): TrendResult[] => - isTrendsInsight(filters?.insight) && Array.isArray(result) ? result : [], - ], - loadMoreBreakdownUrl: [ - (s) => [s.insight], - ({ filters, next }) => (isTrendsInsight(filters?.insight) ? next : null), - ], - resultsLoading: [(s) => [s.insightLoading], (insightLoading) => insightLoading], - numberOfSeries: [ - (selectors) => [selectors.filters], - (filters): number => (filters.events?.length || 0) + (filters.actions?.length || 0), - ], - indexedResults: [ - (s) => [s.filters, s.results, s.toggledLifecycles], - (filters, _results, toggledLifecycles): IndexedTrendResult[] => { - let results = _results || [] - results = results.map((result, index) => ({ ...result, seriesIndex: index })) - if ( - isFilterWithDisplay(filters) && - (filters.display === ChartDisplayType.ActionsBarValue || - filters.display === ChartDisplayType.ActionsPie) - ) { - results.sort((a, b) => b.aggregated_value - a.aggregated_value) - } else if (isLifecycleFilter(filters)) { - results = results.filter((result) => toggledLifecycles.includes(String(result.status))) - } - return results.map((result, index) => ({ ...result, id: index } as IndexedTrendResult)) - }, - ], - aggregationTargetLabel: [ - (s) => [s.aggregationLabel, s.targetAction], - (aggregationLabel, targetAction): Noun => { - return aggregationLabel(targetAction.math_group_type_index) - }, - ], - incompletenessOffsetFromEnd: [ - (s) => [s.filters, s.insight], - (filters, insight) => { - // Returns negative number of points to paint over starting from end of array - if (insight?.result?.[0]?.days === undefined) { - return 0 - } - const startDate = dayjs().startOf(filters.interval ?? 'd') - const startIndex = insight.result[0].days.findIndex((day: string) => dayjs(day) >= startDate) - - if (startIndex !== undefined && startIndex !== -1) { - return startIndex - insight.result[0].days.length - } else { - return 0 - } - }, - ], - labelGroupType: [ - (s) => [s.filters], - (filters): 'people' | 'none' | number => { - // Find the commonly shared aggregation group index if there is one. - const eventsAndActions = [...(filters.events ?? []), ...(filters.actions ?? [])] - const firstAggregationGroupTypeIndex = eventsAndActions?.[0]?.math_group_type_index - return eventsAndActions.every((eOrA) => eOrA?.math_group_type_index === firstAggregationGroupTypeIndex) - ? firstAggregationGroupTypeIndex ?? 'people' // if undefined, will resolve to 'people' label - : 'none' // mixed group types - }, - ], - toggledLifecycles: [ - (s) => [s.filters, s.loadedFilters], - (inflightFilters, loadedFilters): string[] => { - const defaultToggleState = ['new', 'resurrecting', 'returning', 'dormant'] - if (isLifecycleFilter(inflightFilters)) { - return inflightFilters.toggledLifecycles || defaultToggleState - } else if (isLifecycleFilter(loadedFilters)) { - return (loadedFilters as Partial).toggledLifecycles || defaultToggleState - } else { - return defaultToggleState - } - }, - ], - }), - - listeners(({ actions, values, props }) => ({ - setFilters: async ({ filters, mergeFilters }) => { - insightLogic(props).actions.setFilters(mergeFilters ? { ...values.filters, ...filters } : filters) - }, - setDisplay: async ({ display }) => { - actions.setFilters({ display }, true) - }, - loadMoreBreakdownValues: async () => { - if (!values.loadMoreBreakdownUrl) { - return - } - actions.setBreakdownValuesLoading(true) - - const { filters } = values - const response = await api.get(values.loadMoreBreakdownUrl) - insightLogic(props).actions.setInsight( - { - ...values.insight, - result: [...values.results, ...(response.result ? response.result : [])], - filters: filters, - next: response.next, - }, - {} - ) - actions.setBreakdownValuesLoading(false) - }, - toggleLifecycle: ({ lifecycleName }) => { - const toggledLifecycles = values.toggledLifecycles.includes(lifecycleName) - ? values.toggledLifecycles.filter((s) => s !== lifecycleName) - : [...values.toggledLifecycles, lifecycleName] - actions.setFilters({ toggledLifecycles } as Partial, true) - }, - })), -]) diff --git a/frontend/src/scenes/trends/viz/ActionsHorizontalBar.tsx b/frontend/src/scenes/trends/viz/ActionsHorizontalBar.tsx index 0bd70a248e1ca..017bde1423b7a 100644 --- a/frontend/src/scenes/trends/viz/ActionsHorizontalBar.tsx +++ b/frontend/src/scenes/trends/viz/ActionsHorizontalBar.tsx @@ -2,29 +2,31 @@ import { useState, useEffect } from 'react' import { LineGraph } from '../../insights/views/LineGraph/LineGraph' import { getSeriesColor } from 'lib/colors' import { useValues } from 'kea' -import { trendsLogic } from 'scenes/trends/trendsLogic' import { InsightEmptyState } from '../../insights/EmptyStates' import { ChartParams, GraphType } from '~/types' import { insightLogic } from 'scenes/insights/insightLogic' import { openPersonsModal } from '../persons-modal/PersonsModal' import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' import { urlsForDatasets } from '../persons-modal/persons-modal-utils' -import { isTrendsFilter } from 'scenes/insights/sharedUtils' import { cohortsModel } from '~/models/cohortsModel' import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel' import { formatBreakdownLabel } from 'scenes/insights/utils' +import { trendsDataLogic } from '../trendsDataLogic' type DataSet = any export function ActionsHorizontalBar({ inCardView, showPersonsModal = true }: ChartParams): JSX.Element | null { const [data, setData] = useState(null) const [total, setTotal] = useState(0) - const { insightProps, insight, hiddenLegendKeys } = useValues(insightLogic) - const logic = trendsLogic(insightProps) - const { indexedResults, labelGroupType } = useValues(logic) + const { cohorts } = useValues(cohortsModel) const { formatPropertyValueForDisplay } = useValues(propertyDefinitionsModel) + const { insightProps, hiddenLegendKeys } = useValues(insightLogic) + const { indexedResults, labelGroupType, trendsFilter, formula, showValueOnSeries } = useValues( + trendsDataLogic(insightProps) + ) + function updateData(): void { const _data = [...indexedResults] const colorList = indexedResults.map((_, idx) => getSeriesColor(idx)) @@ -75,10 +77,12 @@ export function ActionsHorizontalBar({ inCardView, showPersonsModal = true }: Ch labels={data[0].labels} hiddenLegendKeys={hiddenLegendKeys} showPersonsModal={showPersonsModal} - filters={insight.filters} + trendsFilter={trendsFilter} + formula={formula} + showValueOnSeries={showValueOnSeries} inCardView={inCardView} onClick={ - !showPersonsModal || (isTrendsFilter(insight.filters) && insight.filters.formula) + !showPersonsModal || trendsFilter?.formula ? undefined : (point) => { const { index, points, crossDataset } = point diff --git a/frontend/src/scenes/trends/viz/ActionsLineGraph.tsx b/frontend/src/scenes/trends/viz/ActionsLineGraph.tsx index de451287a735d..95da3899968b1 100644 --- a/frontend/src/scenes/trends/viz/ActionsLineGraph.tsx +++ b/frontend/src/scenes/trends/viz/ActionsLineGraph.tsx @@ -1,48 +1,53 @@ import { LineGraph } from '../../insights/views/LineGraph/LineGraph' import { useValues } from 'kea' -import { trendsLogic } from 'scenes/trends/trendsLogic' import { InsightEmptyState } from '../../insights/EmptyStates' -import { ChartDisplayType, ChartParams, GraphType, InsightType } from '~/types' +import { ChartDisplayType, ChartParams, GraphType } from '~/types' import { insightLogic } from 'scenes/insights/insightLogic' import { capitalizeFirstLetter, isMultiSeriesFormula } from 'lib/utils' import { openPersonsModal } from '../persons-modal/PersonsModal' import { urlsForDatasets } from '../persons-modal/persons-modal-utils' import { DateDisplay } from 'lib/components/DateDisplay' import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' -import { isFilterWithDisplay, isLifecycleFilter, isTrendsFilter } from 'scenes/insights/sharedUtils' +import { trendsDataLogic } from '../trendsDataLogic' export function ActionsLineGraph({ inSharedMode = false, showPersonsModal = true, context, }: ChartParams): JSX.Element | null { - const { insightProps } = useValues(insightLogic) - const { filters, indexedResults, incompletenessOffsetFromEnd, hiddenLegendKeys, labelGroupType } = useValues( - trendsLogic(insightProps) - ) - const compare = isTrendsFilter(filters) && !!filters.compare - const formula = isTrendsFilter(filters) ? filters.formula : undefined + const { insightProps, hiddenLegendKeys } = useValues(insightLogic) + const { + indexedResults, + labelGroupType, + incompletenessOffsetFromEnd, + formula, + compare, + display, + interval, + shownAs, + showValueOnSeries, + trendsFilter, + isLifecycle, + isStickiness, + } = useValues(trendsDataLogic(insightProps)) return indexedResults && indexedResults[0]?.data && indexedResults.filter((result) => result.count !== 0).length > 0 ? ( { @@ -54,9 +59,9 @@ export function ActionsLineGraph({ } : undefined } - isCompare={compare} - isInProgress={filters.insight !== InsightType.STICKINESS && incompletenessOffsetFromEnd < 0} - isArea={isFilterWithDisplay(filters) && filters.display === ChartDisplayType.ActionsAreaGraph} + compare={compare} + isInProgress={!isStickiness && incompletenessOffsetFromEnd < 0} + isArea={display === ChartDisplayType.ActionsAreaGraph} incompletenessOffsetFromEnd={incompletenessOffsetFromEnd} onClick={ !showPersonsModal || isMultiSeriesFormula(formula) @@ -76,7 +81,7 @@ export function ActionsLineGraph({ if (urls?.length) { const title = - filters.shown_as === 'Stickiness' ? ( + shownAs === 'Stickiness' ? ( <> stickiness on day {day} @@ -84,10 +89,7 @@ export function ActionsLineGraph({ (label: string) => ( <> {label} on{' '} - + ) ) diff --git a/frontend/src/scenes/trends/viz/ActionsPie.tsx b/frontend/src/scenes/trends/viz/ActionsPie.tsx index 55bac474d99a5..fdfc6069d5652 100644 --- a/frontend/src/scenes/trends/viz/ActionsPie.tsx +++ b/frontend/src/scenes/trends/viz/ActionsPie.tsx @@ -2,7 +2,6 @@ import './ActionsPie.scss' import { useState, useEffect } from 'react' import { getSeriesColor } from 'lib/colors' import { useValues } from 'kea' -import { trendsLogic } from 'scenes/trends/trendsLogic' import { ChartParams, GraphType, GraphDataset } from '~/types' import { insightLogic } from 'scenes/insights/insightLogic' import { formatAggregationAxisValue } from 'scenes/insights/aggregationAxisFormat' @@ -12,20 +11,23 @@ import { urlsForDatasets } from '../persons-modal/persons-modal-utils' import { PieChart } from 'scenes/insights/views/LineGraph/PieChart' import { InsightLegend } from 'lib/components/InsightLegend/InsightLegend' import clsx from 'clsx' -import { isTrendsFilter } from 'scenes/insights/sharedUtils' import { cohortsModel } from '~/models/cohortsModel' import { propertyDefinitionsModel } from '~/models/propertyDefinitionsModel' import { formatBreakdownLabel } from 'scenes/insights/utils' +import { trendsDataLogic } from '../trendsDataLogic' export function ActionsPie({ inSharedMode, inCardView, showPersonsModal = true }: ChartParams): JSX.Element | null { const [data, setData] = useState(null) const [total, setTotal] = useState(0) - const { insightProps, insight } = useValues(insightLogic) - const logic = trendsLogic(insightProps) - const { indexedResults, labelGroupType, hiddenLegendKeys, filters } = useValues(logic) + const { cohorts } = useValues(cohortsModel) const { formatPropertyValueForDisplay } = useValues(propertyDefinitionsModel) + const { insightProps, hiddenLegendKeys } = useValues(insightLogic) + const { indexedResults, labelGroupType, trendsFilter, formula, showValueOnSeries } = useValues( + trendsDataLogic(insightProps) + ) + function updateData(): void { const _data = [...indexedResults].sort((a, b) => b.aggregated_value - a.aggregated_value) const days = _data.length > 0 ? _data[0].days : [] @@ -68,7 +70,7 @@ export function ActionsPie({ inSharedMode, inCardView, showPersonsModal = true } className={clsx( 'ActionsPie w-full', inCardView && 'flex flex-row h-full items-center', - isTrendsFilter(filters) && filters.show_legend && 'pr-4' + trendsFilter?.show_legend && 'pr-4' )} >
@@ -82,9 +84,11 @@ export function ActionsPie({ inSharedMode, inCardView, showPersonsModal = true } labelGroupType={labelGroupType} inSharedMode={!!inSharedMode} showPersonsModal={showPersonsModal} - filters={insight.filters} + trendsFilter={trendsFilter} + formula={formula} + showValueOnSeries={showValueOnSeries} onClick={ - !showPersonsModal || (isTrendsFilter(insight.filters) && insight.filters?.formula) + !showPersonsModal || formula ? undefined : (payload) => { const { points, index, crossDataset } = payload @@ -106,10 +110,10 @@ export function ActionsPie({ inSharedMode, inCardView, showPersonsModal = true } />

- {formatAggregationAxisValue(insight.filters, total)} + {formatAggregationAxisValue(trendsFilter, total)}

- {inCardView && isTrendsFilter(filters) && filters.show_legend && } + {inCardView && trendsFilter?.show_legend && }
) : (

We couldn't find any matching actions.