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 15 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
7 changes: 7 additions & 0 deletions frontend/src/queries/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,10 @@ 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) || (isTrendsQuery(query.source) && query.source.series.length === 1))
)
}
89 changes: 83 additions & 6 deletions frontend/src/scenes/experiments/experimentLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { hasFormErrors, toParams } from 'lib/utils'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { addProjectIdIfMissing } from 'lib/utils/router-utils'
import merge from 'lodash.merge'
import {
indexToVariantKeyFeatureFlagPayloads,
variantKeyToIndexFeatureFlagPayloads,
Expand All @@ -18,6 +19,7 @@
import { featureFlagsLogic } from 'scenes/feature-flags/featureFlagsLogic'
import { funnelDataLogic } from 'scenes/funnels/funnelDataLogic'
import { insightDataLogic } from 'scenes/insights/insightDataLogic'
import { insightsApi } from 'scenes/insights/utils/api'
import { cleanFilters, getDefaultEvent } from 'scenes/insights/utils/cleanFilters'
import { projectLogic } from 'scenes/projectLogic'
import { sceneLogic } from 'scenes/sceneLogic'
Expand All @@ -35,10 +37,14 @@
ExperimentFunnelsQuery,
ExperimentSignificanceCode,
ExperimentTrendsQuery,
type FunnelsQuery,
InsightQueryNode,
InsightVizNode,
NodeKind,
type TrendsQuery,
} from '~/queries/schema/schema-general'
import { isFunnelsQuery, isTrendsQuery } from '~/queries/utils'
import { isNodeWithSource, isValidQueryForExperiment } from '~/queries/utils'
import {
Breadcrumb,
BreakdownAttributionType,
Expand All @@ -55,10 +61,12 @@
FunnelExperimentVariant,
FunnelStep,
FunnelVizType,
type InsightShortId,
InsightType,
MultivariateFlagVariant,
ProductKey,
PropertyMathType,
type QueryBasedInsightModel,
TrendExperimentVariant,
TrendResult,
TrendsFilterType,
Expand Down Expand Up @@ -591,6 +599,10 @@
actions.touchExperimentField(`parameters.feature_flag_variants.${i}.key`)
)

const insight = values.insight as QueryBasedInsightModel | null

const experimentMetric = getExperimentMetricFromInsight(insight)

if (hasFormErrors(values.experimentErrors)) {
return
}
Expand Down Expand Up @@ -618,6 +630,7 @@
recommended_sample_size: recommendedSampleSize,
minimum_detectable_effect: minimumDetectableEffect,
},
metrics: experimentMetric ? [experimentMetric] : [],
...(!draft && { start_date: dayjs() }),
// backwards compatibility: Remove any global properties set on the experiment.
// These were used to change feature flag targeting, but this is controlled directly
Expand All @@ -630,6 +643,7 @@
},
}
)

if (response?.id) {
actions.updateExperiments(response)
actions.setEditExperiment(false)
Expand All @@ -646,6 +660,7 @@
minimum_detectable_effect: minimumDetectableEffect,
},
...(!draft && { start_date: dayjs() }),
metrics: experimentMetric ? [experimentMetric] : [],
})
if (response) {
actions.reportExperimentCreated(response)
Expand Down Expand Up @@ -1034,6 +1049,12 @@
return response
},
},
insight: {
joshsny marked this conversation as resolved.
Show resolved Hide resolved
loadInsight: async (shortId: InsightShortId): Promise<QueryBasedInsightModel | null> => {
const insight = await insightsApi.getByShortId(shortId, undefined, 'async')
return insight
},
},
metricResults: [
null as (CachedExperimentTrendsQueryResponse | CachedExperimentFunnelsQueryResponse | null)[] | null,
{
Expand All @@ -1048,7 +1069,7 @@
metrics = [...metrics, ...sharedMetrics]
}

return (await Promise.all(
return await Promise.all(

Check failure on line 1072 in frontend/src/scenes/experiments/experimentLogic.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Type '({ fakeInsightId: string; kind: NodeKind.ExperimentFunnelsQuery; insight: Record<string, any>[][]; funnels_query?: FunnelsQuery | undefined; variants: ExperimentVariantFunnelsBaseStats[]; ... 5 more ...; stats_version?: number | undefined; } | { ...; } | null)[]' is not assignable to type '(CachedExperimentTrendsQueryResponse | CachedExperimentFunnelsQueryResponse | null)[]'.
metrics.map(async (metric, index) => {
try {
const queryWithExperimentId = {
Expand All @@ -1075,7 +1096,7 @@
return null
}
})
)) as (CachedExperimentTrendsQueryResponse | CachedExperimentFunnelsQueryResponse | null)[]
)
},
},
],
Expand All @@ -1093,7 +1114,7 @@
metrics = [...metrics, ...sharedMetrics]
}

