diff --git a/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx b/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx
index 1d66bee399d04..b8e2ce7a0eab8 100644
--- a/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx
+++ b/frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx
@@ -6,8 +6,7 @@ import { WebExperimentImplementationDetails } from 'scenes/experiments/WebExperi
import { ExperimentImplementationDetails } from '../ExperimentImplementationDetails'
import { experimentLogic } from '../experimentLogic'
-import { PrimaryMetricModal } from '../Metrics/PrimaryMetricModal'
-import { SecondaryMetricModal } from '../Metrics/SecondaryMetricModal'
+import { MetricModal } from '../Metrics/MetricModal'
import { MetricsView } from '../MetricsView/MetricsView'
import {
ExperimentLoadingAnimation,
@@ -142,8 +141,9 @@ export function ExperimentView(): JSX.Element {
/>
>
)}
-
-
+
+
+
>
diff --git a/frontend/src/scenes/experiments/Metrics/SecondaryGoalFunnels.tsx b/frontend/src/scenes/experiments/Metrics/FunnelsMetricForm.tsx
similarity index 83%
rename from frontend/src/scenes/experiments/Metrics/SecondaryGoalFunnels.tsx
rename to frontend/src/scenes/experiments/Metrics/FunnelsMetricForm.tsx
index fe704d9f7a953..46d6cccce4c5e 100644
--- a/frontend/src/scenes/experiments/Metrics/SecondaryGoalFunnels.tsx
+++ b/frontend/src/scenes/experiments/Metrics/FunnelsMetricForm.tsx
@@ -1,6 +1,7 @@
import { LemonLabel } from '@posthog/lemon-ui'
import { LemonInput } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
+import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { TestAccountFilterSwitch } from 'lib/components/TestAccountFiltersSwitch'
import { EXPERIMENT_DEFAULT_DURATION } from 'lib/constants'
import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
@@ -22,13 +23,26 @@ import {
FunnelAttributionSelect,
FunnelConversionWindowFilter,
} from './Selectors'
-
-export function SecondaryGoalFunnels({ metricIdx }: { metricIdx: number }): JSX.Element {
+export function FunnelsMetricForm({ isSecondary = false }: { isSecondary?: boolean }): JSX.Element {
const { currentTeam } = useValues(teamLogic)
- const { experiment, isExperimentRunning } = useValues(experimentLogic)
+ const { experiment, isExperimentRunning, editingPrimaryMetricIndex, editingSecondaryMetricIndex } =
+ useValues(experimentLogic)
const { setFunnelsMetric } = useActions(experimentLogic)
const hasFilters = (currentTeam?.test_account_filters || []).length > 0
- const currentMetric = experiment.metrics_secondary[metricIdx] as ExperimentFunnelsQuery
+
+ const metrics = isSecondary ? experiment.metrics_secondary : experiment.metrics
+ const metricIdx = isSecondary ? editingSecondaryMetricIndex : editingPrimaryMetricIndex
+
+ if (!metricIdx && metricIdx !== 0) {
+ return <>>
+ }
+
+ const currentMetric = metrics[metricIdx] as ExperimentFunnelsQuery
+
+ const actionFilterProps = {
+ ...commonActionFilterProps,
+ actionsTaxonomicGroupTypes: [TaxonomicFilterGroupType.Events, TaxonomicFilterGroupType.Actions],
+ }
return (
<>
@@ -40,7 +54,7 @@ export function SecondaryGoalFunnels({ metricIdx }: { metricIdx: number }): JSX.
setFunnelsMetric({
metricIdx,
name: newName,
- isSecondary: true,
+ isSecondary,
})
}}
/>
@@ -58,7 +72,7 @@ export function SecondaryGoalFunnels({ metricIdx }: { metricIdx: number }): JSX.
setFunnelsMetric({
metricIdx,
series,
- isSecondary: true,
+ isSecondary,
})
}}
typeKey="experiment-metric"
@@ -68,7 +82,7 @@ export function SecondaryGoalFunnels({ metricIdx }: { metricIdx: number }): JSX.
seriesIndicatorType="numeric"
sortable={true}
showNestedArrow={true}
- {...commonActionFilterProps}
+ {...actionFilterProps}
/>
@@ -91,14 +105,14 @@ export function SecondaryGoalFunnels({ metricIdx }: { metricIdx: number }): JSX.
setFunnelsMetric({
metricIdx,
funnelWindowInterval: funnelWindowInterval,
- isSecondary: true,
+ isSecondary,
})
}}
onFunnelWindowIntervalUnitChange={(funnelWindowIntervalUnit) => {
setFunnelsMetric({
metricIdx,
funnelWindowIntervalUnit: funnelWindowIntervalUnit || undefined,
- isSecondary: true,
+ isSecondary,
})
}}
/>
@@ -126,18 +140,21 @@ export function SecondaryGoalFunnels({ metricIdx }: { metricIdx: number }): JSX.
breakdownAttributionValue: breakdownAttributionValue
? parseInt(breakdownAttributionValue)
: undefined,
- isSecondary: true,
+ isSecondary,
})
}}
stepsLength={currentMetric.funnels_query?.series?.length}
/>
{
+ const val = currentMetric.funnels_query?.filterTestAccounts
+ return hasFilters ? !!val : false
+ })()}
onChange={(checked: boolean) => {
setFunnelsMetric({
metricIdx,
filterTestAccounts: checked,
- isSecondary: true,
+ isSecondary,
})
}}
fullWidth
diff --git a/frontend/src/scenes/experiments/Metrics/SecondaryMetricModal.tsx b/frontend/src/scenes/experiments/Metrics/MetricModal.tsx
similarity index 54%
rename from frontend/src/scenes/experiments/Metrics/SecondaryMetricModal.tsx
rename to frontend/src/scenes/experiments/Metrics/MetricModal.tsx
index b61af3fff9c7e..dde6c2e1b6d00 100644
--- a/frontend/src/scenes/experiments/Metrics/SecondaryMetricModal.tsx
+++ b/frontend/src/scenes/experiments/Metrics/MetricModal.tsx
@@ -1,48 +1,61 @@
import { LemonButton, LemonModal, LemonSelect } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
+import { ExperimentFunnelsQuery } from '~/queries/schema'
import { Experiment, InsightType } from '~/types'
import { experimentLogic, getDefaultFunnelsMetric, getDefaultTrendsMetric } from '../experimentLogic'
-import { SecondaryGoalFunnels } from './SecondaryGoalFunnels'
-import { SecondaryGoalTrends } from './SecondaryGoalTrends'
+import { FunnelsMetricForm } from './FunnelsMetricForm'
+import { TrendsMetricForm } from './TrendsMetricForm'
-export function SecondaryMetricModal({ experimentId }: { experimentId: Experiment['id'] }): JSX.Element {
+export function MetricModal({
+ experimentId,
+ isSecondary,
+}: {
+ experimentId: Experiment['id']
+ isSecondary?: boolean
+}): JSX.Element {
const {
experiment,
experimentLoading,
+ getMetricType,
getSecondaryMetricType,
+ isPrimaryMetricModalOpen,
isSecondaryMetricModalOpen,
+ editingPrimaryMetricIndex,
editingSecondaryMetricIndex,
} = useValues(experimentLogic({ experimentId }))
- const { setExperiment, updateExperimentGoal, closeSecondaryMetricModal } = useActions(
+ const { updateExperimentGoal, setExperiment, closePrimaryMetricModal, closeSecondaryMetricModal } = useActions(
experimentLogic({ experimentId })
)
- if (!editingSecondaryMetricIndex && editingSecondaryMetricIndex !== 0) {
+ const metricIdx = isSecondary ? editingSecondaryMetricIndex : editingPrimaryMetricIndex
+ const metricsField = isSecondary ? 'metrics_secondary' : 'metrics'
+
+ if (!metricIdx && metricIdx !== 0) {
return <>>
}
- const metricIdx = editingSecondaryMetricIndex
- const metricType = getSecondaryMetricType(metricIdx)
+ const metricType = isSecondary ? getSecondaryMetricType(metricIdx) : getMetricType(metricIdx)
+ const metrics = experiment[metricsField]
+ const metric = metrics[metricIdx]
+ const funnelStepsLength = (metric as ExperimentFunnelsQuery)?.funnels_query?.series?.length || 0
return (
{
- const newMetricsSecondary = experiment.metrics_secondary.filter(
- (_, idx) => idx !== metricIdx
- )
+ const newMetrics = metrics.filter((_, idx) => idx !== metricIdx)
setExperiment({
- metrics_secondary: newMetricsSecondary,
+ [metricsField]: newMetrics,
})
updateExperimentGoal()
}}
@@ -50,10 +63,20 @@ export function SecondaryMetricModal({ experimentId }: { experimentId: Experimen
Delete
-
+
Cancel
{
updateExperimentGoal()
}}
@@ -75,12 +98,12 @@ export function SecondaryMetricModal({ experimentId }: { experimentId: Experimen
onChange={(newMetricType) => {
setExperiment({
...experiment,
- metrics_secondary: [
- ...experiment.metrics_secondary.slice(0, metricIdx),
+ [metricsField]: [
+ ...metrics.slice(0, metricIdx),
newMetricType === InsightType.TRENDS
? getDefaultTrendsMetric()
: getDefaultFunnelsMetric(),
- ...experiment.metrics_secondary.slice(metricIdx + 1),
+ ...metrics.slice(metricIdx + 1),
],
})
}}
@@ -91,9 +114,9 @@ export function SecondaryMetricModal({ experimentId }: { experimentId: Experimen
/>
{metricType === InsightType.TRENDS ? (
-
+
) : (
-
+
)}
)
diff --git a/frontend/src/scenes/experiments/Metrics/PrimaryGoalFunnels.tsx b/frontend/src/scenes/experiments/Metrics/PrimaryGoalFunnels.tsx
deleted file mode 100644
index 2c5fe6f2da780..0000000000000
--- a/frontend/src/scenes/experiments/Metrics/PrimaryGoalFunnels.tsx
+++ /dev/null
@@ -1,320 +0,0 @@
-import { LemonLabel } from '@posthog/lemon-ui'
-import { LemonInput } from '@posthog/lemon-ui'
-import { useActions, useValues } from 'kea'
-import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
-import { TestAccountFilterSwitch } from 'lib/components/TestAccountFiltersSwitch'
-import { EXPERIMENT_DEFAULT_DURATION, FEATURE_FLAGS } from 'lib/constants'
-import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
-import { ActionFilter } from 'scenes/insights/filters/ActionFilter/ActionFilter'
-import { MathAvailability } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow'
-import { getHogQLValue } from 'scenes/insights/filters/AggregationSelect'
-import { teamLogic } from 'scenes/teamLogic'
-
-import { actionsAndEventsToSeries, filtersToQueryNode } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode'
-import { queryNodeToFilter } from '~/queries/nodes/InsightQuery/utils/queryNodeToFilter'
-import { Query } from '~/queries/Query/Query'
-import { ExperimentFunnelsQuery, NodeKind } from '~/queries/schema'
-import { BreakdownAttributionType, FilterType, FunnelsFilterType } from '~/types'
-
-import { experimentLogic } from '../experimentLogic'
-import {
- commonActionFilterProps,
- FunnelAggregationSelect,
- FunnelAttributionSelect,
- FunnelConversionWindowFilter,
-} from './Selectors'
-export function PrimaryGoalFunnels(): JSX.Element {
- const { currentTeam } = useValues(teamLogic)
- const { experiment, isExperimentRunning, featureFlags, editingPrimaryMetricIndex } = useValues(experimentLogic)
- const { setExperiment, setFunnelsMetric } = useActions(experimentLogic)
- const hasFilters = (currentTeam?.test_account_filters || []).length > 0
-
- if (!editingPrimaryMetricIndex && editingPrimaryMetricIndex !== 0) {
- return <>>
- }
-
- const metricIdx = editingPrimaryMetricIndex
- const currentMetric = experiment.metrics[metricIdx] as ExperimentFunnelsQuery
-
- const actionFilterProps = {
- ...commonActionFilterProps,
- // Remove data warehouse from the list because it's not supported in experiments
- actionsTaxonomicGroupTypes: [TaxonomicFilterGroupType.Events, TaxonomicFilterGroupType.Actions],
- }
-
- return (
- <>
-
- Name (optional)
- {featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL] && (
- {
- setFunnelsMetric({
- metricIdx,
- name: newName,
- })
- }}
- />
- )}
-
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return queryNodeToFilter(currentMetric.funnels_query)
- }
- return experiment.filters
- })()}
- setFilters={({ actions, events, data_warehouse }: Partial): void => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- const series = actionsAndEventsToSeries(
- { actions, events, data_warehouse } as any,
- true,
- MathAvailability.None
- )
-
- setFunnelsMetric({
- metricIdx,
- series,
- })
- } else {
- if (actions?.length) {
- setExperiment({
- filters: {
- ...experiment.filters,
- actions,
- events: undefined,
- data_warehouse: undefined,
- },
- })
- } else if (events?.length) {
- setExperiment({
- filters: {
- ...experiment.filters,
- events,
- actions: undefined,
- data_warehouse: undefined,
- },
- })
- } else if (data_warehouse?.length) {
- setExperiment({
- filters: {
- ...experiment.filters,
- data_warehouse,
- actions: undefined,
- events: undefined,
- },
- })
- }
- }
- }}
- typeKey="experiment-metric"
- mathAvailability={MathAvailability.None}
- buttonCopy="Add funnel step"
- showSeriesIndicator={true}
- seriesIndicatorType="numeric"
- sortable={true}
- showNestedArrow={true}
- {...actionFilterProps}
- />
-
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return getHogQLValue(
- currentMetric.funnels_query.aggregation_group_type_index ?? undefined,
- currentMetric.funnels_query.funnelsFilter?.funnelAggregateByHogQL ?? undefined
- )
- }
- return getHogQLValue(
- experiment.filters.aggregation_group_type_index,
- (experiment.filters as FunnelsFilterType).funnel_aggregate_by_hogql
- )
- })()}
- onChange={(value) => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- setFunnelsMetric({
- metricIdx,
- funnelAggregateByHogQL: value,
- })
- } else {
- setExperiment({
- filters: {
- ...experiment.filters,
- funnel_aggregate_by_hogql: value,
- },
- })
- }
- }}
- />
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return currentMetric.funnels_query?.funnelsFilter?.funnelWindowInterval
- }
- return (experiment.filters as FunnelsFilterType).funnel_window_interval
- })()}
- funnelWindowIntervalUnit={(() => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return currentMetric.funnels_query?.funnelsFilter?.funnelWindowIntervalUnit
- }
- return (experiment.filters as FunnelsFilterType).funnel_window_interval_unit
- })()}
- onFunnelWindowIntervalChange={(funnelWindowInterval) => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- setFunnelsMetric({
- metricIdx,
- funnelWindowInterval: funnelWindowInterval,
- })
- } else {
- setExperiment({
- filters: {
- ...experiment.filters,
- funnel_window_interval: funnelWindowInterval,
- },
- })
- }
- }}
- onFunnelWindowIntervalUnitChange={(funnelWindowIntervalUnit) => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- setFunnelsMetric({
- metricIdx,
- funnelWindowIntervalUnit: funnelWindowIntervalUnit || undefined,
- })
- } else {
- setExperiment({
- filters: {
- ...experiment.filters,
- funnel_window_interval_unit: funnelWindowIntervalUnit || undefined,
- },
- })
- }
- }}
- />
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- let breakdownAttributionType
- let breakdownAttributionValue
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- breakdownAttributionType =
- currentMetric.funnels_query?.funnelsFilter?.breakdownAttributionType
- breakdownAttributionValue =
- currentMetric.funnels_query?.funnelsFilter?.breakdownAttributionValue
- } else {
- breakdownAttributionType = (experiment.filters as FunnelsFilterType)
- .breakdown_attribution_type
- breakdownAttributionValue = (experiment.filters as FunnelsFilterType)
- .breakdown_attribution_value
- }
-
- const currentValue: BreakdownAttributionType | `${BreakdownAttributionType.Step}/${number}` =
- !breakdownAttributionType
- ? BreakdownAttributionType.FirstTouch
- : breakdownAttributionType === BreakdownAttributionType.Step
- ? `${breakdownAttributionType}/${breakdownAttributionValue || 0}`
- : breakdownAttributionType
-
- return currentValue
- })()}
- onChange={(value) => {
- const [breakdownAttributionType, breakdownAttributionValue] = (value || '').split('/')
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- setFunnelsMetric({
- metricIdx,
- breakdownAttributionType: breakdownAttributionType as BreakdownAttributionType,
- breakdownAttributionValue: breakdownAttributionValue
- ? parseInt(breakdownAttributionValue)
- : undefined,
- })
- } else {
- setExperiment({
- filters: {
- ...experiment.filters,
- breakdown_attribution_type: breakdownAttributionType as BreakdownAttributionType,
- breakdown_attribution_value: breakdownAttributionValue
- ? parseInt(breakdownAttributionValue)
- : 0,
- },
- })
- }
- }}
- stepsLength={(() => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return currentMetric.funnels_query?.series?.length
- }
- return Math.max(
- experiment.filters.actions?.length ?? 0,
- experiment.filters.events?.length ?? 0,
- experiment.filters.data_warehouse?.length ?? 0
- )
- })()}
- />
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- const val = (experiment.metrics[metricIdx] as ExperimentFunnelsQuery).funnels_query
- ?.filterTestAccounts
- return hasFilters ? !!val : false
- }
- return hasFilters ? !!experiment.filters.filter_test_accounts : false
- })()}
- onChange={(checked: boolean) => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- setFunnelsMetric({
- metricIdx,
- filterTestAccounts: checked,
- })
- } else {
- setExperiment({
- filters: {
- ...experiment.filters,
- filter_test_accounts: checked,
- },
- })
- }
- }}
- fullWidth
- />
-
- {isExperimentRunning && (
-
- Preview insights are generated based on {EXPERIMENT_DEFAULT_DURATION} days of data. This can cause a
- mismatch between the preview and the actual results.
-
- )}
-
- {/* :FLAG: CLEAN UP AFTER MIGRATION */}
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return currentMetric.funnels_query
- }
- return filtersToQueryNode(experiment.filters)
- })(),
- showTable: false,
- showLastComputation: true,
- showLastComputationRefresh: false,
- }}
- readOnly
- />
-
- >
- )
-}
diff --git a/frontend/src/scenes/experiments/Metrics/PrimaryMetricModal.tsx b/frontend/src/scenes/experiments/Metrics/PrimaryMetricModal.tsx
deleted file mode 100644
index 31d4eec71265b..0000000000000
--- a/frontend/src/scenes/experiments/Metrics/PrimaryMetricModal.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import { LemonButton, LemonModal, LemonSelect } from '@posthog/lemon-ui'
-import { useActions, useValues } from 'kea'
-import { FEATURE_FLAGS } from 'lib/constants'
-
-import { ExperimentFunnelsQuery } from '~/queries/schema'
-import { Experiment, InsightType } from '~/types'
-
-import { experimentLogic, getDefaultFilters, getDefaultFunnelsMetric, getDefaultTrendsMetric } from '../experimentLogic'
-import { PrimaryGoalFunnels } from '../Metrics/PrimaryGoalFunnels'
-import { PrimaryGoalTrends } from '../Metrics/PrimaryGoalTrends'
-
-export function PrimaryMetricModal({ experimentId }: { experimentId: Experiment['id'] }): JSX.Element {
- const {
- experiment,
- experimentLoading,
- getMetricType,
- featureFlags,
- isPrimaryMetricModalOpen,
- editingPrimaryMetricIndex,
- } = useValues(experimentLogic({ experimentId }))
- const { updateExperimentGoal, setExperiment, closePrimaryMetricModal } = useActions(
- experimentLogic({ experimentId })
- )
-
- if (!editingPrimaryMetricIndex && editingPrimaryMetricIndex !== 0) {
- return <>>
- }
-
- const metricIdx = editingPrimaryMetricIndex
- const metricType = getMetricType(metricIdx)
-
- let funnelStepsLength = 0
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL] && metricType === InsightType.FUNNELS) {
- const metric = experiment.metrics[metricIdx] as ExperimentFunnelsQuery
- funnelStepsLength = metric?.funnels_query?.series?.length || 0
- } else {
- funnelStepsLength = (experiment.filters?.events?.length || 0) + (experiment.filters?.actions?.length || 0)
- }
-
- return (
-
- {
- const newMetrics = experiment.metrics.filter((_, idx) => idx !== metricIdx)
- setExperiment({
- metrics: newMetrics,
- })
- updateExperimentGoal()
- }}
- >
- Delete
-
-
-
- Cancel
-
- {
- updateExperimentGoal()
- }}
- type="primary"
- loading={experimentLoading}
- data-attr="create-annotation-submit"
- >
- Save
-
-
-
- }
- >
-
- Metric type
- {
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- setExperiment({
- ...experiment,
- metrics: [
- ...experiment.metrics.slice(0, metricIdx),
- newMetricType === InsightType.TRENDS
- ? getDefaultTrendsMetric()
- : getDefaultFunnelsMetric(),
- ...experiment.metrics.slice(metricIdx + 1),
- ],
- })
- } else {
- setExperiment({
- ...experiment,
- filters: getDefaultFilters(newMetricType, undefined),
- })
- }
- }}
- options={[
- { value: InsightType.TRENDS, label: Trends },
- { value: InsightType.FUNNELS, label: Funnels },
- ]}
- />
-
- {metricType === InsightType.TRENDS ? : }
-
- )
-}
diff --git a/frontend/src/scenes/experiments/Metrics/SecondaryGoalTrends.tsx b/frontend/src/scenes/experiments/Metrics/SecondaryGoalTrends.tsx
deleted file mode 100644
index 20aae645e6e1e..0000000000000
--- a/frontend/src/scenes/experiments/Metrics/SecondaryGoalTrends.tsx
+++ /dev/null
@@ -1,204 +0,0 @@
-import { LemonLabel } from '@posthog/lemon-ui'
-import { LemonInput } from '@posthog/lemon-ui'
-import { useActions, useValues } from 'kea'
-import { TestAccountFilterSwitch } from 'lib/components/TestAccountFiltersSwitch'
-import { EXPERIMENT_DEFAULT_DURATION, FEATURE_FLAGS } from 'lib/constants'
-import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
-import { ActionFilter } from 'scenes/insights/filters/ActionFilter/ActionFilter'
-import { MathAvailability } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow'
-import { teamLogic } from 'scenes/teamLogic'
-
-import { actionsAndEventsToSeries, filtersToQueryNode } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode'
-import { queryNodeToFilter } from '~/queries/nodes/InsightQuery/utils/queryNodeToFilter'
-import { Query } from '~/queries/Query/Query'
-import { ExperimentTrendsQuery, NodeKind } from '~/queries/schema'
-import { FilterType } from '~/types'
-
-import { experimentLogic } from '../experimentLogic'
-import { commonActionFilterProps } from './Selectors'
-
-export function SecondaryGoalTrends({ metricIdx }: { metricIdx: number }): JSX.Element {
- const { experiment, isExperimentRunning, featureFlags } = useValues(experimentLogic)
- const { setExperiment, setTrendsMetric } = useActions(experimentLogic)
- const { currentTeam } = useValues(teamLogic)
- const hasFilters = (currentTeam?.test_account_filters || []).length > 0
- const currentMetric = experiment.metrics_secondary[metricIdx] as ExperimentTrendsQuery
-
- return (
- <>
-
- Name (optional)
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return currentMetric.name
- }
- return experiment.secondary_metrics[metricIdx].name
- })()}
- onChange={(newName) => {
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- setTrendsMetric({
- metricIdx,
- name: newName,
- isSecondary: true,
- })
- } else {
- setExperiment({
- secondary_metrics: experiment.secondary_metrics.map((metric, idx) =>
- idx === metricIdx ? { ...metric, name: newName } : metric
- ),
- })
- }
- }}
- />
-
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return queryNodeToFilter(currentMetric.count_query)
- }
- return experiment.secondary_metrics[metricIdx].filters
- })()}
- setFilters={({ actions, events, data_warehouse }: Partial): void => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- const series = actionsAndEventsToSeries(
- { actions, events, data_warehouse } as any,
- true,
- MathAvailability.All
- )
-
- setTrendsMetric({
- metricIdx,
- series,
- isSecondary: true,
- })
- } else {
- if (actions?.length) {
- setExperiment({
- secondary_metrics: experiment.secondary_metrics.map((metric, idx) =>
- idx === metricIdx
- ? {
- ...metric,
- filters: {
- ...metric.filters,
- actions,
- events: undefined,
- data_warehouse: undefined,
- },
- }
- : metric
- ),
- })
- } else if (events?.length) {
- setExperiment({
- secondary_metrics: experiment.secondary_metrics.map((metric, idx) =>
- idx === metricIdx
- ? {
- ...metric,
- filters: {
- ...metric.filters,
- events,
- actions: undefined,
- data_warehouse: undefined,
- },
- }
- : metric
- ),
- })
- } else if (data_warehouse?.length) {
- setExperiment({
- secondary_metrics: experiment.secondary_metrics.map((metric, idx) =>
- idx === metricIdx
- ? {
- ...metric,
- filters: {
- ...metric.filters,
- data_warehouse,
- actions: undefined,
- events: undefined,
- },
- }
- : metric
- ),
- })
- }
- }
- }}
- typeKey="experiment-metric"
- buttonCopy="Add graph series"
- showSeriesIndicator={true}
- entitiesLimit={1}
- showNumericalPropsOnly={true}
- {...commonActionFilterProps}
- />
-
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- const val = currentMetric.count_query?.filterTestAccounts
- return hasFilters ? !!val : false
- }
- return hasFilters
- ? !!experiment.secondary_metrics[metricIdx].filters.filter_test_accounts
- : false
- })()}
- onChange={(checked: boolean) => {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- setTrendsMetric({
- metricIdx,
- filterTestAccounts: checked,
- isSecondary: true,
- })
- } else {
- setExperiment({
- secondary_metrics: experiment.secondary_metrics.map((metric, idx) =>
- idx === metricIdx
- ? {
- ...metric,
- filters: {
- ...metric.filters,
- filter_test_accounts: checked,
- },
- }
- : metric
- ),
- })
- }
- }}
- fullWidth
- />
-
- {isExperimentRunning && (
-
- Preview insights are generated based on {EXPERIMENT_DEFAULT_DURATION} days of data. This can cause a
- mismatch between the preview and the actual results.
-
- )}
-
- {/* :FLAG: CLEAN UP AFTER MIGRATION */}
- {
- // :FLAG: CLEAN UP AFTER MIGRATION
- if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
- return currentMetric.count_query
- }
- return filtersToQueryNode(experiment.secondary_metrics[metricIdx].filters)
- })(),
- showTable: false,
- showLastComputation: true,
- showLastComputationRefresh: false,
- }}
- readOnly
- />
-
- >
- )
-}
diff --git a/frontend/src/scenes/experiments/Metrics/TrendsMetricForm.tsx b/frontend/src/scenes/experiments/Metrics/TrendsMetricForm.tsx
new file mode 100644
index 0000000000000..f76d69664f008
--- /dev/null
+++ b/frontend/src/scenes/experiments/Metrics/TrendsMetricForm.tsx
@@ -0,0 +1,290 @@
+import { IconCheckCircle } from '@posthog/icons'
+import { LemonInput, LemonLabel, LemonTabs, LemonTag } from '@posthog/lemon-ui'
+import { useActions, useValues } from 'kea'
+import { TestAccountFilterSwitch } from 'lib/components/TestAccountFiltersSwitch'
+import { EXPERIMENT_DEFAULT_DURATION } from 'lib/constants'
+import { dayjs } from 'lib/dayjs'
+import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
+import { useState } from 'react'
+import { ActionFilter } from 'scenes/insights/filters/ActionFilter/ActionFilter'
+import { MathAvailability } from 'scenes/insights/filters/ActionFilter/ActionFilterRow/ActionFilterRow'
+import { teamLogic } from 'scenes/teamLogic'
+
+import { actionsAndEventsToSeries } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode'
+import { queryNodeToFilter } from '~/queries/nodes/InsightQuery/utils/queryNodeToFilter'
+import { Query } from '~/queries/Query/Query'
+import { ExperimentTrendsQuery, InsightQueryNode, NodeKind } from '~/queries/schema'
+import { BaseMathType, ChartDisplayType, FilterType } from '~/types'
+
+import { experimentLogic } from '../experimentLogic'
+import { commonActionFilterProps } from './Selectors'
+
+export function TrendsMetricForm({ isSecondary = false }: { isSecondary?: boolean }): JSX.Element {
+ const { experiment, isExperimentRunning, editingPrimaryMetricIndex, editingSecondaryMetricIndex } =
+ useValues(experimentLogic)
+ const { setTrendsMetric, setTrendsExposureMetric, setExperiment } = useActions(experimentLogic)
+ const { currentTeam } = useValues(teamLogic)
+ const hasFilters = (currentTeam?.test_account_filters || []).length > 0
+ const [activeTab, setActiveTab] = useState('main')
+
+ const metrics = isSecondary ? experiment.metrics_secondary : experiment.metrics
+ const metricIdx = isSecondary ? editingSecondaryMetricIndex : editingPrimaryMetricIndex
+
+ if (!metricIdx && metricIdx !== 0) {
+ return <>>
+ }
+
+ const currentMetric = metrics[metricIdx] as ExperimentTrendsQuery
+
+ return (
+ <>
+ setActiveTab(newKey)}
+ tabs={[
+ {
+ key: 'main',
+ label: 'Main metric',
+ content: (
+ <>
+
+ Name (optional)
+ {
+ setTrendsMetric({
+ metricIdx,
+ name: newName,
+ isSecondary,
+ })
+ }}
+ />
+
+ ): void => {
+ const series = actionsAndEventsToSeries(
+ { actions, events, data_warehouse } as any,
+ true,
+ MathAvailability.All
+ )
+
+ setTrendsMetric({
+ metricIdx,
+ series,
+ isSecondary,
+ })
+ }}
+ typeKey="experiment-metric"
+ buttonCopy="Add graph series"
+ showSeriesIndicator={true}
+ entitiesLimit={1}
+ showNumericalPropsOnly={true}
+ {...commonActionFilterProps}
+ />
+
+ {
+ setTrendsMetric({
+ metricIdx,
+ filterTestAccounts: checked,
+ isSecondary,
+ })
+ }}
+ fullWidth
+ />
+
+ {isExperimentRunning && (
+
+ Preview insights are generated based on {EXPERIMENT_DEFAULT_DURATION} days of
+ data. This can cause a mismatch between the preview and the actual results.
+
+ )}
+
+
+
+ >
+ ),
+ },
+ {
+ key: 'exposure',
+ label: 'Exposure',
+ content: (
+ <>
+
+
{
+ const metricsField = isSecondary ? 'metrics_secondary' : 'metrics'
+ setExperiment({
+ ...experiment,
+ [metricsField]: metrics.map((metric, idx) =>
+ idx === metricIdx
+ ? { ...metric, exposure_query: undefined }
+ : metric
+ ),
+ })
+ }}
+ >
+
+ Default
+ {!currentMetric.exposure_query && (
+
+ )}
+
+
+ Uses the number of unique users who trigger the{' '}
+ $feature_flag_called event as your exposure count. This
+ is the recommended setting for most experiments, as it accurately tracks
+ variant exposure.
+
+
+
{
+ const metricsField = isSecondary ? 'metrics_secondary' : 'metrics'
+ setExperiment({
+ ...experiment,
+ [metricsField]: metrics.map((metric, idx) =>
+ idx === metricIdx
+ ? {
+ ...metric,
+ exposure_query: {
+ kind: NodeKind.TrendsQuery,
+ series: [
+ {
+ kind: NodeKind.EventsNode,
+ name: '$feature_flag_called',
+ event: '$feature_flag_called',
+ math: BaseMathType.UniqueUsers,
+ },
+ ],
+ 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,
+ },
+ }
+ : metric
+ ),
+ })
+ }}
+ >
+
+ Custom
+ {currentMetric.exposure_query && (
+
+ )}
+
+
+ Define your own exposure metric for specific use cases, such as counting by
+ sessions instead of users. This gives you full control but requires careful
+ configuration.
+
+
+
+ {currentMetric.exposure_query && (
+ <>
+ ): void => {
+ const series = actionsAndEventsToSeries(
+ { actions, events, data_warehouse } as any,
+ true,
+ MathAvailability.All
+ )
+
+ setTrendsExposureMetric({
+ metricIdx,
+ series,
+ isSecondary,
+ })
+ }}
+ typeKey="experiment-metric"
+ buttonCopy="Add graph series"
+ showSeriesIndicator={true}
+ entitiesLimit={1}
+ showNumericalPropsOnly={true}
+ {...commonActionFilterProps}
+ />
+
+ {
+ const val = currentMetric.exposure_query?.filterTestAccounts
+ return hasFilters ? !!val : false
+ })()}
+ onChange={(checked: boolean) => {
+ setTrendsExposureMetric({
+ metricIdx,
+ filterTestAccounts: checked,
+ isSecondary,
+ })
+ }}
+ fullWidth
+ />
+
+ {isExperimentRunning && (
+
+ Preview insights are generated based on {EXPERIMENT_DEFAULT_DURATION}{' '}
+ days of data. This can cause a mismatch between the preview and the
+ actual results.
+
+ )}
+
+
+
+ >
+ )}
+ >
+ ),
+ },
+ ]}
+ />
+ >
+ )
+}
diff --git a/frontend/src/scenes/experiments/experimentLogic.tsx b/frontend/src/scenes/experiments/experimentLogic.tsx
index 44753c32582a8..beb7356103c97 100644
--- a/frontend/src/scenes/experiments/experimentLogic.tsx
+++ b/frontend/src/scenes/experiments/experimentLogic.tsx
@@ -220,12 +220,14 @@ export const experimentLogic = kea([
name,
series,
filterTestAccounts,
+ isSecondary = false,
}: {
metricIdx: number
name?: string
series?: any[]
filterTestAccounts?: boolean
- }) => ({ metricIdx, name, series, filterTestAccounts }),
+ isSecondary?: boolean
+ }) => ({ metricIdx, name, series, filterTestAccounts, isSecondary }),
setFunnelsMetric: ({
metricIdx,
name,
@@ -348,8 +350,9 @@ export const experimentLogic = kea([
[metricsKey]: metrics,
}
},
- setTrendsExposureMetric: (state, { metricIdx, name, series, filterTestAccounts }) => {
- const metrics = [...(state?.metrics || [])]
+ setTrendsExposureMetric: (state, { metricIdx, name, series, filterTestAccounts, isSecondary }) => {
+ const metricsKey = isSecondary ? 'metrics_secondary' : 'metrics'
+ const metrics = [...(state?.[metricsKey] || [])]
const metric = metrics[metricIdx]
metrics[metricIdx] = {
@@ -364,7 +367,7 @@ export const experimentLogic = kea([
return {
...state,
- metrics,
+ [metricsKey]: metrics,
}
},
setFunnelsMetric: (