Skip to content

Commit

Permalink
feat(experiments): Create a dashboard in one click (#27503)
Browse files Browse the repository at this point in the history
Co-authored-by: Anders <[email protected]>
  • Loading branch information
danielbachhuber and andehen authored Jan 15, 2025
1 parent 71454ca commit 2cfe828
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 1 deletion.
11 changes: 11 additions & 0 deletions frontend/src/lib/utils/eventUsageLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,10 @@ export const eventUsageLogic = kea<eventUsageLogicType>([
experimentId,
sharedMetric,
}),
reportExperimentDashboardCreated: (experiment: Experiment, dashboardId: number) => ({
experiment,
dashboardId,
}),
// Definition Popover
reportDataManagementDefinitionHovered: (type: TaxonomicFilterGroupType) => ({ type }),
reportDataManagementDefinitionClickView: (type: TaxonomicFilterGroupType) => ({ type }),
Expand Down Expand Up @@ -1118,6 +1122,13 @@ export const eventUsageLogic = kea<eventUsageLogicType>([
shared_metric_kind: sharedMetric.query.kind,
})
},
reportExperimentDashboardCreated: ({ experiment, dashboardId }) => {
posthog.capture('experiment dashboard created', {
experiment_name: experiment.name,
experiment_id: experiment.id,
dashboard_id: dashboardId,
})
},
reportPropertyGroupFilterAdded: () => {
posthog.capture('property group filter added')
},
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/scenes/experiments/ExperimentView/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ export function PageHeaderCustom(): JSX.Element {
isSingleVariantShipped,
featureFlags,
hasGoalSet,
isCreatingExperimentDashboard,
} = useValues(experimentLogic)
const {
launchExperiment,
Expand All @@ -266,7 +267,9 @@ export function PageHeaderCustom(): JSX.Element {
loadSecondaryMetricResults,
createExposureCohort,
openShipVariantModal,
createExperimentDashboard,
} = useActions(experimentLogic)

const exposureCohortId = experiment?.exposure_cohort

return (
Expand Down Expand Up @@ -302,6 +305,13 @@ export function PageHeaderCustom(): JSX.Element {
>
{exposureCohortId ? 'View' : 'Create'} exposure cohort
</LemonButton>
<LemonButton
onClick={() => createExperimentDashboard()}
fullWidth
disabled={isCreatingExperimentDashboard}
>
Create dashboard
</LemonButton>
</>
}
/>
Expand Down
82 changes: 81 additions & 1 deletion frontend/src/scenes/experiments/experimentLogic.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { actions, connect, kea, key, listeners, path, props, reducers, selectors } from 'kea'
import { actions, connect, isBreakpoint, kea, key, listeners, path, props, reducers, selectors } from 'kea'
import { forms } from 'kea-forms'
import { loaders } from 'kea-loaders'
import { router, urlToAction } from 'kea-router'
Expand All @@ -9,6 +9,7 @@ import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { hasFormErrors, toParams } from 'lib/utils'
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { addProjectIdIfMissing } from 'lib/utils/router-utils'
import {
indexToVariantKeyFeatureFlagPayloads,
variantKeyToIndexFeatureFlagPayloads,
Expand All @@ -34,6 +35,8 @@ import {
ExperimentFunnelsQuery,
ExperimentSignificanceCode,
ExperimentTrendsQuery,
InsightQueryNode,
InsightVizNode,
NodeKind,
} from '~/queries/schema/schema-general'
import {
Expand All @@ -42,6 +45,7 @@ import {
ChartDisplayType,
CohortType,
CountPerActorMathType,
DashboardType,
EntityTypes,
Experiment,
ExperimentResults,
Expand Down Expand Up @@ -146,6 +150,7 @@ export const experimentLogic = kea<experimentLogicType>([
'reportExperimentReleaseConditionsViewed',
'reportExperimentHoldoutAssigned',
'reportExperimentSharedMetricAssigned',
'reportExperimentDashboardCreated',
],
teamLogic,
['addProductIntent'],
Expand Down Expand Up @@ -271,6 +276,8 @@ export const experimentLogic = kea<experimentLogicType>([
metadata,
}),
removeSharedMetricFromExperiment: (sharedMetricId: SharedMetric['id']) => ({ sharedMetricId }),
createExperimentDashboard: true,
setIsCreatingExperimentDashboard: (isCreating: boolean) => ({ isCreating }),
}),
reducers({
experiment: [
Expand Down Expand Up @@ -559,6 +566,12 @@ export const experimentLogic = kea<experimentLogicType>([
closeSecondarySharedMetricModal: () => false,
},
],
isCreatingExperimentDashboard: [
false,
{
setIsCreatingExperimentDashboard: (_, { isCreating }) => isCreating,
},
],
}),
listeners(({ values, actions }) => ({
createExperiment: async ({ draft }) => {
Expand Down Expand Up @@ -923,6 +936,73 @@ export const experimentLogic = kea<experimentLogicType>([
actions.closeSecondarySharedMetricModal()
actions.loadExperiment()
},
createExperimentDashboard: async () => {
actions.setIsCreatingExperimentDashboard(true)
try {
// 1. Create the dashboard
// 2. Create secondary metric insights in reverse order
// 3. Create primary metric insights in reverse order

const experimentUrl =
window.location.origin + addProjectIdIfMissing(urls.experiment(values.experimentId))
const dashboard: DashboardType = await api.create(
`api/environments/${teamLogic.values.currentTeamId}/dashboards/`,
{
name: 'Experiment: ' + values.experiment.name,
description: `Dashboard for [${experimentUrl}](${experimentUrl})`,
} as Partial<DashboardType>
)

// Reverse order because adding an insight to the dashboard
// places it at the beginning of the list
for (const type of ['secondary', 'primary']) {
const singleMetrics =
type === 'secondary' ? values.experiment.metrics_secondary : values.experiment.metrics
const sharedMetrics = values.experiment?.saved_metrics.filter(
(sharedMetric) => sharedMetric.metadata.type === type
)
const metrics = [
...singleMetrics,
...sharedMetrics.map((m) => ({ name: m.name, ...m.query })),
].reverse()
for (const query of metrics) {
const insightQuery: InsightVizNode = {
kind: NodeKind.InsightVizNode,
source: (query.kind === NodeKind.ExperimentTrendsQuery
? query.count_query
: query.funnels_query) as InsightQueryNode,
}
await api.create(`api/projects/${teamLogic.values.currentTeamId}/insights`, {
name: query.name || undefined,
query: insightQuery,
dashboards: [dashboard.id],
})
}
}

actions.reportExperimentDashboardCreated(values.experiment, dashboard.id)

const dashboardUrl = window.location.origin + addProjectIdIfMissing(urls.dashboard(dashboard.id))
actions.updateExperiment({
description:
(values.experiment.description ? values.experiment.description + `\n\n` : '') +
`Dashboard: [${dashboardUrl}](${dashboardUrl})`,
})

lemonToast.success('Dashboard created successfully', {
button: {
label: 'View dashboard',
action: () => router.actions.push(`/dashboard/${dashboard.id}`),
},
})
} catch (error: any) {
if (!isBreakpoint(error)) {
const message = error.code && error.detail ? `${error.code}: ${error.detail}` : error
lemonToast.error(`Could not create dashboard: ${message}`)
}
}
actions.setIsCreatingExperimentDashboard(false)
},
})),
loaders(({ actions, props, values }) => ({
experiment: {
Expand Down

0 comments on commit 2cfe828

Please sign in to comment.