return (await Promise.all(
return await Promise.all(

Check failure on line 1117 in frontend/src/scenes/experiments/experimentLogic.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Type '({ fakeInsightId: string; kind: NodeKind.ExperimentFunnelsQuery; insight: Record<string, any>[][]; funnels_query?: FunnelsQuery | undefined; variants: ExperimentVariantFunnelsBaseStats[]; ... 5 more ...; stats_version?: number | undefined; } | { ...; } | null)[]' is not assignable to type '(CachedExperimentTrendsQueryResponse | CachedExperimentFunnelsQueryResponse | null)[]'.
metrics.map(async (metric, index) => {
try {
const queryWithExperimentId = {
Expand All @@ -1120,7 +1141,7 @@
return null
}
})
)) as (CachedExperimentTrendsQueryResponse | CachedExperimentFunnelsQueryResponse | null)[]
)
},
},
],
Expand Down Expand Up @@ -1812,19 +1833,26 @@
},
})),
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,
name: query.name ?? '',
})
}
if (parsedId !== 'new' && parsedId === values.experimentId) {
actions.loadExperiment()
}

if (query.insight) {
actions.loadInsight(query.insight as InsightShortId)
}
}
},
})),
Expand Down Expand Up @@ -1947,3 +1975,52 @@
},
}
}

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,
},
filterTestAccounts: insight.query.source.filterTestAccounts,
aggregation_group_type_index: insight.query.source.aggregation_group_type_index,
})

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
}
26 changes: 23 additions & 3 deletions frontend/src/scenes/funnels/FunnelCanvasLabel.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { IconInfo } from '@posthog/icons'
import { Link } from '@posthog/lemon-ui'
import { IconInfo, IconTestTube } from '@posthog/icons'
import { LemonButton, Link } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { humanFriendlyDuration, percentage } from 'lib/utils'
import React from 'react'
import { insightLogic } from 'scenes/insights/insightLogic'
import { FunnelStepsPicker } from 'scenes/insights/views/Funnels/FunnelStepsPicker'
import { urls } from 'scenes/urls'

import { FunnelVizType } from '~/types'

import { funnelDataLogic } from './funnelDataLogic'

export function FunnelCanvasLabel(): JSX.Element | null {
const { insightProps } = useValues(insightLogic)
const { insightProps, insight, supportsCreatingExperiment } = useValues(insightLogic)

const { conversionMetrics, aggregationTargetLabel, funnelsFilter } = useValues(funnelDataLogic(insightProps))
const { updateInsightFilter } = useActions(funnelDataLogic(insightProps))

Expand Down Expand Up @@ -66,6 +68,24 @@ export function FunnelCanvasLabel(): JSX.Element | null {
</>,
]
: []),

...(supportsCreatingExperiment
? [
<LemonButton
key="run-experiment"
icon={<IconTestTube />}
type="secondary"
data-attr="create-experiment-from-insight"
size="xsmall"
to={urls.experiment('new', {
insight: insight.short_id ?? undefined,
name: (insight.name || insight.derived_name) ?? undefined,
})}
>
Run Experiment
</LemonButton>,
joshsny marked this conversation as resolved.
Show resolved Hide resolved
]
: []),
]

return (
Expand Down
1 change: 0 additions & 1 deletion frontend/src/scenes/insights/InsightPageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,6 @@ export function InsightPageHeader({ insightLogicProps }: { insightLogicProps: In
<AddToDashboard insight={insight} setOpenModal={setAddToDashboardModalOpenModal} />
</>
)}

{insightMode !== ItemMode.Edit ? (
canEditInsight && (
<LemonButton
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/scenes/insights/insightLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import { insightsModel } from '~/models/insightsModel'
import { tagsModel } from '~/models/tagsModel'
import { DashboardFilter, HogQLVariable, Node } from '~/queries/schema'
import { isValidQueryForExperiment } from '~/queries/utils'
import { InsightLogicProps, InsightShortId, ItemMode, QueryBasedInsightModel, SetInsightOptions } from '~/types'

import { teamLogic } from '../teamLogic'
Expand Down Expand Up @@ -317,6 +318,7 @@
},
],
showPersonsModal: [() => [(s) => s.query], (query) => !query || !query.hidePersonsModal],
supportsCreatingExperiment: [(s) => [s.insight], (insight) => isValidQueryForExperiment(insight.query)],

Check failure on line 321 in frontend/src/scenes/insights/insightLogic.tsx

View workflow job for this annotation

GitHub Actions / Code quality checks

Argument of type 'Node<Record<string, any>> | null | undefined' is not assignable to parameter of type 'Node<Record<string, any>>'.
}),
listeners(({ actions, values }) => ({
saveInsight: async ({ redirectToViewMode }) => {
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/scenes/urls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { combineUrl } from 'kea-router'
import { AlertType } from 'lib/components/Alerts/types'
import { toParams } from 'lib/utils'
import { getCurrentTeamId } from 'lib/utils/getAppContext'

import { ExportOptions } from '~/exporter/types'
Expand Down Expand Up @@ -159,7 +160,13 @@ export const urls = {
`/groups/${groupTypeIndex}/${encode ? encodeURIComponent(groupKey) : groupKey}${tab ? `/${tab}` : ''}`,
cohort: (id: string | number): string => `/cohorts/${id}`,
cohorts: (): string => '/cohorts',
experiment: (id: string | number): string => `/experiments/${id}`,
experiment: (
id: string | number,
options?: {
insight?: string
name?: string
}
): string => `/experiments/${id}${options ? `?${toParams(options)}` : ''}`,
experiments: (): string => '/experiments',
experimentsSharedMetrics: (): string => '/experiments/shared-metrics',
experimentsSharedMetric: (id: string | number): string => `/experiments/shared-metrics/${id}`,
Expand Down
Loading