Skip to content

Commit

Permalink
refactor(insights): query based insight usage reporting (#23084)
Browse files Browse the repository at this point in the history
  • Loading branch information
thmsobrmlr authored and timgl committed Jun 27, 2024
1 parent 0a7cf88 commit d2055dd
Show file tree
Hide file tree
Showing 15 changed files with 260 additions and 541 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
157 changes: 61 additions & 96 deletions frontend/src/lib/utils/eventUsageLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@ import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import type { Dayjs } from 'lib/dayjs'
import { now } from 'lib/dayjs'
import { isCoreFilter, PROPERTY_KEYS } from 'lib/taxonomy'
import { objectClean } from 'lib/utils'
import posthog from 'posthog-js'
import {
isFilterWithDisplay,
isFunnelsFilter,
isPathsFilter,
isRetentionFilter,
isStickinessFilter,
isTrendsFilter,
} from 'scenes/insights/sharedUtils'
import { isFilterWithDisplay, isFunnelsFilter, isTrendsFilter } from 'scenes/insights/sharedUtils'
import { preflightLogic } from 'scenes/PreflightCheck/preflightLogic'
import { EventIndex } from 'scenes/session-recordings/player/eventIndex'
import { NewSurvey, SurveyTemplateType } from 'scenes/surveys/constants'
import { userLogic } from 'scenes/userLogic'

import { Node } from '~/queries/schema'
import {
getBreakdown,
getCompareFilter,
getDisplay,
getFormula,
getInterval,
getSeries,
isActionsNode,
isDataWarehouseNode,
isEventsNode,
isFunnelsQuery,
isInsightQueryNode,
} from '~/queries/utils'
import {
AccessLevel,
AnyPartialFilterType,
Expand All @@ -35,7 +43,6 @@ import {
InsightModel,
InsightShortId,
InsightType,
ItemMode,
MultipleSurveyQuestion,
PersonType,
PropertyFilterType,
Expand Down Expand Up @@ -228,28 +235,19 @@ export const eventUsageLogic = kea<eventUsageLogicType>([
reportPersonsModalViewed: (params: any) => ({
params,
}),
reportCohortCreatedFromPersonsModal: (filters: Partial<FilterType>) => ({ filters }),
// insights
reportInsightCreated: (insightType: InsightType | null) => ({ insightType }),
reportInsightSaved: (filters: Partial<FilterType>, isNewInsight: boolean) => ({ filters, isNewInsight }),
reportInsightViewed: (
insightModel: Partial<InsightModel>,
filters: Partial<FilterType>,
insightMode: ItemMode,
query: Node | null,
isFirstLoad: boolean,
fromDashboard: boolean,
delay?: number,
changedFilters?: Record<string, any>,
isUsingSessionAnalysis?: boolean
delay?: number
) => ({
insightModel,
filters,
insightMode,
query,
isFirstLoad,
fromDashboard,
delay,
changedFilters,
isUsingSessionAnalysis,
}),
reportFunnelCalculated: (
eventCount: number,
Expand Down Expand Up @@ -588,95 +586,62 @@ export const eventUsageLogic = kea<eventUsageLogicType>([
is_new_insight: isNewInsight,
})
},
reportInsightViewed: ({
insightModel,
filters,
insightMode,
isFirstLoad,
fromDashboard,
delay,
changedFilters,
isUsingSessionAnalysis,
}) => {
const properties: Record<string, any> = {
...sanitizeFilterParams(filters),
reportInsightViewed: ({ insightModel, query, isFirstLoad, delay }) => {
const payload: Record<string, string | number | boolean | undefined> = {
report_delay: delay,
is_first_component_load: isFirstLoad,
from_dashboard: fromDashboard,
viewer_is_creator:
insightModel.created_by?.uuid && values.user?.uuid
? insightModel.created_by?.uuid === values.user?.uuid
: undefined,
is_saved: insightModel.saved,
description_length: insightModel.description?.length ?? 0,
tags_count: insightModel.tags?.length ?? 0,
query_kind: query?.kind,
}

properties.total_event_actions_count = (properties.events_count || 0) + (properties.actions_count || 0)
if (isInsightQueryNode(query)) {
const { dateRange, filterTestAccounts, samplingFactor, properties } = query

let totalEventActionFilters = 0
const entities = (filters.events || []).concat(filters.actions || [])
entities.forEach((entity) => {
if (entity.properties?.length) {
totalEventActionFilters += entity.properties.length
}
})
// date range and sampling
payload.date_from = dateRange?.date_from || undefined
payload.date_to = dateRange?.date_to || undefined
payload.interval = getInterval(query)
payload.samplingFactor = samplingFactor || undefined

// The total # of filters applied on events and actions.
properties.total_event_action_filters_count = totalEventActionFilters
// series
payload.series_length = getSeries(query)?.length
payload.event_entity_count = getSeries(query)?.filter((e) => isEventsNode(e)).length
payload.action_entity_count = getSeries(query)?.filter((e) => isActionsNode(e)).length
payload.data_warehouse_entity_count = getSeries(query)?.filter((e) => isDataWarehouseNode(e)).length

// Custom properties for each insight
if (isTrendsFilter(filters)) {
properties.breakdown_type = filters.breakdown_type
properties.breakdown = filters.breakdown
properties.using_session_analysis = isUsingSessionAnalysis
properties.compare = filters.compare // "Compare previous" option
properties.show_legend = filters.show_legend
} else if (isRetentionFilter(filters)) {
properties.period = filters.period
properties.date_to = filters.date_to
properties.retention_type = filters.retention_type
const cohortizingEvent = filters.target_entity
const retainingEvent = filters.returning_entity
properties.same_retention_and_cohortizing_event =
cohortizingEvent?.id == retainingEvent?.id && cohortizingEvent?.type == retainingEvent?.type
} else if (isPathsFilter(filters)) {
properties.path_type = filters.path_type
properties.has_start_point = !!filters.start_point
properties.has_end_point = !!filters.end_point
properties.has_funnel_filter = Object.keys(filters.funnel_filter || {}).length > 0
properties.funnel_paths = filters.funnel_paths
properties.has_min_edge_weight = !!filters.min_edge_weight
properties.has_max_edge_weight = !!filters.max_edge_weight
properties.has_edge_limit = !!filters.edge_limit
properties.has_local_cleaning_filters = (filters.local_path_cleaning_filters || []).length > 0
properties.has_path_replacements = !!filters.path_replacements
properties.has_wildcards = (filters.path_groupings || []).length > 0
properties.using_advanced_features =
properties.has_min_edge_weight ||
properties.has_max_edge_weight ||
properties.has_edge_limit ||
properties.has_local_cleaning_filters ||
properties.has_path_replacements
properties.using_basic_features =
properties.has_start_point ||
properties.has_end_point ||
properties.has_funnel_filter ||
properties.has_wildcards
} else if (isStickinessFilter(filters)) {
properties.stickiness_days = filters.stickiness_days
// properties
payload.has_properties = !!properties
payload.filter_test_accounts = filterTestAccounts

// breakdown
payload.breakdown_type = getBreakdown(query)?.breakdown_type || undefined
payload.breakdown_limit = getBreakdown(query)?.breakdown_limit || undefined
payload.breakdown_hide_other_aggregation =
getBreakdown(query)?.breakdown_hide_other_aggregation || undefined

// trends like
payload.has_formula = !!getFormula(query)
payload.display = getDisplay(query)
payload.compare = getCompareFilter(query)?.compare
payload.compare_to = getCompareFilter(query)?.compare_to

// funnels
payload.funnel_viz_type = isFunnelsQuery(query) ? query.funnelsFilter?.funnelVizType : undefined
payload.funnel_order_type = isFunnelsQuery(query) ? query.funnelsFilter?.funnelOrderType : undefined
}
properties.mode = insightMode // View or edit
properties.viewer_is_creator =
insightModel.created_by?.uuid && values.user?.uuid
? insightModel.created_by?.uuid === values.user?.uuid
: null
properties.is_saved = insightModel.saved
properties.description_length = insightModel.description?.length ?? 0
properties.tags_count = insightModel.tags?.length ?? 0

const eventName = delay ? 'insight analyzed' : 'insight viewed'
posthog.capture(eventName, { ...properties, ...(changedFilters ? changedFilters : {}) })
posthog.capture(eventName, objectClean(payload))
},
reportPersonsModalViewed: async ({ params }) => {
posthog.capture('insight person modal viewed', params)
},
reportCohortCreatedFromPersonsModal: async ({ filters }) => {
posthog.capture('person modal cohort created', sanitizeFilterParams(filters))
},
reportDashboardViewed: async ({ dashboard, lastRefreshed, delay }, breakpoint) => {
if (!delay) {
await breakpoint(500) // Debounce to avoid noisy events from continuous navigation
Expand Down
103 changes: 2 additions & 101 deletions frontend/src/queries/nodes/InsightViz/utils.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
import equal from 'fast-deep-equal'
import { PERCENT_STACK_VIEW_DISPLAY_TYPE } from 'lib/constants'
import { getEventNamesForAction, isEmptyObject } from 'lib/utils'

import {
ActionsNode,
BreakdownFilter,
CompareFilter,
DataWarehouseNode,
EventsNode,
InsightQueryNode,
InsightVizNode,
Node,
NodeKind,
TrendsQuery,
} from '~/queries/schema'
import {
isInsightQueryWithBreakdown,
isInsightQueryWithCompare,
isInsightQueryWithSeries,
isLifecycleQuery,
isStickinessQuery,
isTrendsQuery,
} from '~/queries/utils'
import { ActionType, ChartDisplayType, FilterType, InsightModel, IntervalType, QueryBasedInsightModel } from '~/types'
import { InsightQueryNode, InsightVizNode, Node, NodeKind, TrendsQuery } from '~/queries/schema'
import { ActionType, FilterType, InsightModel, QueryBasedInsightModel } from '~/types'

import { filtersToQueryNode } from '../InsightQuery/utils/filtersToQueryNode'
import { seriesToActionsAndEvents } from '../InsightQuery/utils/queryNodeToFilter'
Expand All @@ -44,85 +24,6 @@ export const getAllEventNames = (query: InsightQueryNode, allActions: ActionType
return Array.from(new Set(allEvents.filter((a): a is string => !!a)))
}

export const getDisplay = (query: InsightQueryNode): ChartDisplayType | undefined => {
if (isStickinessQuery(query)) {
return query.stickinessFilter?.display
} else if (isTrendsQuery(query)) {
return query.trendsFilter?.display
}
return undefined
}

export const getFormula = (query: InsightQueryNode): string | undefined => {
if (isTrendsQuery(query)) {
return query.trendsFilter?.formula
}
return undefined
}

export const getSeries = (query: InsightQueryNode): (EventsNode | ActionsNode | DataWarehouseNode)[] | undefined => {
if (isInsightQueryWithSeries(query)) {
return query.series
}
return undefined
}

export const getInterval = (query: InsightQueryNode): IntervalType | undefined => {
if (isInsightQueryWithSeries(query)) {
return query.interval
}
return undefined
}

export const getBreakdown = (query: InsightQueryNode): BreakdownFilter | undefined => {
if (isInsightQueryWithBreakdown(query)) {
return query.breakdownFilter
}
return undefined
}

export const getCompareFilter = (query: InsightQueryNode): CompareFilter | undefined => {
if (isInsightQueryWithCompare(query)) {
return query.compareFilter
}
return undefined
}

export const getShowLegend = (query: InsightQueryNode): boolean | undefined => {
if (isStickinessQuery(query)) {
return query.stickinessFilter?.showLegend
} else if (isTrendsQuery(query)) {
return query.trendsFilter?.showLegend
} else if (isLifecycleQuery(query)) {
return query.lifecycleFilter?.showLegend
}
return undefined
}

export const getShowValuesOnSeries = (query: InsightQueryNode): boolean | undefined => {
if (isLifecycleQuery(query)) {
return query.lifecycleFilter?.showValuesOnSeries
} else if (isStickinessQuery(query)) {
return query.stickinessFilter?.showValuesOnSeries
} else if (isTrendsQuery(query)) {
return query.trendsFilter?.showValuesOnSeries
}
return undefined
}

export const getShowLabelsOnSeries = (query: InsightQueryNode): boolean | undefined => {
if (isTrendsQuery(query)) {
return query.trendsFilter?.showLabelsOnSeries
}
return undefined
}

export const supportsPercentStackView = (q: InsightQueryNode | null | undefined): boolean =>
isTrendsQuery(q) && PERCENT_STACK_VIEW_DISPLAY_TYPE.includes(getDisplay(q) || ChartDisplayType.ActionsLineGraph)

export const getShowPercentStackView = (query: InsightQueryNode): boolean | undefined =>
supportsPercentStackView(query) && (query as TrendsQuery)?.trendsFilter?.showPercentStackView

export const getCachedResults = (
cachedInsight: Partial<InsightModel> | undefined | null,
query: InsightQueryNode
Expand Down
Loading

0 comments on commit d2055dd

Please sign in to comment.