mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The five days of upstream evolution since the 2026-08-01 import land through bin/debrand: new query schema, billing counters, ducklake compaction, the desktop sandbox default, capture evolution. Ours' invariants hold everywhere they met it: IAM-native auth only, no ee, no upsell surfaces, one sessions model, org-is-the-tenant, v1-first serving, fail-closed capture. Crates and workflows the fork retired stay retired; the Lemon prefix finishes dissolving into elements.
1138 lines
47 KiB
TypeScript
1138 lines
47 KiB
TypeScript
import { MakeLogicType, actions, connect, kea, path, reducers, selectors } from 'kea'
|
|
import { router, urlToAction } from 'kea-router'
|
|
|
|
import { FunnelLayout } from 'lib/constants'
|
|
import { trackedActionToUrl } from 'lib/logic/scenes/trackedActionToUrl'
|
|
import { getDefaultInterval } from 'lib/utils/dateFilters'
|
|
import { capitalizeFirstLetter, wordPluralize } from 'lib/utils/strings'
|
|
import { sceneConfigurations } from 'scenes/scenes'
|
|
import { Scene } from 'scenes/sceneTypes'
|
|
import { urls } from 'scenes/urls'
|
|
|
|
import { groupsModel } from '~/models/groupsModel'
|
|
import {
|
|
ActionsNode,
|
|
AnyEntityNode,
|
|
EventsNode,
|
|
InsightVizNode,
|
|
LifecycleDataWarehouseNode,
|
|
FunnelsDataWarehouseNode,
|
|
NodeKind,
|
|
} from '~/queries/schema/schema-general'
|
|
import { sceneLogic } from '~/scenes/sceneLogic'
|
|
import {
|
|
BaseMathType,
|
|
Breadcrumb,
|
|
BreakdownAttributionType,
|
|
ChartDisplayType,
|
|
FunnelConversionWindowTimeUnit,
|
|
FunnelStepReference,
|
|
FunnelVizType,
|
|
GroupMathType,
|
|
GroupTypeIndex,
|
|
PropertyMathType,
|
|
SimpleIntervalType,
|
|
StepOrderValue,
|
|
} from '~/types'
|
|
|
|
import type { Noun } from '../../../frontend/src/models/groupsModel'
|
|
import type { CustomerAnalyticsConfig } from '../../../frontend/src/queries/schema/schema-general'
|
|
import type { GroupType } from '../../../frontend/src/types'
|
|
import { CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS } from './constants'
|
|
import { customerAnalyticsConfigLogic } from './customerAnalyticsConfigLogic'
|
|
|
|
export type BusinessType = 'b2c' | 'b2b'
|
|
|
|
type ActiveTab = 'dashboard' | 'journeys' | 'accounts' | 'notes' | 'announcements' | 'feed'
|
|
|
|
const SCENE_KEY_TO_TAB: Record<string, ActiveTab> = {
|
|
customerAnalyticsJourneys: 'journeys',
|
|
customerAnalyticsAccounts: 'accounts',
|
|
customerAnalyticsNotes: 'notes',
|
|
customerAnalyticsAnnouncements: 'announcements',
|
|
customerAnalyticsFeed: 'feed',
|
|
}
|
|
|
|
export interface InsightDefinition {
|
|
name: string
|
|
description?: string
|
|
query: InsightVizNode
|
|
requiredSeries?: Record<string, AnyEntityNode | null>
|
|
className?: string
|
|
}
|
|
|
|
const getDefaultCustomerAnalyticsInterval = (dateFrom: string | null, dateTo: string | null): SimpleIntervalType => {
|
|
const interval = getDefaultInterval(dateFrom, dateTo)
|
|
return interval === 'day' ? 'day' : 'month'
|
|
}
|
|
|
|
const INITIAL_DATE_FROM = '-30d' as string | null
|
|
const INITIAL_DATE_TO = null as string | null
|
|
const INITIAL_INTERVAL: SimpleIntervalType = getDefaultCustomerAnalyticsInterval(INITIAL_DATE_FROM, INITIAL_DATE_TO)
|
|
const INITIAL_DATE_FILTER = {
|
|
dateFrom: INITIAL_DATE_FROM,
|
|
dateTo: INITIAL_DATE_TO,
|
|
interval: INITIAL_INTERVAL,
|
|
}
|
|
|
|
const teamId = window.INSIGHTS_APP_CONTEXT?.current_team?.id
|
|
const persistConfig = { persist: true, prefix: `${teamId}_customer_analytics__` }
|
|
|
|
const setQueryParams = (params: Record<string, string>): string => {
|
|
const searchParams = { ...router.values.searchParams }
|
|
const urlParams = new URLSearchParams(searchParams)
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
urlParams.set(key, value)
|
|
})
|
|
|
|
const currentPath = router.values.location.pathname
|
|
return `${currentPath}${urlParams.toString() ? '?' + urlParams.toString() : ''}`
|
|
}
|
|
|
|
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
|
|
export interface customerAnalyticsSceneLogicValues {
|
|
activityEvent: AnyEntityNode // customerAnalyticsConfigLogic
|
|
customerAnalyticsConfig: CustomerAnalyticsConfig // customerAnalyticsConfigLogic
|
|
paymentEvent: AnyEntityNode // customerAnalyticsConfigLogic
|
|
signupEvent: AnyEntityNode // customerAnalyticsConfigLogic
|
|
signupPageviewEvent: AnyEntityNode // customerAnalyticsConfigLogic
|
|
subscriptionEvent: AnyEntityNode // customerAnalyticsConfigLogic
|
|
aggregationLabel: (groupTypeIndex: number | null | undefined, deferToUserWording?: boolean) => Noun // groupsModel
|
|
groupTypesRaw: Array<GroupType> // groupsModel
|
|
groupsEnabled: boolean // groupsModel
|
|
sceneKey: string | null // sceneLogic
|
|
activeTab: ActiveTab
|
|
activeUsersInsights: InsightDefinition[]
|
|
breadcrumbs: Breadcrumb[]
|
|
businessType: BusinessType
|
|
customerLabel: {
|
|
plural: string
|
|
singular: string
|
|
}
|
|
dateFilter: {
|
|
dateFrom: string | null
|
|
dateTo: string | null
|
|
interval: SimpleIntervalType
|
|
}
|
|
dateRange: {
|
|
date_from: string | null
|
|
date_to: string | null
|
|
}
|
|
dauSeries: AnyEntityNode | null
|
|
filterTestAccounts: boolean
|
|
groupOptions: {
|
|
label: string
|
|
value: number
|
|
}[]
|
|
mauSeries: AnyEntityNode | null
|
|
mineOnly: boolean
|
|
paymentSeries: AnyEntityNode | null
|
|
selectedGroupType: number
|
|
sessionInsights: InsightDefinition[]
|
|
signupInsights: InsightDefinition[]
|
|
signupPageviewSeries: AnyEntityNode | null
|
|
signupSeries: AnyEntityNode | null
|
|
subscriptionSeries: AnyEntityNode | null
|
|
wauSeries: AnyEntityNode | null
|
|
}
|
|
|
|
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
|
|
export interface customerAnalyticsSceneLogicActions {
|
|
setBusinessType: (businessType: BusinessType) => {
|
|
businessType: BusinessType
|
|
}
|
|
setDates: (
|
|
dateFrom: string | null,
|
|
dateTo: string | null
|
|
) => {
|
|
dateFrom: string | null
|
|
dateTo: string | null
|
|
}
|
|
setFilterTestAccounts: (filterTestAccounts: boolean) => {
|
|
filterTestAccounts: boolean
|
|
}
|
|
setMineOnly: (mineOnly: boolean) => {
|
|
mineOnly: boolean
|
|
}
|
|
setSelectedGroupType: (selectedGroupType: number) => {
|
|
selectedGroupType: number
|
|
}
|
|
}
|
|
|
|
// Generated by kea-typegen. Update if you're an agent, ignore if you're human.
|
|
export interface customerAnalyticsSceneLogicMeta {
|
|
__keaTypeGenInternalSelectorTypes: {
|
|
activeTab: (sceneKey: string | null) => ActiveTab
|
|
customerLabel: (
|
|
aggregationLabel: (groupTypeIndex: number | null | undefined, deferToUserWording?: boolean) => Noun, // groupsModel
|
|
businessType: BusinessType,
|
|
selectedGroupType: number
|
|
) => {
|
|
plural: string
|
|
singular: string
|
|
}
|
|
dateRange: (dateFilter: { dateFrom: string | null; dateTo: string | null; interval: SimpleIntervalType }) => {
|
|
date_from: string | null
|
|
date_to: string | null
|
|
}
|
|
groupOptions: (groupTypesRaw: GroupType[]) => {
|
|
label: string
|
|
value: number
|
|
}[]
|
|
dauSeries: (
|
|
activityEvent: AnyEntityNode,
|
|
businessType: BusinessType,
|
|
selectedGroupType: number
|
|
) => AnyEntityNode | null
|
|
wauSeries: (
|
|
activityEvent: AnyEntityNode,
|
|
businessType: BusinessType,
|
|
selectedGroupType: number
|
|
) => AnyEntityNode | null
|
|
mauSeries: (
|
|
activityEvent: AnyEntityNode,
|
|
businessType: BusinessType,
|
|
selectedGroupType: number
|
|
) => AnyEntityNode | null
|
|
signupSeries: (
|
|
businessType: BusinessType,
|
|
selectedGroupType: number,
|
|
signupEvent: AnyEntityNode
|
|
) => AnyEntityNode | null
|
|
signupPageviewSeries: (
|
|
businessType: BusinessType,
|
|
selectedGroupType: number,
|
|
signupPageviewEvent: AnyEntityNode
|
|
) => AnyEntityNode | null
|
|
subscriptionSeries: (
|
|
businessType: BusinessType,
|
|
selectedGroupType: number,
|
|
subscriptionEvent: AnyEntityNode
|
|
) => AnyEntityNode | null
|
|
paymentSeries: (
|
|
businessType: BusinessType,
|
|
selectedGroupType: number,
|
|
paymentEvent: AnyEntityNode
|
|
) => AnyEntityNode | null
|
|
activeUsersInsights: (
|
|
customerLabel: {
|
|
plural: string
|
|
singular: string
|
|
},
|
|
dauSeries: AnyEntityNode | null,
|
|
wauSeries: AnyEntityNode | null,
|
|
mauSeries: AnyEntityNode | null,
|
|
dateRange: {
|
|
date_from: string | null
|
|
date_to: string | null
|
|
},
|
|
filterTestAccounts: boolean
|
|
) => InsightDefinition[]
|
|
sessionInsights: (
|
|
customerLabel: {
|
|
plural: string
|
|
singular: string
|
|
},
|
|
filterTestAccounts: boolean
|
|
) => InsightDefinition[]
|
|
signupInsights: (
|
|
businessType: BusinessType,
|
|
customerLabel: {
|
|
plural: string
|
|
singular: string
|
|
},
|
|
signupSeries: AnyEntityNode | null,
|
|
paymentSeries: AnyEntityNode | null,
|
|
selectedGroupType: number,
|
|
subscriptionSeries: AnyEntityNode | null,
|
|
signupPageviewSeries: AnyEntityNode | null,
|
|
dauSeries: AnyEntityNode | null,
|
|
dateRange: {
|
|
date_from: string | null
|
|
date_to: string | null
|
|
},
|
|
filterTestAccounts: boolean
|
|
) => InsightDefinition[]
|
|
}
|
|
}
|
|
|
|
export type customerAnalyticsSceneLogicType = MakeLogicType<
|
|
customerAnalyticsSceneLogicValues,
|
|
customerAnalyticsSceneLogicActions,
|
|
Record<string, any>,
|
|
customerAnalyticsSceneLogicMeta
|
|
>
|
|
|
|
export const customerAnalyticsSceneLogic = kea<customerAnalyticsSceneLogicType>([
|
|
path(['scenes', 'customerAnalytics', 'customerAnalyticsScene']),
|
|
connect(() => ({
|
|
values: [
|
|
customerAnalyticsConfigLogic,
|
|
[
|
|
'customerAnalyticsConfig',
|
|
'activityEvent',
|
|
'signupEvent',
|
|
'signupPageviewEvent',
|
|
'subscriptionEvent',
|
|
'paymentEvent',
|
|
],
|
|
groupsModel,
|
|
['aggregationLabel', 'groupsEnabled', 'groupTypesRaw'],
|
|
sceneLogic,
|
|
['sceneKey'],
|
|
],
|
|
})),
|
|
actions({
|
|
setDates: (dateFrom: string | null, dateTo: string | null) => ({ dateFrom, dateTo }),
|
|
setBusinessType: (businessType: BusinessType) => ({ businessType }),
|
|
setSelectedGroupType: (selectedGroupType: number) => ({ selectedGroupType }),
|
|
setFilterTestAccounts: (filterTestAccounts: boolean) => ({ filterTestAccounts }),
|
|
// Shared "My accounts" (assigned-to-me) toggle backing the checkbox on both the
|
|
// Accounts and Notes tabs. Held here (not per-tab) so the choice persists as the
|
|
// user switches between them.
|
|
setMineOnly: (mineOnly: boolean) => ({ mineOnly }),
|
|
}),
|
|
reducers(() => ({
|
|
dateFilter: [
|
|
INITIAL_DATE_FILTER,
|
|
persistConfig,
|
|
{
|
|
setDates: (_, { dateTo, dateFrom }) => ({
|
|
dateTo,
|
|
dateFrom,
|
|
interval: getDefaultCustomerAnalyticsInterval(dateFrom, dateTo),
|
|
}),
|
|
},
|
|
],
|
|
businessType: [
|
|
'b2c' as BusinessType,
|
|
persistConfig,
|
|
{
|
|
setBusinessType: (_, { businessType }) => businessType,
|
|
},
|
|
],
|
|
selectedGroupType: [
|
|
0,
|
|
persistConfig,
|
|
{
|
|
setSelectedGroupType: (_, { selectedGroupType }) => selectedGroupType,
|
|
},
|
|
],
|
|
filterTestAccounts: [
|
|
true,
|
|
persistConfig,
|
|
{
|
|
setFilterTestAccounts: (_, { filterTestAccounts }) => filterTestAccounts,
|
|
},
|
|
],
|
|
mineOnly: [
|
|
false,
|
|
persistConfig,
|
|
{
|
|
setMineOnly: (_, { mineOnly }) => mineOnly,
|
|
},
|
|
],
|
|
})),
|
|
selectors({
|
|
activeTab: [
|
|
(s) => [s.sceneKey],
|
|
(sceneKey: string | null): ActiveTab => (sceneKey && SCENE_KEY_TO_TAB[sceneKey]) || 'dashboard',
|
|
],
|
|
breadcrumbs: [
|
|
() => [],
|
|
(): Breadcrumb[] => [
|
|
{
|
|
key: Scene.CustomerAnalytics,
|
|
name: sceneConfigurations[Scene.CustomerAnalytics].name,
|
|
path: urls.customerAnalyticsDashboard(),
|
|
iconType: sceneConfigurations[Scene.CustomerAnalytics].iconType || 'default_icon_type',
|
|
},
|
|
],
|
|
],
|
|
customerLabel: [
|
|
(s) => [s.aggregationLabel, s.businessType, s.selectedGroupType],
|
|
(
|
|
aggregationLabel: any,
|
|
businessType: BusinessType,
|
|
selectedGroupType: number
|
|
): { singular: string; plural: string } => {
|
|
if (!aggregationLabel || typeof aggregationLabel !== 'function') {
|
|
return { singular: 'user', plural: 'users' }
|
|
}
|
|
if (businessType === 'b2c') {
|
|
return aggregationLabel(undefined, true)
|
|
}
|
|
return aggregationLabel(selectedGroupType)
|
|
},
|
|
],
|
|
dateRange: [
|
|
(s) => [s.dateFilter],
|
|
(dateFilter: {
|
|
dateFrom: string | null
|
|
dateTo: string | null
|
|
}): { date_from: string | null; date_to: string | null } => ({
|
|
date_from: dateFilter.dateFrom,
|
|
date_to: dateFilter.dateTo,
|
|
}),
|
|
],
|
|
groupOptions: [
|
|
(s) => [s.groupTypesRaw],
|
|
(groupTypesRaw: any[]): { label: string; value: number }[] => {
|
|
return groupTypesRaw.map((groupType) => ({
|
|
label: capitalizeFirstLetter(groupType.name_plural || wordPluralize(groupType.group_type)),
|
|
value: groupType.group_type_index,
|
|
}))
|
|
},
|
|
],
|
|
dauSeries: [
|
|
(s) => [s.activityEvent, s.businessType, s.selectedGroupType],
|
|
(
|
|
activityEvent: EventsNode | ActionsNode | null,
|
|
businessType: BusinessType,
|
|
selectedGroupType: GroupTypeIndex
|
|
): AnyEntityNode | null => {
|
|
if (!activityEvent) {
|
|
return null
|
|
}
|
|
if (businessType === 'b2c') {
|
|
return {
|
|
...activityEvent,
|
|
math: BaseMathType.UniqueUsers,
|
|
}
|
|
}
|
|
return {
|
|
...activityEvent,
|
|
math: GroupMathType.UniqueGroup,
|
|
math_group_type_index: selectedGroupType,
|
|
}
|
|
},
|
|
],
|
|
wauSeries: [
|
|
(s) => [s.activityEvent, s.businessType, s.selectedGroupType],
|
|
(
|
|
activityEvent: EventsNode | ActionsNode | null,
|
|
businessType: BusinessType,
|
|
selectedGroupType: GroupTypeIndex
|
|
): AnyEntityNode | null => {
|
|
if (!activityEvent) {
|
|
return null
|
|
}
|
|
if (businessType === 'b2b') {
|
|
return {
|
|
...activityEvent,
|
|
math: BaseMathType.WeeklyActiveUsers,
|
|
math_group_type_index: selectedGroupType,
|
|
}
|
|
}
|
|
return {
|
|
...activityEvent,
|
|
math: BaseMathType.WeeklyActiveUsers,
|
|
}
|
|
},
|
|
],
|
|
mauSeries: [
|
|
(s) => [s.activityEvent, s.businessType, s.selectedGroupType],
|
|
(
|
|
activityEvent: EventsNode | ActionsNode | null,
|
|
businessType: BusinessType,
|
|
selectedGroupType: GroupTypeIndex
|
|
): AnyEntityNode | null => {
|
|
if (!activityEvent) {
|
|
return null
|
|
}
|
|
if (businessType === 'b2b') {
|
|
return {
|
|
...activityEvent,
|
|
math: BaseMathType.MonthlyActiveUsers,
|
|
math_group_type_index: selectedGroupType,
|
|
}
|
|
}
|
|
return {
|
|
...activityEvent,
|
|
math: BaseMathType.MonthlyActiveUsers,
|
|
}
|
|
},
|
|
],
|
|
signupSeries: [
|
|
(s) => [s.businessType, s.selectedGroupType, s.signupEvent],
|
|
(
|
|
businessType: BusinessType,
|
|
selectedGroupType: GroupTypeIndex,
|
|
signupEvent: AnyEntityNode
|
|
): AnyEntityNode | null => {
|
|
if (Object.keys(signupEvent).length === 0) {
|
|
return null
|
|
}
|
|
if (businessType === 'b2c') {
|
|
return {
|
|
...signupEvent,
|
|
math: BaseMathType.UniqueUsers,
|
|
}
|
|
}
|
|
return {
|
|
...signupEvent,
|
|
math: GroupMathType.UniqueGroup,
|
|
math_group_type_index: selectedGroupType,
|
|
}
|
|
},
|
|
],
|
|
signupPageviewSeries: [
|
|
(s) => [s.businessType, s.selectedGroupType, s.signupPageviewEvent],
|
|
(
|
|
businessType: BusinessType,
|
|
selectedGroupType: GroupTypeIndex,
|
|
signupPageviewEvent: AnyEntityNode
|
|
): AnyEntityNode | null => {
|
|
if (Object.keys(signupPageviewEvent).length === 0) {
|
|
return null
|
|
}
|
|
if (businessType === 'b2c') {
|
|
return {
|
|
...signupPageviewEvent,
|
|
math: BaseMathType.UniqueUsers,
|
|
}
|
|
}
|
|
return {
|
|
...signupPageviewEvent,
|
|
math: GroupMathType.UniqueGroup,
|
|
math_group_type_index: selectedGroupType,
|
|
}
|
|
},
|
|
],
|
|
subscriptionSeries: [
|
|
(s) => [s.businessType, s.selectedGroupType, s.subscriptionEvent],
|
|
(
|
|
businessType: BusinessType,
|
|
selectedGroupType: GroupTypeIndex,
|
|
subscriptionEvent: AnyEntityNode
|
|
): AnyEntityNode | null => {
|
|
if (Object.keys(subscriptionEvent).length === 0) {
|
|
return null
|
|
}
|
|
if (businessType === 'b2c') {
|
|
return {
|
|
...subscriptionEvent,
|
|
math: BaseMathType.UniqueUsers,
|
|
}
|
|
}
|
|
return {
|
|
...subscriptionEvent,
|
|
math: GroupMathType.UniqueGroup,
|
|
math_group_type_index: selectedGroupType,
|
|
}
|
|
},
|
|
],
|
|
paymentSeries: [
|
|
(s) => [s.businessType, s.selectedGroupType, s.paymentEvent],
|
|
(
|
|
businessType: BusinessType,
|
|
selectedGroupType: GroupTypeIndex,
|
|
paymentEvent: AnyEntityNode
|
|
): AnyEntityNode | null => {
|
|
if (Object.keys(paymentEvent).length === 0) {
|
|
return null
|
|
}
|
|
if (businessType === 'b2c') {
|
|
return {
|
|
...paymentEvent,
|
|
math: BaseMathType.UniqueUsers,
|
|
}
|
|
}
|
|
return {
|
|
...paymentEvent,
|
|
math: GroupMathType.UniqueGroup,
|
|
math_group_type_index: selectedGroupType,
|
|
}
|
|
},
|
|
],
|
|
activeUsersInsights: [
|
|
(s) => [s.customerLabel, s.dauSeries, s.wauSeries, s.mauSeries, s.dateRange, s.filterTestAccounts],
|
|
(
|
|
customerLabel: Record<string, string>,
|
|
dauSeries: AnyEntityNode | null,
|
|
wauSeries: AnyEntityNode | null,
|
|
mauSeries: AnyEntityNode | null,
|
|
dateRange: { date_from: string | null; date_to: string | null },
|
|
filterTestAccounts: boolean
|
|
): InsightDefinition[] => {
|
|
// Backend guarantees activity event exists, but add safety check
|
|
if (!dauSeries || !wauSeries || !mauSeries) {
|
|
return []
|
|
}
|
|
return [
|
|
{
|
|
name: `Active ${customerLabel.plural} (daily/weekly/monthly)`,
|
|
className: 'row-span-2 h-[576px]',
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
// Label each line distinctly here rather than on the shared
|
|
// selectors, which are reused by other single-metric charts.
|
|
series: [
|
|
{ ...dauSeries, custom_name: 'Daily active' },
|
|
{ ...wauSeries, custom_name: 'Weekly active' },
|
|
{ ...mauSeries, custom_name: 'Monthly active' },
|
|
],
|
|
interval: 'day',
|
|
dateRange: {
|
|
date_from: dateRange.date_from,
|
|
date_to: dateRange.date_to,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.ActionsLineGraph,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
smoothingIntervals: 1,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `Weekly active ${customerLabel.plural}`,
|
|
className: 'h-[284px]',
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [wauSeries],
|
|
interval: 'day',
|
|
dateRange: {
|
|
date_from: '-7d',
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.BoldNumber,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
smoothingIntervals: 1,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
compareFilter: {
|
|
compare: true,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `Monthly active ${customerLabel.plural}`,
|
|
className: 'h-[284px]',
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [mauSeries],
|
|
interval: 'day',
|
|
dateRange: {
|
|
date_to: null,
|
|
date_from: '-30d',
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.BoldNumber,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
smoothingIntervals: 1,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
compareFilter: {
|
|
compare: true,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
]
|
|
},
|
|
],
|
|
sessionInsights: [
|
|
(s) => [s.customerLabel, s.filterTestAccounts],
|
|
(
|
|
customerLabel: {
|
|
plural: string
|
|
singular: string
|
|
},
|
|
filterTestAccounts: boolean
|
|
): InsightDefinition[] => [
|
|
{
|
|
name: 'Unique sessions',
|
|
description: 'Events without session IDs are excluded.',
|
|
className: 'h-[284px]',
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [
|
|
{
|
|
kind: NodeKind.EventsNode,
|
|
math: BaseMathType.UniqueSessions,
|
|
name: 'All events',
|
|
event: null,
|
|
},
|
|
],
|
|
interval: 'minute',
|
|
dateRange: {
|
|
date_to: '',
|
|
date_from: '-1h',
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.BoldNumber,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
compareFilter: {
|
|
compare: true,
|
|
},
|
|
breakdownFilter: undefined,
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `Unique ${customerLabel.plural}`,
|
|
className: 'h-[284px]',
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [
|
|
{
|
|
kind: NodeKind.EventsNode,
|
|
math: BaseMathType.UniqueUsers,
|
|
name: 'All events',
|
|
event: null,
|
|
},
|
|
],
|
|
interval: 'hour',
|
|
dateRange: {
|
|
date_to: '',
|
|
date_from: '-1h',
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.BoldNumber,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
compareFilter: {
|
|
compare: true,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'Average session duration',
|
|
className: 'h-[284px]',
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [
|
|
{
|
|
kind: NodeKind.EventsNode,
|
|
math: PropertyMathType.Average,
|
|
name: '$pageview',
|
|
event: '$pageview',
|
|
math_property: '$session_duration',
|
|
},
|
|
],
|
|
interval: 'minute',
|
|
dateRange: {
|
|
date_to: '',
|
|
date_from: '-1h',
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.BoldNumber,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'duration',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
compareFilter: {
|
|
compare: true,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
],
|
|
signupInsights: [
|
|
(s) => [
|
|
s.businessType,
|
|
s.customerLabel,
|
|
s.signupSeries,
|
|
s.paymentSeries,
|
|
s.selectedGroupType,
|
|
s.subscriptionSeries,
|
|
s.signupPageviewSeries,
|
|
s.dauSeries,
|
|
s.dateRange,
|
|
s.filterTestAccounts,
|
|
],
|
|
(
|
|
businessType: BusinessType,
|
|
customerLabel: {
|
|
plural: string
|
|
singular: string
|
|
},
|
|
signupSeries: AnyEntityNode | null,
|
|
paymentSeries: AnyEntityNode | null,
|
|
selectedGroupType: number,
|
|
subscriptionSeries: AnyEntityNode | null,
|
|
signupPageviewSeries: AnyEntityNode | null,
|
|
dauSeries: AnyEntityNode | null,
|
|
dateRange: {
|
|
date_from: string | null
|
|
date_to: string | null
|
|
},
|
|
filterTestAccounts: boolean
|
|
): InsightDefinition[] => [
|
|
{
|
|
name: `${capitalizeFirstLetter(customerLabel.singular)} signups`,
|
|
requiredSeries: { signupSeries },
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [signupSeries as AnyEntityNode],
|
|
interval: 'day',
|
|
dateRange: {
|
|
date_to: dateRange.date_to,
|
|
date_from: dateRange.date_from,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.BoldNumber,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
smoothingIntervals: 1,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
compareFilter: {
|
|
compare: true,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `Total paying ${customerLabel.plural}`,
|
|
requiredSeries: { paymentSeries },
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [paymentSeries as AnyEntityNode],
|
|
interval: 'day',
|
|
dateRange: {
|
|
date_to: dateRange.date_to,
|
|
date_from: dateRange.date_from,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.BoldNumber,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
compareFilter: {
|
|
compare: true,
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `${capitalizeFirstLetter(customerLabel.singular)} signups and subscriptions`,
|
|
requiredSeries: { signupSeries, subscriptionSeries },
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [signupSeries as AnyEntityNode, subscriptionSeries as AnyEntityNode],
|
|
interval: 'day',
|
|
dateRange: {
|
|
date_from: dateRange.date_from,
|
|
date_to: dateRange.date_to,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.ActionsLineGraph,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showMultipleYAxes: false,
|
|
showValuesOnSeries: false,
|
|
smoothingIntervals: 7,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'New signups',
|
|
requiredSeries: { signupSeries },
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [signupSeries as AnyEntityNode],
|
|
interval: 'week',
|
|
dateRange: {
|
|
date_to: dateRange.date_to,
|
|
date_from: dateRange.date_from,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.ActionsAreaGraph,
|
|
showLegend: false,
|
|
yAxisScaleType: 'linear',
|
|
showValuesOnSeries: false,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'Cumulative signups (adoption)',
|
|
requiredSeries: { signupSeries },
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.TrendsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
series: [signupSeries as AnyEntityNode],
|
|
interval: 'day',
|
|
dateRange: {
|
|
date_from: dateRange.date_from,
|
|
date_to: dateRange.date_to,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
trendsFilter: {
|
|
display: ChartDisplayType.ActionsLineGraphCumulative,
|
|
showLegend: false,
|
|
showTrendLines: false,
|
|
yAxisScaleType: 'linear',
|
|
showMultipleYAxes: false,
|
|
showValuesOnSeries: false,
|
|
showPercentStackView: false,
|
|
aggregationAxisFormat: 'numeric',
|
|
showAlertThresholdLines: false,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'Signup conversion',
|
|
requiredSeries: { signupSeries, signupPageviewSeries },
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.FunnelsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
...(businessType === 'b2c' ? {} : { aggregation_group_type_index: selectedGroupType }),
|
|
series: [
|
|
signupPageviewSeries as AnyEntityNode<FunnelsDataWarehouseNode>,
|
|
signupSeries as AnyEntityNode<FunnelsDataWarehouseNode>,
|
|
],
|
|
interval: 'week',
|
|
dateRange: {
|
|
date_from: dateRange.date_from,
|
|
date_to: dateRange.date_to,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
funnelsFilter: {
|
|
layout: FunnelLayout.horizontal,
|
|
exclusions: [],
|
|
funnelVizType: FunnelVizType.Steps,
|
|
funnelOrderType: StepOrderValue.ORDERED,
|
|
funnelStepReference: FunnelStepReference.total,
|
|
funnelWindowInterval: 14,
|
|
breakdownAttributionType: BreakdownAttributionType.FirstTouch,
|
|
funnelWindowIntervalUnit: FunnelConversionWindowTimeUnit.Day,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: `Which ${customerLabel.plural} are highly engaged?`,
|
|
requiredSeries: { dauSeries },
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.LifecycleQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
...(businessType === 'b2c' ? {} : { aggregation_group_type_index: selectedGroupType }),
|
|
series: [dauSeries as AnyEntityNode<LifecycleDataWarehouseNode>],
|
|
interval: 'week',
|
|
dateRange: {
|
|
date_from: dateRange.date_from,
|
|
date_to: dateRange.date_to,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
lifecycleFilter: {
|
|
showLegend: false,
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'Free-to-paid conversion',
|
|
requiredSeries: { signupSeries, paymentSeries },
|
|
query: {
|
|
kind: NodeKind.InsightVizNode,
|
|
source: {
|
|
kind: NodeKind.FunnelsQuery,
|
|
tags: CUSTOMER_ANALYTICS_DEFAULT_QUERY_TAGS,
|
|
...(businessType === 'b2c' ? {} : { aggregation_group_type_index: selectedGroupType }),
|
|
series: [
|
|
signupSeries as AnyEntityNode<FunnelsDataWarehouseNode>,
|
|
paymentSeries as AnyEntityNode<FunnelsDataWarehouseNode>,
|
|
],
|
|
dateRange: {
|
|
date_from: dateRange.date_from,
|
|
date_to: dateRange.date_to,
|
|
explicitDate: false,
|
|
},
|
|
properties: [],
|
|
funnelsFilter: {
|
|
layout: FunnelLayout.horizontal,
|
|
exclusions: [],
|
|
funnelVizType: FunnelVizType.Steps,
|
|
funnelOrderType: StepOrderValue.ORDERED,
|
|
funnelStepReference: FunnelStepReference.total,
|
|
funnelWindowInterval: 6,
|
|
breakdownAttributionType: BreakdownAttributionType.FirstTouch,
|
|
funnelWindowIntervalUnit: FunnelConversionWindowTimeUnit.Week,
|
|
},
|
|
breakdownFilter: {
|
|
breakdown_type: 'event',
|
|
},
|
|
filterTestAccounts,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
],
|
|
}),
|
|
trackedActionToUrl(() => ({
|
|
setDates: ({ dateFrom, dateTo }): string =>
|
|
setQueryParams({ date_from: dateFrom ?? '', date_to: dateTo ?? '' }),
|
|
setFilterTestAccounts: ({ filterTestAccounts }): string =>
|
|
setQueryParams({ filter_test_accounts: String(filterTestAccounts) }),
|
|
})),
|
|
urlToAction(({ actions, values }) => ({
|
|
'*': (_, { date_from, date_to, filter_test_accounts }) => {
|
|
if (
|
|
(date_from && date_from !== values.dateFilter.dateFrom) ||
|
|
(date_to && date_to !== values.dateFilter.dateTo)
|
|
) {
|
|
actions.setDates(date_from, date_to)
|
|
}
|
|
|
|
const filterTestAccountsValue =
|
|
filter_test_accounts === undefined
|
|
? values.filterTestAccounts
|
|
: [true, 'true', 1, '1'].includes(filter_test_accounts as string | number | boolean)
|
|
|
|
if (filterTestAccountsValue !== values.filterTestAccounts) {
|
|
actions.setFilterTestAccounts(filterTestAccountsValue)
|
|
}
|
|
},
|
|
})),
|
|
])
|