Skip to content

Commit

Permalink
feat(data-exploration): implement retention insight (fixes #14015) (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
thmsobrmlr authored and raquelmsmith committed Feb 4, 2023
1 parent ba9085c commit 736aa4c
Show file tree
Hide file tree
Showing 39 changed files with 1,196 additions and 809 deletions.
4 changes: 2 additions & 2 deletions frontend/src/lib/dayjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function dayjsLocalToTimezone(
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Dayjs extends DayjsOriginal {}

export type UnitTypeShort = 'd' | 'M' | 'y' | 'h' | 'm' | 's' | 'ms'
export type UnitTypeShort = 'd' | 'D' | 'M' | 'y' | 'h' | 'm' | 's' | 'ms'

export type UnitTypeLong = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year' | 'date'

Expand All @@ -82,4 +82,4 @@ export type UnitType = UnitTypeLong | UnitTypeLongPlural | UnitTypeShort

export type OpUnitType = UnitType | 'week' | 'weeks' | 'w'
export type QUnitType = UnitType | 'quarter' | 'quarters' | 'Q'
export type ManipulateType = Omit<OpUnitType, 'date' | 'dates'>
export type ManipulateType = Exclude<OpUnitType, 'date' | 'dates'>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { InsightQueryNode, EventsNode, ActionsNode, NodeKind, SupportedNodeKind } from '~/queries/schema'
import { InsightQueryNode, EventsNode, ActionsNode, NodeKind, InsightNodeKind } from '~/queries/schema'
import { FilterType, InsightType, ActionFilter } from '~/types'
import { isLifecycleQuery, isStickinessQuery } from '~/queries/utils'
import { isLifecycleFilter, isStickinessFilter } from 'scenes/insights/sharedUtils'
import { objectClean } from 'lib/utils'

const reverseInsightMap: Record<InsightType, SupportedNodeKind> = {
const reverseInsightMap: Record<InsightType, InsightNodeKind> = {
[InsightType.TRENDS]: NodeKind.TrendsQuery,
[InsightType.FUNNELS]: NodeKind.FunnelsQuery,
[InsightType.RETENTION]: NodeKind.RetentionQuery,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import {
InsightQueryNode,
EventsNode,
ActionsNode,
SupportedNodeKind,
NodeKind,
BreakdownFilter,
NewEntityNode,
InsightNodeKind,
} from '~/queries/schema'
import { FilterType, InsightType, ActionFilter, EntityTypes, TrendsFilterType, StickinessFilterType } from '~/types'
import {
Expand All @@ -15,7 +15,6 @@ import {
isRetentionQuery,
isPathsQuery,
isStickinessQuery,
isUnimplementedQuery,
isLifecycleQuery,
isActionsNode,
} from '~/queries/utils'
Expand Down Expand Up @@ -67,7 +66,7 @@ export const seriesToActionsAndEvents = (
return { actions, events, new_entity }
}

const insightMap: Record<SupportedNodeKind, InsightType> = {
const insightMap: Record<InsightNodeKind, InsightType> = {
[NodeKind.TrendsQuery]: InsightType.TRENDS,
[NodeKind.FunnelsQuery]: InsightType.FUNNELS,
[NodeKind.RetentionQuery]: InsightType.RETENTION,
Expand All @@ -76,7 +75,7 @@ const insightMap: Record<SupportedNodeKind, InsightType> = {
[NodeKind.LifecycleQuery]: InsightType.LIFECYCLE,
}

const filterMap: Record<SupportedNodeKind, string> = {
const filterMap: Record<InsightNodeKind, string> = {
[NodeKind.TrendsQuery]: 'trendsFilter',
[NodeKind.FunnelsQuery]: 'funnelsFilter',
[NodeKind.RetentionQuery]: 'retentionFilter',
Expand All @@ -96,7 +95,7 @@ export const queryNodeToFilter = (query: InsightQueryNode): Partial<FilterType>
entity_type: 'events',
})

if (!isRetentionQuery(query) && !isPathsQuery(query) && !isUnimplementedQuery(query)) {
if (!isRetentionQuery(query) && !isPathsQuery(query)) {
const { actions, events, new_entity } = seriesToActionsAndEvents(query.series)
if (actions.length > 0) {
filters.actions = actions
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/queries/nodes/InsightViz/EditorFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import {
} from '~/types'
import { insightLogic } from 'scenes/insights/insightLogic'
import { userLogic } from 'scenes/userLogic'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { FEATURE_FLAGS, NON_BREAKDOWN_DISPLAY_TYPES } from 'lib/constants'
import { NON_BREAKDOWN_DISPLAY_TYPES } from 'lib/constants'
import {
isTrendsQuery,
isFunnelsQuery,
Expand Down Expand Up @@ -45,6 +44,7 @@ import { PayGateMini } from 'lib/components/PayGateMini/PayGateMini'
import { FunnelsQueryStepsDataExploration } from 'scenes/insights/EditorFilters/FunnelsQuerySteps'
import { AttributionDataExploration } from 'scenes/insights/EditorFilters/AttributionFilter'
import { FunnelsAdvancedDataExploration } from 'scenes/insights/EditorFilters/FunnelsAdvanced'
import { RetentionSummaryDataExploration } from 'scenes/insights/EditorFilters/RetentionSummary'
export interface EditorFiltersProps {
query: InsightQueryNode
setQuery: (node: InsightQueryNode) => void
Expand All @@ -56,8 +56,6 @@ export function EditorFilters({ query, setQuery }: EditorFiltersProps): JSX.Elem

const { insight, insightProps, filterPropertiesCount } = useValues(insightLogic)

const { featureFlags } = useValues(featureFlagLogic)

const isTrends = isTrendsQuery(query)
const isFunnels = isFunnelsQuery(query)
const isRetention = isRetentionQuery(query)
Expand All @@ -70,9 +68,6 @@ export function EditorFilters({ query, setQuery }: EditorFiltersProps): JSX.Elem

const hasBreakdown =
(isTrends && !NON_BREAKDOWN_DISPLAY_TYPES.includes(display || ChartDisplayType.ActionsLineGraph)) ||
(isRetention &&
featureFlags[FEATURE_FLAGS.RETENTION_BREAKDOWN] &&
display !== ChartDisplayType.ActionsLineGraph) ||
(isFunnels && query.funnelsFilter?.funnel_viz_type === FunnelVizType.Steps)
const hasPathsAdvanced = availableFeatures.includes(AvailableFeature.PATHS_ADVANCED)
const hasAttribution = isFunnels && query.funnelsFilter?.funnel_viz_type === FunnelVizType.Steps
Expand All @@ -83,6 +78,11 @@ export function EditorFilters({ query, setQuery }: EditorFiltersProps): JSX.Elem
{
title: 'General',
editorFilters: filterFalsy([
isRetention && {
key: 'retention-summary',
label: 'Retention Summary',
component: RetentionSummaryDataExploration,
},
...(isPaths
? filterFalsy([
{
Expand Down
12 changes: 1 addition & 11 deletions frontend/src/queries/nodes/InsightViz/TrendsSeries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@ import { MathAvailability } from 'scenes/insights/filters/ActionFilter/ActionFil
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { SINGLE_SERIES_DISPLAY_TYPES } from 'lib/constants'
import { TrendsQuery, FunnelsQuery, LifecycleQuery, StickinessQuery } from '~/queries/schema'
import {
isLifecycleQuery,
isStickinessQuery,
isTrendsQuery,
isInsightQueryWithDisplay,
isUnimplementedQuery,
} from '~/queries/utils'
import { isLifecycleQuery, isStickinessQuery, isTrendsQuery, isInsightQueryWithDisplay } from '~/queries/utils'
import { queryNodeToFilter } from '../InsightQuery/utils/queryNodeToFilter'
import { actionsAndEventsToSeries } from '../InsightQuery/utils/filtersToQueryNode'

Expand Down Expand Up @@ -45,10 +39,6 @@ export function TrendsSeries({ insightProps }: TrendsSeriesProps): JSX.Element |
TaxonomicFilterGroupType.HogQLExpression,
]

if (isUnimplementedQuery(querySource)) {
return null
}

const display = getDisplay(querySource)
const filters = queryNodeToFilter(querySource)

Expand Down
49 changes: 6 additions & 43 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@
"type": "array"
},
"period": {
"type": "string"
"$ref": "#/definitions/RetentionPeriod"
},
"properties": {
"anyOf": [
Expand Down Expand Up @@ -1962,9 +1962,6 @@
},
{
"$ref": "#/definitions/LifecycleQuery"
},
{
"$ref": "#/definitions/UnimplementedQuery"
}
]
},
Expand Down Expand Up @@ -2452,7 +2449,7 @@
"additionalProperties": false,
"properties": {
"period": {
"type": "string"
"$ref": "#/definitions/RetentionPeriod"
},
"retention_reference": {
"enum": ["total", "previous"],
Expand All @@ -2473,6 +2470,10 @@
},
"type": "object"
},
"RetentionPeriod": {
"enum": ["Hour", "Day", "Week", "Month"],
"type": "string"
},
"RetentionQuery": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -2775,44 +2776,6 @@
},
"required": ["kind", "series"],
"type": "object"
},
"UnimplementedQuery": {
"additionalProperties": false,
"properties": {
"aggregation_group_type_index": {
"description": "Groups aggregation",
"type": "number"
},
"dateRange": {
"$ref": "#/definitions/DateRange",
"description": "Date range for the query"
},
"filterTestAccounts": {
"description": "Exclude internal and test users by applying the respective filters",
"type": "boolean"
},
"kind": {
"const": "UnimplementedQuery",
"description": "Used for insights that haven't been converted to the new query format yet",
"type": "string"
},
"properties": {
"anyOf": [
{
"items": {
"$ref": "#/definitions/AnyPropertyFilter"
},
"type": "array"
},
{
"$ref": "#/definitions/PropertyGroupFilter"
}
],
"description": "Property filters for all series"
}
},
"required": ["kind"],
"type": "object"
}
}
}
8 changes: 0 additions & 8 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ export enum NodeKind {

/** Performance */
RecentPerformancePageViewNode = 'RecentPerformancePageViewNode',

/** Used for insights that haven't been converted to the new query format yet */
UnimplementedQuery = 'UnimplementedQuery',
}

export type AnyDataNode = EventsNode | EventsQuery | ActionsNode | PersonsNode
Expand Down Expand Up @@ -322,9 +319,6 @@ export interface LifecycleQuery extends InsightsQueryBase {
/** Properties specific to the lifecycle insight */
lifecycleFilter?: LifecycleFilter
}
export interface UnimplementedQuery extends InsightsQueryBase {
kind: NodeKind.UnimplementedQuery
}

export type InsightQueryNode =
| TrendsQuery
Expand All @@ -333,7 +327,6 @@ export type InsightQueryNode =
| PathsQuery
| StickinessQuery
| LifecycleQuery
| UnimplementedQuery
export type InsightNodeKind = InsightQueryNode['kind']
export type InsightFilterProperty =
| 'trendsFilter'
Expand All @@ -349,7 +342,6 @@ export type InsightFilter =
| PathsFilter
| StickinessFilter
| LifecycleFilter
export type SupportedNodeKind = Exclude<InsightNodeKind, NodeKind.UnimplementedQuery>

export const dateRangeForFilter = (source: FilterType | undefined): DateRange | undefined => {
if (!source) {
Expand Down
29 changes: 10 additions & 19 deletions frontend/src/queries/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,24 @@ import {
DateRange,
EventsNode,
EventsQuery,
TrendsQuery,
FunnelsQuery,
RetentionQuery,
PathsQuery,
StickinessQuery,
LifecycleQuery,
InsightFilter,
InsightFilterProperty,
InsightQueryNode,
InsightVizNode,
LegacyQuery,
LifecycleQuery,
Node,
NodeKind,
PathsQuery,
PersonsNode,
RecentPerformancePageViewNode,
RetentionQuery,
StickinessQuery,
SupportedNodeKind,
TimeToSeeDataQuery,
TimeToSeeDataSessionsQuery,
TrendsQuery,
UnimplementedQuery,
InsightNodeKind,
} from '~/queries/schema'
import { TaxonomicFilterGroupType, TaxonomicFilterValue } from 'lib/components/TaxonomicFilter/types'

Expand Down Expand Up @@ -94,19 +93,14 @@ export function isInsightQueryWithBreakdown(node?: Node): node is TrendsQuery |
return isTrendsQuery(node) || isFunnelsQuery(node)
}

export function isUnimplementedQuery(node?: Node): node is UnimplementedQuery {
return node?.kind === NodeKind.UnimplementedQuery
}

export function isInsightQueryNode(node?: Node): node is InsightQueryNode {
return (
isTrendsQuery(node) ||
isFunnelsQuery(node) ||
isRetentionQuery(node) ||
isPathsQuery(node) ||
isStickinessQuery(node) ||
isLifecycleQuery(node) ||
isUnimplementedQuery(node)
isLifecycleQuery(node)
)
}

Expand Down Expand Up @@ -154,7 +148,7 @@ export function dateRangeFor(node?: Node): DateRange | undefined {
return undefined
}

const nodeKindToFilterProperty: Record<SupportedNodeKind, InsightFilterProperty> = {
const nodeKindToFilterProperty: Record<InsightNodeKind, InsightFilterProperty> = {
[NodeKind.TrendsQuery]: 'trendsFilter',
[NodeKind.FunnelsQuery]: 'funnelsFilter',
[NodeKind.RetentionQuery]: 'retentionFilter',
Expand All @@ -163,15 +157,12 @@ const nodeKindToFilterProperty: Record<SupportedNodeKind, InsightFilterProperty>
[NodeKind.LifecycleQuery]: 'lifecycleFilter',
}

export function filterPropertyForQuery(node: Exclude<InsightQueryNode, UnimplementedQuery>): InsightFilterProperty {
export function filterPropertyForQuery(node: InsightQueryNode): InsightFilterProperty {
return nodeKindToFilterProperty[node.kind]
}

export function filterForQuery(node: InsightQueryNode): InsightFilter | undefined {
if (node.kind === NodeKind.UnimplementedQuery) {
return undefined
}
const filterProperty = filterPropertyForQuery(node)
const filterProperty = nodeKindToFilterProperty[node.kind]
return node[filterProperty]
}

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/funnels/funnelDataLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const funnelDataLogic = kea<funnelDataLogicType>({
(s) => [s.querySource],
(querySource: FunnelsQuery): Omit<FunnelStepRangeEntityFilter, 'id' | 'name'> => ({
funnel_from_step: 0,
funnel_to_step: querySource.series.length > 1 ? querySource.series.length - 1 : 1,
funnel_to_step: (querySource.series || []).length > 1 ? querySource.series.length - 1 : 1,
}),
],
exclusionFilters: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export function FunnelsQueryStepsDataExploration({ insightProps }: QueryEditorFi
<FunnelsQueryStepsComponent
actionFilters={actionFilters}
setActionFilters={setActionFilters}
filterSteps={(querySource as FunnelsQuery).series}
showSeriesIndicator={(querySource as FunnelsQuery).series.length > 0}
filterSteps={(querySource as FunnelsQuery).series || []}
showSeriesIndicator={((querySource as FunnelsQuery).series || []).length > 0}
isDataExploration
insightProps={insightProps}
/>
Expand Down
Loading

0 comments on commit 736aa4c

Please sign in to comment.