Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create experiment from a funnel #27473

Merged
merged 40 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fcdb028
feat(wip): create experiment from insight, initial logic
joshsny Jan 13, 2025
906919f
Update UI snapshots for `webkit` (2)
github-actions[bot] Jan 13, 2025
55f5b41
chore: move button position
joshsny Jan 13, 2025
983b243
Merge branch 'create-experiment-from-funnel' of https://github.com/Po…
joshsny Jan 13, 2025
ad4ac9b
Update UI snapshots for `chromium` (1)
github-actions[bot] Jan 13, 2025
aed0020
Update UI snapshots for `chromium` (2)
github-actions[bot] Jan 13, 2025
5fab380
Update UI snapshots for `webkit` (2)
github-actions[bot] Jan 13, 2025
6e012e9
Update UI snapshots for `chromium` (2)
github-actions[bot] Jan 13, 2025
98fc1e2
feat: create experiment from insight
joshsny Jan 16, 2025
ec6b4e3
chore: remove button for filters
joshsny Jan 16, 2025
d20dfe3
chore: add data-attr
joshsny Jan 16, 2025
e7bf1ea
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 16, 2025
5382d03
Update UI snapshots for `webkit` (2)
github-actions[bot] Jan 16, 2025
8392b98
Update UI snapshots for `chromium` (1)
github-actions[bot] Jan 16, 2025
efadcd6
Update UI snapshots for `chromium` (2)
github-actions[bot] Jan 16, 2025
5236134
chore: fix type casting, running experiment for unsaved insight
joshsny Jan 17, 2025
81596a6
Update UI snapshots for `webkit` (2)
github-actions[bot] Jan 17, 2025
ab9c039
fix: display an error toast to the user when an insight fails to load…
joshsny Jan 17, 2025
3ce9742
Merge branch 'create-experiment-from-funnel' of https://github.com/Po…
joshsny Jan 17, 2025
fc43297
fix: resolve type changes from merge
joshsny Jan 17, 2025
d457419
Update UI snapshots for `chromium` (2)
github-actions[bot] Jan 17, 2025
5acbd57
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 17, 2025
64efe84
fix: pull in breakdown attribution value
joshsny Jan 17, 2025
922cde7
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 21, 2025
05806a1
Update UI snapshots for `webkit` (2)
github-actions[bot] Jan 21, 2025
5c386dc
Update UI snapshots for `chromium` (2)
github-actions[bot] Jan 21, 2025
d98422b
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 22, 2025
9d3f4c2
chrore: rewrite metric logic to not load insights, support creating f…
joshsny Jan 22, 2025
6938ea9
chore: add tooltip and banner
joshsny Jan 22, 2025
998ed17
chore: use has primary metric set
joshsny Jan 22, 2025
c17d3f9
chore: only let trends queries be valid
joshsny Jan 22, 2025
85a4fcc
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 23, 2025
44b31ab
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 24, 2025
5a47f3e
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 24, 2025
5504697
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 27, 2025
56ea5f4
Merge branch 'master' into create-experiment-from-funnel
joshsny Jan 28, 2025
0b6e9ea
fix: move experiment metric from insight to funnel canvas to fix logi…
joshsny Jan 29, 2025
cf6da66
chore: move experiment metric helper functions out of logic file
joshsny Jan 29, 2025
3148ea4
Update UI snapshots for `webkit` (2)
github-actions[bot] Jan 29, 2025
a58a292
Update UI snapshots for `chromium` (2)
github-actions[bot] Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
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.
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.
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.
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.
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.
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.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-area--dark.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-area--light.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-bar--dark.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-bar--light.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-pie--dark.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-pie--light.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-table--dark.png
Binary file modified frontend/__snapshots__/scenes-app-insights--trends-table--light.png
1 change: 1 addition & 0 deletions frontend/src/lib/utils/product-intents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum ProductIntentContext {
// Cross Sells
TAXONOMIC_FILTER_EMPTY_STATE = 'taxonomic filter empty state',
WEB_ANALYTICS_INSIGHT = 'web_analytics_insight',
CREATE_EXPERIMENT_FROM_FUNNEL_BUTTON = 'create experiment from funnel button',
}

