diff --git a/frontend/__snapshots__/scenes-app-insights--retention--dark--webkit.png b/frontend/__snapshots__/scenes-app-insights--retention--dark--webkit.png index 19eb6b34f50fd..47364961020ec 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention--dark--webkit.png and b/frontend/__snapshots__/scenes-app-insights--retention--dark--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention--dark.png b/frontend/__snapshots__/scenes-app-insights--retention--dark.png index 927563369ded2..347e8b243f914 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention--dark.png and b/frontend/__snapshots__/scenes-app-insights--retention--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention--light--webkit.png b/frontend/__snapshots__/scenes-app-insights--retention--light--webkit.png index 98955ebe1823a..c75dd1e05cec3 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention--light--webkit.png and b/frontend/__snapshots__/scenes-app-insights--retention--light--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention--light.png b/frontend/__snapshots__/scenes-app-insights--retention--light.png index 91ad4dbffd505..caae7c5d55f7f 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention--light.png and b/frontend/__snapshots__/scenes-app-insights--retention--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-breakdown--dark--webkit.png b/frontend/__snapshots__/scenes-app-insights--retention-breakdown--dark--webkit.png index fdac6bede034f..1b63026bca0d8 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-breakdown--dark--webkit.png and b/frontend/__snapshots__/scenes-app-insights--retention-breakdown--dark--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-breakdown--dark.png b/frontend/__snapshots__/scenes-app-insights--retention-breakdown--dark.png index def32f8757e8a..ff25775c978f8 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-breakdown--dark.png and b/frontend/__snapshots__/scenes-app-insights--retention-breakdown--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-breakdown--light--webkit.png b/frontend/__snapshots__/scenes-app-insights--retention-breakdown--light--webkit.png index 1b9273147cd5b..0b10df8105053 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-breakdown--light--webkit.png and b/frontend/__snapshots__/scenes-app-insights--retention-breakdown--light--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-breakdown--light.png b/frontend/__snapshots__/scenes-app-insights--retention-breakdown--light.png index 5e1c108f46405..cd49f26d5ebe4 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-breakdown--light.png and b/frontend/__snapshots__/scenes-app-insights--retention-breakdown--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--dark--webkit.png b/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--dark--webkit.png index 0a1a35d26f1af..81ecbef9c7228 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--dark--webkit.png and b/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--dark--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--dark.png b/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--dark.png index d817ed456bad2..9d02223e70bdd 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--dark.png and b/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--light--webkit.png b/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--light--webkit.png index a0b860c81091e..e89c3a0f26ce7 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--light--webkit.png and b/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--light--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--light.png b/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--light.png index 9beae1f1874dd..d27862c49cfec 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--retention-breakdown-edit--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-edit--dark--webkit.png b/frontend/__snapshots__/scenes-app-insights--retention-edit--dark--webkit.png index fca005ebf7dbf..8a3217ddfc463 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-edit--dark--webkit.png and b/frontend/__snapshots__/scenes-app-insights--retention-edit--dark--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-edit--dark.png b/frontend/__snapshots__/scenes-app-insights--retention-edit--dark.png index bf76fde0ae9fa..cdd59bf8ba40b 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-edit--dark.png and b/frontend/__snapshots__/scenes-app-insights--retention-edit--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-edit--light--webkit.png b/frontend/__snapshots__/scenes-app-insights--retention-edit--light--webkit.png index 572ced3bc856b..1e058276090bd 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-edit--light--webkit.png and b/frontend/__snapshots__/scenes-app-insights--retention-edit--light--webkit.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--retention-edit--light.png b/frontend/__snapshots__/scenes-app-insights--retention-edit--light.png index f305bad67b82a..758abfd91603c 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--retention-edit--light.png and b/frontend/__snapshots__/scenes-app-insights--retention-edit--light.png differ diff --git a/frontend/__snapshots__/scenes-app-insights--trends-line-multi--light.png b/frontend/__snapshots__/scenes-app-insights--trends-line-multi--light.png index 6ddf4043a6bce..7130e4a73b833 100644 Binary files a/frontend/__snapshots__/scenes-app-insights--trends-line-multi--light.png and b/frontend/__snapshots__/scenes-app-insights--trends-line-multi--light.png differ diff --git a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts index 6acf6bd829c0b..32114f00ac62a 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/filtersToQueryNode.ts @@ -436,6 +436,7 @@ export const retentionFilterToQuery = (filters: Partial): R targetEntity: sanitizeRetentionEntity(filters.target_entity), period: filters.period, showMean: filters.show_mean, + cumulative: filters.cumulative, }) // TODO: query.aggregation_group_type_index } diff --git a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts index 378c3550f576f..b493bc46e4e1c 100644 --- a/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts +++ b/frontend/src/queries/nodes/InsightQuery/utils/queryNodeToFilter.ts @@ -272,12 +272,14 @@ export const queryNodeToFilter = (query: InsightQueryNode): Partial camelCasedRetentionProps.target_entity = queryCopy.retentionFilter?.targetEntity camelCasedRetentionProps.total_intervals = queryCopy.retentionFilter?.totalIntervals camelCasedRetentionProps.show_mean = queryCopy.retentionFilter?.showMean + camelCasedRetentionProps.cumulative = queryCopy.retentionFilter?.cumulative delete queryCopy.retentionFilter?.retentionReference delete queryCopy.retentionFilter?.retentionType delete queryCopy.retentionFilter?.returningEntity delete queryCopy.retentionFilter?.targetEntity delete queryCopy.retentionFilter?.totalIntervals delete queryCopy.retentionFilter?.showMean + delete queryCopy.retentionFilter?.cumulative } else if (isPathsQuery(queryCopy)) { camelCasedPathsProps.edge_limit = queryCopy.pathsFilter?.edgeLimit camelCasedPathsProps.paths_hogql_expression = queryCopy.pathsFilter?.pathsHogQLExpression diff --git a/frontend/src/queries/nodes/InsightViz/InsightDisplayConfig.tsx b/frontend/src/queries/nodes/InsightViz/InsightDisplayConfig.tsx index 86d42ae2ea769..1e72d999b1dfe 100644 --- a/frontend/src/queries/nodes/InsightViz/InsightDisplayConfig.tsx +++ b/frontend/src/queries/nodes/InsightViz/InsightDisplayConfig.tsx @@ -17,6 +17,7 @@ import { ScalePicker } from 'scenes/insights/EditorFilters/ScalePicker' import { ShowLegendFilter } from 'scenes/insights/EditorFilters/ShowLegendFilter' import { ValueOnSeriesFilter } from 'scenes/insights/EditorFilters/ValueOnSeriesFilter' import { InsightDateFilter } from 'scenes/insights/filters/InsightDateFilter' +import { RetentionCumulativeCheckbox } from 'scenes/insights/filters/RetentionCumulativeCheckbox' import { RetentionMeanCheckbox } from 'scenes/insights/filters/RetentionMeanCheckbox' import { RetentionReferencePicker } from 'scenes/insights/filters/RetentionReferencePicker' import { insightLogic } from 'scenes/insights/insightLogic' @@ -146,6 +147,7 @@ export function InsightDisplayConfig(): JSX.Element { + )} diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 863fe9cd4f3af..d5dda70c77287 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -8090,6 +8090,9 @@ "RetentionFilter": { "additionalProperties": false, "properties": { + "cumulative": { + "type": "boolean" + }, "period": { "$ref": "#/definitions/RetentionPeriod", "default": "Day" @@ -8121,6 +8124,9 @@ "additionalProperties": false, "description": "`RetentionFilterType` minus everything inherited from `FilterType`", "properties": { + "cumulative": { + "type": "boolean" + }, "period": { "$ref": "#/definitions/RetentionPeriod" }, diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index e97757584ef8e..d7f4ece53f11d 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -868,6 +868,7 @@ export type RetentionFilter = { /** @default Day */ period?: RetentionFilterLegacy['period'] showMean?: RetentionFilterLegacy['show_mean'] + cumulative?: RetentionFilterLegacy['cumulative'] } export interface RetentionValue { diff --git a/frontend/src/scenes/insights/RetentionDatePicker.tsx b/frontend/src/scenes/insights/RetentionDatePicker.tsx index 9b70c81a3a127..9faf788143891 100644 --- a/frontend/src/scenes/insights/RetentionDatePicker.tsx +++ b/frontend/src/scenes/insights/RetentionDatePicker.tsx @@ -26,7 +26,7 @@ export function RetentionDatePicker(): JSX.Element { clearable buttonProps={{ tooltip: 'Cohorts up to this end date', - type: 'tertiary', + type: 'secondary', sideIcon: null, size: 'small', }} diff --git a/frontend/src/scenes/insights/filters/RetentionCumulativeCheckbox.tsx b/frontend/src/scenes/insights/filters/RetentionCumulativeCheckbox.tsx new file mode 100644 index 0000000000000..d0c532bb23e6b --- /dev/null +++ b/frontend/src/scenes/insights/filters/RetentionCumulativeCheckbox.tsx @@ -0,0 +1,45 @@ +import { IconInfo } from '@posthog/icons' +import { LemonSwitch, Tooltip } from '@posthog/lemon-ui' +import { useActions, useValues } from 'kea' +import { insightLogic } from 'scenes/insights/insightLogic' +import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' + +export function RetentionCumulativeCheckbox(): JSX.Element | null { + const { insightProps, canEditInsight } = useValues(insightLogic) + + const { retentionFilter } = useValues(insightVizDataLogic(insightProps)) + const { updateInsightFilter } = useActions(insightVizDataLogic(insightProps)) + + const cumulativeRetention = retentionFilter?.cumulative || false + + if (!canEditInsight) { + return null + } + + return ( + { + updateInsightFilter({ cumulative }) + }} + checked={cumulativeRetention} + label={ + + Rolling retention + + Rolling, or unbounded, retention includes any subsequent time period, instead of only + the next period. For example, if a user is comes back on day 7, they are counted in all + previous retention periods. + + } + > + + + + } + bordered + size="small" + /> + ) +} diff --git a/frontend/src/scenes/insights/filters/RetentionMeanCheckbox.tsx b/frontend/src/scenes/insights/filters/RetentionMeanCheckbox.tsx index d16e7f4d5a157..dbedb799f55f6 100644 --- a/frontend/src/scenes/insights/filters/RetentionMeanCheckbox.tsx +++ b/frontend/src/scenes/insights/filters/RetentionMeanCheckbox.tsx @@ -1,4 +1,4 @@ -import { LemonCheckbox } from '@posthog/lemon-ui' +import { LemonSwitch } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { insightLogic } from 'scenes/insights/insightLogic' import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic' @@ -16,7 +16,7 @@ export function RetentionMeanCheckbox(): JSX.Element | null { } return ( - { updateInsightFilter({ showMean }) }} diff --git a/frontend/src/scenes/insights/utils/cleanFilters.ts b/frontend/src/scenes/insights/utils/cleanFilters.ts index 1fc283cf996b2..2e3c2022ff814 100644 --- a/frontend/src/scenes/insights/utils/cleanFilters.ts +++ b/frontend/src/scenes/insights/utils/cleanFilters.ts @@ -306,6 +306,7 @@ export function cleanFilters( breakdown_type: filters.breakdown_type, retention_reference: filters.retention_reference, show_mean: filters.show_mean, + cumulative: filters.cumulative, total_intervals: Math.min(Math.max(filters.total_intervals ?? 11, 0), 100), ...(filters.aggregation_group_type_index != undefined ? { aggregation_group_type_index: filters.aggregation_group_type_index } diff --git a/frontend/src/scenes/insights/utils/compareInsightQuery.ts b/frontend/src/scenes/insights/utils/compareInsightQuery.ts index f753d609c2be3..ec68b414ab743 100644 --- a/frontend/src/scenes/insights/utils/compareInsightQuery.ts +++ b/frontend/src/scenes/insights/utils/compareInsightQuery.ts @@ -61,6 +61,7 @@ const cleanInsightQuery = (query: InsightQueryNode, ignoreVisualizationOnlyChang toggledLifecycles: undefined, showLabelsOnSeries: undefined, showMean: undefined, + cumulative: undefined, yAxisScaleType: undefined, hiddenLegendIndexes: undefined, hiddenLegendBreakdowns: undefined, diff --git a/frontend/src/scenes/retention/RetentionModal.tsx b/frontend/src/scenes/retention/RetentionModal.tsx index f80ea150b52b4..4dbb46e896a76 100644 --- a/frontend/src/scenes/retention/RetentionModal.tsx +++ b/frontend/src/scenes/retention/RetentionModal.tsx @@ -27,7 +27,7 @@ export function RetentionModal(): JSX.Element | null { const { results } = useValues(retentionLogic(insightProps)) const { people, peopleLoading, peopleLoadingMore } = useValues(retentionPeopleLogic(insightProps)) const { loadMorePeople } = useActions(retentionPeopleLogic(insightProps)) - const { aggregationTargetLabel, selectedInterval, exploreUrl, actorsQuery } = useValues( + const { aggregationTargetLabel, selectedInterval, exploreUrl, actorsQuery, retentionFilter } = useValues( retentionModalLogic(insightProps) ) const { closeModal } = useActions(retentionModalLogic(insightProps)) @@ -111,20 +111,32 @@ export function RetentionModal(): JSX.Element | null { {capitalizeFirstLetter(aggregationTargetLabel.singular)} - {row.values?.map((data: any, index: number) => ( - -
{results[index].label}
-
- {data.count} -   - {data.count > 0 && ( - - ({percentage(data.count / row?.values[0]['count'])}) - - )} -
- - ))} + {row.values?.map((data: any, index: number) => { + let cumulativeCount = data.count + if (retentionFilter?.cumulative) { + for (let i = index + 1; i < row.values.length; i++) { + cumulativeCount += row.values[i].count + } + cumulativeCount = Math.min(cumulativeCount, row.values[0].count) + } + const percentageValue = + row.values[0].count > 0 ? cumulativeCount / row.values[0].count : 0 + + return ( + +
{results[index].label}
+
+ {cumulativeCount} +   + {cumulativeCount > 0 && ( + + ({percentage(percentageValue)}) + + )} +
+ + ) + })} {people.result && people.result.map((personAppearances: RetentionTableAppearanceType) => ( diff --git a/frontend/src/scenes/retention/RetentionTable.tsx b/frontend/src/scenes/retention/RetentionTable.tsx index 76cf7c17e29dc..7fb6d4a0c968f 100644 --- a/frontend/src/scenes/retention/RetentionTable.tsx +++ b/frontend/src/scenes/retention/RetentionTable.tsx @@ -35,7 +35,7 @@ export function RetentionTable({ inCardView = false }: { inCardView?: boolean }) {showMean && tableRows.length > 0 ? ( - {range(0, tableRows[0].length - 1).map((columnIndex) => ( + {range(0, tableRows[0].length).map((columnIndex) => ( {columnIndex <= (hideSizeColumn ? 0 : 1) ? ( columnIndex == 0 ? ( @@ -47,23 +47,23 @@ export function RetentionTable({ inCardView = false }: { inCardView?: boolean }) mean( tableRows.map((row) => { // Stop before the last item in a row, which is an incomplete time period - if (columnIndex < row.length - 1) { - return row[columnIndex].percentage + if ( + (columnIndex >= row.length - 1 && isLatestPeriod) || + !row[columnIndex] + ) { + return null } - return null + return row[columnIndex].percentage }) ) || 0 } - latest={columnIndex == tableRows[0].length - 1} + latest={isLatestPeriod && columnIndex == tableRows[0].length - 1} clickable={false} backgroundColor={PURPLE} /> )} ))} - - - ) : undefined} diff --git a/frontend/src/scenes/retention/retentionLineGraphLogic.ts b/frontend/src/scenes/retention/retentionLineGraphLogic.ts index 2bd1ff9663e2c..7815a68525b83 100644 --- a/frontend/src/scenes/retention/retentionLineGraphLogic.ts +++ b/frontend/src/scenes/retention/retentionLineGraphLogic.ts @@ -29,7 +29,7 @@ export const retentionLineGraphLogic = kea([ trendSeries: [ (s) => [s.results, s.retentionFilter], (results, retentionFilter): RetentionTrendPayload[] => { - const { period, retentionReference } = retentionFilter || {} + const { period, retentionReference, cumulative } = retentionFilter || {} // If the retention reference option is specified as previous, // then translate retention rates to relative to previous, // otherwise, just use what the result was originally. @@ -45,12 +45,22 @@ export const retentionLineGraphLogic = kea([ // further and translate these numbers into percentage of the // previous value so we get some idea for the rate of // convergence. + return results.map((cohortRetention, datasetIndex) => { - const retentionPercentages = cohortRetention.values + let retentionPercentages = cohortRetention.values .map((value) => value.count / cohortRetention.values[0].count) - // Make them display in the right scale .map((value) => (isNaN(value) ? 0 : 100 * value)) + if (cumulative) { + retentionPercentages = retentionPercentages.map((value, valueIndex, arr) => { + let cumulativeValue = value + for (let i = valueIndex + 1; i < arr.length; i++) { + cumulativeValue += arr[i] + } + return Math.min(cumulativeValue, 100) + }) + } + // To calculate relative percentages, we take for instance Cohort 1 as percentages // of the cohort size and create another series that has a 100 at prepended so we have // diff --git a/frontend/src/scenes/retention/retentionModalLogic.ts b/frontend/src/scenes/retention/retentionModalLogic.ts index 34275f019b1a2..e8e583298b178 100644 --- a/frontend/src/scenes/retention/retentionModalLogic.ts +++ b/frontend/src/scenes/retention/retentionModalLogic.ts @@ -22,7 +22,7 @@ export const retentionModalLogic = kea([ connect((props: InsightLogicProps) => ({ values: [ insightVizDataLogic(props), - ['querySource'], + ['querySource', 'retentionFilter'], groupsModel, ['aggregationLabel'], featureFlagLogic, diff --git a/frontend/src/scenes/retention/retentionTableLogic.ts b/frontend/src/scenes/retention/retentionTableLogic.ts index 0969d1ffa6f53..6ba3d44a15e50 100644 --- a/frontend/src/scenes/retention/retentionTableLogic.ts +++ b/frontend/src/scenes/retention/retentionTableLogic.ts @@ -69,7 +69,7 @@ export const retentionTableLogic = kea([ tableRows: [ (s) => [s.results, s.maxIntervalsCount, s.retentionFilter, s.breakdownFilter, s.hideSizeColumn], (results, maxIntervalsCount, retentionFilter, breakdownFilter, hideSizeColumn) => { - const { period } = retentionFilter || {} + const { period, cumulative } = retentionFilter || {} const { breakdowns } = breakdownFilter || {} return range(maxIntervalsCount).map((index: number) => { @@ -99,12 +99,21 @@ export const retentionTableLogic = kea([ const secondColumn = hideSizeColumn ? [] : [currentResult.values[0].count] - const otherColumns = currentResult.values.map((value) => { + const otherColumns = currentResult.values.map((value, valueIndex) => { const totalCount = currentResult.values[0]['count'] - const percentage = totalCount > 0 ? (value['count'] / totalCount) * 100 : 0 + let count = value['count'] + + if (cumulative && valueIndex > 0) { + for (let i = valueIndex + 1; i < currentResult.values.length; i++) { + count += currentResult.values[i]['count'] + } + count = Math.min(count, totalCount) + } + + const percentage = totalCount > 0 ? (count / totalCount) * 100 : 0 return { - count: value['count'], + count, percentage, } }) diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 186d4c88edd4e..7758a6d138b17 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -2269,6 +2269,7 @@ export interface RetentionFilterType extends FilterType { //frontend only show_mean?: boolean + cumulative?: boolean } export interface LifecycleFilterType extends FilterType { /** @deprecated */ diff --git a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py index 812af183a36f4..585f37f387755 100644 --- a/posthog/hogql_queries/legacy_compatibility/filter_to_query.py +++ b/posthog/hogql_queries/legacy_compatibility/filter_to_query.py @@ -456,6 +456,7 @@ def _insight_filter(filter: dict): ), period=filter.get("period"), showMean=filter.get("show_mean"), + cumulative=filter.get("cumulative"), ) } elif _insight_type(filter) == "PATHS": diff --git a/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py b/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py index 084a7791c43f7..b0e9e6a6c4ec9 100644 --- a/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py +++ b/posthog/hogql_queries/legacy_compatibility/test/test_filter_to_query.py @@ -1504,6 +1504,7 @@ def test_retention_filter(self): "target_entity": {"id": "$pageview", "name": "$pageview", "type": "events"}, "period": "Week", "show_mean": True, + "cumulative": True, } query = filter_to_query(filter) @@ -1530,6 +1531,7 @@ def test_retention_filter(self): "order": None, }, showMean=True, + cumulative=True, ), ) diff --git a/posthog/schema.py b/posthog/schema.py index 4dfe859b4af86..218621e3b0eba 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -3083,6 +3083,7 @@ class RetentionFilter(BaseModel): model_config = ConfigDict( extra="forbid", ) + cumulative: Optional[bool] = None period: Optional[RetentionPeriod] = RetentionPeriod.DAY retentionReference: Optional[RetentionReference] = None retentionType: Optional[RetentionType] = None @@ -3096,6 +3097,7 @@ class RetentionFilterLegacy(BaseModel): model_config = ConfigDict( extra="forbid", ) + cumulative: Optional[bool] = None period: Optional[RetentionPeriod] = None retention_reference: Optional[RetentionReference] = None retention_type: Optional[RetentionType] = None diff --git a/posthog/schema_helpers.py b/posthog/schema_helpers.py index 356e49c98a063..126c68ca29a6c 100644 --- a/posthog/schema_helpers.py +++ b/posthog/schema_helpers.py @@ -70,6 +70,7 @@ def serialize_query(self, next_serializer): "toggledLifecycles", "showLabelsOnSeries", "showMean", + "cumulative", "yAxisScaleType", ] }