export type ProductIntentMetadata = Record<string, unknown>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/queries/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,7 @@ export function isValidBreakdown(breakdownFilter?: BreakdownFilter | null): brea
(breakdownFilter.breakdowns && breakdownFilter.breakdowns.length > 0))
)
}

export function isValidQueryForExperiment(query: Node): boolean {
return isNodeWithSource(query) && isFunnelsQuery(query.source) && query.source.series.length >= 2
}
9 changes: 7 additions & 2 deletions frontend/src/scenes/experiments/ExperimentForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconMagicWand, IconPlusSmall, IconTrash } from '@posthog/icons'
import { LemonDivider, LemonInput, LemonTextArea, Tooltip } from '@posthog/lemon-ui'
import { LemonBanner, LemonDivider, LemonInput, LemonTextArea, Tooltip } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { Form, Group } from 'kea-forms'
import { ExperimentVariantNumber } from 'lib/components/SeriesGlyph'
Expand All @@ -15,14 +15,19 @@ import { experimentsLogic } from 'scenes/experiments/experimentsLogic'
import { experimentLogic } from './experimentLogic'

const ExperimentFormFields = (): JSX.Element => {
const { experiment, groupTypes, aggregationLabel } = useValues(experimentLogic)
const { experiment, groupTypes, aggregationLabel, hasPrimaryMetricSet } = useValues(experimentLogic)
const { addVariant, removeExperimentGroup, setExperiment, createExperiment, setExperimentType } =
useActions(experimentLogic)
const { webExperimentsAvailable, unavailableFeatureFlagKeys } = useValues(experimentsLogic)
const { groupsAccessStatus } = useValues(groupsAccessLogic)

return (
<div>
{hasPrimaryMetricSet && (
<LemonBanner type="info" className="my-4">
Fill out the details below to create your experiment based off of the insight.
</LemonBanner>
)}
<div className="space-y-8">
<div className="space-y-6 max-w-120">
<LemonField name="name" label="Name">
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/scenes/experiments/Metrics/MetricModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { useActions, useValues } from 'kea'
import { ExperimentFunnelsQuery } from '~/queries/schema'
import { Experiment, InsightType } from '~/types'

import { experimentLogic, getDefaultFunnelsMetric, getDefaultTrendsMetric } from '../experimentLogic'
import { experimentLogic } from '../experimentLogic'
import { getDefaultFunnelsMetric, getDefaultTrendsMetric } from '../utils'
import { FunnelsMetricForm } from './FunnelsMetricForm'
import { TrendsMetricForm } from './TrendsMetricForm'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { useActions, useValues } from 'kea'

import { Experiment } from '~/types'

import { experimentLogic, getDefaultFunnelsMetric } from '../experimentLogic'
import { experimentLogic } from '../experimentLogic'
import { getDefaultFunnelsMetric } from '../utils'

export function MetricSourceModal({
experimentId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { SceneExport } from 'scenes/sceneTypes'
import { themeLogic } from '~/layout/navigation-3000/themeLogic'
import { NodeKind } from '~/queries/schema/schema-general'

import { getDefaultFunnelsMetric, getDefaultTrendsMetric } from '../experimentLogic'
import { getDefaultFunnelsMetric, getDefaultTrendsMetric } from '../utils'
import { SharedFunnelsMetricForm } from './SharedFunnelsMetricForm'
import { sharedMetricLogic } from './sharedMetricLogic'
import { SharedTrendsMetricForm } from './SharedTrendsMetricForm'

export const scene: SceneExport = {
component: SharedMetric,
logic: sharedMetricLogic,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { eventUsageLogic } from 'lib/utils/eventUsageLogic'

import { UserBasicType } from '~/types'

import { getDefaultTrendsMetric } from '../experimentLogic'
import { getDefaultTrendsMetric } from '../utils'
import type { sharedMetricLogicType } from './sharedMetricLogicType'
import { sharedMetricsLogic } from './sharedMetricsLogic'

Expand Down
73 changes: 8 additions & 65 deletions frontend/src/scenes/experiments/experimentLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { forms } from 'kea-forms'
import { loaders } from 'kea-loaders'
import { router, urlToAction } from 'kea-router'
import api from 'lib/api'
import { EXPERIMENT_DEFAULT_DURATION, FunnelLayout } from 'lib/constants'
import { EXPERIMENT_DEFAULT_DURATION } from 'lib/constants'
import { dayjs } from 'lib/dayjs'
import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
Expand Down Expand Up @@ -44,16 +44,13 @@ import {
Breadcrumb,
BreakdownAttributionType,
BreakdownType,
ChartDisplayType,
CohortType,
CountPerActorMathType,
DashboardType,
Experiment,
FeatureFlagType,
FunnelConversionWindowTimeUnit,
FunnelExperimentVariant,
FunnelStep,
FunnelVizType,
InsightType,
MultivariateFlagVariant,
ProductKey,
Expand Down Expand Up @@ -623,6 +620,7 @@ export const experimentLogic = kea<experimentLogicType>([
},
}
)

if (response?.id) {
actions.updateExperiments(response)
actions.setEditExperiment(false)
Expand Down Expand Up @@ -1795,15 +1793,19 @@ export const experimentLogic = kea<experimentLogicType>([
},
})),
urlToAction(({ actions, values }) => ({
'/experiments/:id': ({ id }, _, __, currentLocation, previousLocation) => {
'/experiments/:id': ({ id }, query, __, currentLocation, previousLocation) => {
const didPathChange = currentLocation.initial || currentLocation.pathname !== previousLocation?.pathname

actions.setEditExperiment(false)

if (id && didPathChange) {
const parsedId = id === 'new' ? 'new' : parseInt(id)
if (parsedId === 'new') {
actions.resetExperiment()
actions.resetExperiment({
...NEW_EXPERIMENT,
metrics: query.metric ? [query.metric] : [],
name: query.name ?? '',
})
}
if (parsedId !== 'new' && parsedId === values.experimentId) {
actions.loadExperiment()
Expand All @@ -1823,62 +1825,3 @@ export function percentageDistribution(variantCount: number): number[] {
}
return percentages
}

export function getDefaultTrendsMetric(): ExperimentTrendsQuery {
return {
kind: NodeKind.ExperimentTrendsQuery,
count_query: {
kind: NodeKind.TrendsQuery,
series: [
{
kind: NodeKind.EventsNode,
name: '$pageview',
event: '$pageview',
},
],
interval: 'day',
dateRange: {
date_from: dayjs().subtract(EXPERIMENT_DEFAULT_DURATION, 'day').format('YYYY-MM-DDTHH:mm'),
date_to: dayjs().endOf('d').format('YYYY-MM-DDTHH:mm'),
explicitDate: true,
},
trendsFilter: {
display: ChartDisplayType.ActionsLineGraph,
},
filterTestAccounts: true,
},
}
}

export function getDefaultFunnelsMetric(): ExperimentFunnelsQuery {
return {
kind: NodeKind.ExperimentFunnelsQuery,
funnels_query: {
kind: NodeKind.FunnelsQuery,
filterTestAccounts: true,
dateRange: {
date_from: dayjs().subtract(EXPERIMENT_DEFAULT_DURATION, 'day').format('YYYY-MM-DDTHH:mm'),
date_to: dayjs().endOf('d').format('YYYY-MM-DDTHH:mm'),
explicitDate: true,
},
series: [
{
kind: NodeKind.EventsNode,
event: '$pageview',
name: '$pageview',
},
{
kind: NodeKind.EventsNode,
event: '$pageview',
name: '$pageview',
},
],
funnelsFilter: {
funnelVizType: FunnelVizType.Steps,
funnelWindowIntervalUnit: FunnelConversionWindowTimeUnit.Day,
funnelWindowInterval: 14,
layout: FunnelLayout.horizontal,
},
},
}
}
120 changes: 119 additions & 1 deletion frontend/src/scenes/experiments/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { getSeriesColor } from 'lib/colors'
import { EXPERIMENT_DEFAULT_DURATION, FunnelLayout } from 'lib/constants'
import { dayjs } from 'lib/dayjs'
import merge from 'lodash.merge'

import { ExperimentFunnelsQuery, ExperimentTrendsQuery } from '~/queries/schema'
import { AnyEntityNode, NodeKind } from '~/queries/schema/schema-general'
import { AnyEntityNode, type FunnelsQuery, NodeKind, type TrendsQuery } from '~/queries/schema/schema-general'
import { isFunnelsQuery, isTrendsQuery } from '~/queries/utils'
import { isNodeWithSource, isValidQueryForExperiment } from '~/queries/utils'
import {
ChartDisplayType,
FeatureFlagFilters,
FunnelConversionWindowTimeUnit,
FunnelTimeConversionMetrics,
FunnelVizType,
InsightType,
PropertyFilterType,
PropertyOperator,
type QueryBasedInsightModel,
TrendResult,
UniversalFiltersGroupValue,
} from '~/types'
Expand Down Expand Up @@ -177,3 +186,112 @@ export function getViewRecordingFilters(
})
return filters
}

export function getDefaultTrendsMetric(): ExperimentTrendsQuery {
return {
kind: NodeKind.ExperimentTrendsQuery,
count_query: {
kind: NodeKind.TrendsQuery,
series: [
{
kind: NodeKind.EventsNode,
name: '$pageview',
event: '$pageview',
},
],
interval: 'day',
dateRange: {
date_from: dayjs().subtract(EXPERIMENT_DEFAULT_DURATION, 'day').format('YYYY-MM-DDTHH:mm'),
date_to: dayjs().endOf('d').format('YYYY-MM-DDTHH:mm'),
explicitDate: true,
},
trendsFilter: {
display: ChartDisplayType.ActionsLineGraph,
},
filterTestAccounts: true,
},
}
}

export function getDefaultFunnelsMetric(): ExperimentFunnelsQuery {
return {
kind: NodeKind.ExperimentFunnelsQuery,
funnels_query: {
kind: NodeKind.FunnelsQuery,
filterTestAccounts: true,
dateRange: {
date_from: dayjs().subtract(EXPERIMENT_DEFAULT_DURATION, 'day').format('YYYY-MM-DDTHH:mm'),
date_to: dayjs().endOf('d').format('YYYY-MM-DDTHH:mm'),
explicitDate: true,
},
series: [
{
kind: NodeKind.EventsNode,
event: '$pageview',
name: '$pageview',
},
{
kind: NodeKind.EventsNode,
event: '$pageview',
name: '$pageview',
},
],
funnelsFilter: {
funnelVizType: FunnelVizType.Steps,
funnelWindowIntervalUnit: FunnelConversionWindowTimeUnit.Day,
funnelWindowInterval: 14,
layout: FunnelLayout.horizontal,
},
},
}
}

export function getExperimentMetricFromInsight(
insight: QueryBasedInsightModel | null
): ExperimentTrendsQuery | ExperimentFunnelsQuery | undefined {
if (!insight?.query || !isValidQueryForExperiment(insight?.query) || !isNodeWithSource(insight.query)) {
return undefined
}

const metricName = (insight?.name || insight?.derived_name) ?? undefined

if (isFunnelsQuery(insight.query.source)) {
const defaultFunnelsQuery = getDefaultFunnelsMetric().funnels_query

const funnelsQuery: FunnelsQuery = merge(defaultFunnelsQuery, {
series: insight.query.source.series,
funnelsFilter: {
funnelAggregateByHogQL: insight.query.source.funnelsFilter?.funnelAggregateByHogQL,
funnelWindowInterval: insight.query.source.funnelsFilter?.funnelWindowInterval,
funnelWindowIntervalUnit: insight.query.source.funnelsFilter?.funnelWindowIntervalUnit,
layout: insight.query.source.funnelsFilter?.layout,
breakdownAttributionType: insight.query.source.funnelsFilter?.breakdownAttributionType,
breakdownAttributionValue: insight.query.source.funnelsFilter?.breakdownAttributionValue,
},
filterTestAccounts: insight.query.source.filterTestAccounts,
})

return {
kind: NodeKind.ExperimentFunnelsQuery,
funnels_query: funnelsQuery,
name: metricName,
}
}

if (isTrendsQuery(insight.query.source)) {
const defaultTrendsQuery = getDefaultTrendsMetric().count_query

const trendsQuery: TrendsQuery = merge(defaultTrendsQuery, {
series: insight.query.source.series,
filterTestAccounts: insight.query.source.filterTestAccounts,
})

return {
kind: NodeKind.ExperimentTrendsQuery,
count_query: trendsQuery,
name: metricName,
}
}

return undefined
}
Loading
Loading