Skip to content

Commit

Permalink
feat(experiments): new metrics UI tweaks (#27171)
Browse files Browse the repository at this point in the history
  • Loading branch information
jurajmajerik authored Dec 29, 2024
1 parent 6c58e55 commit 6463378
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 157 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export function ExperimentImplementationDetails({ experiment }: ExperimentImplem
}

return (
<div>
<div className="mb-4">
<h2 className="font-semibold text-lg mb-2">Implementation</h2>
<div className="border rounded bg-bg-light">
<div className="p-6 space-y-4">
Expand Down
79 changes: 62 additions & 17 deletions frontend/src/scenes/experiments/ExperimentView/ExperimentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import { SavedMetricModal } from '../Metrics/SavedMetricModal'
import { MetricsView } from '../MetricsView/MetricsView'
import {
ExperimentLoadingAnimation,
ExploreButton,
LoadingState,
NoResultsEmptyState,
PageHeaderCustom,
ResultsHeader,
ResultsQuery,
} from './components'
import { CumulativeExposuresChart } from './CumulativeExposuresChart'
import { DataCollection } from './DataCollection'
Expand All @@ -26,17 +28,58 @@ import { Overview } from './Overview'
import { ReleaseConditionsModal, ReleaseConditionsTable } from './ReleaseConditionsTable'
import { Results } from './Results'
import { SecondaryMetricsTable } from './SecondaryMetricsTable'
import { SummaryTable } from './SummaryTable'

const ResultsTab = (): JSX.Element => {
const { experiment, metricResults, featureFlags } = useValues(experimentLogic)
const result = metricResults?.[0]
const hasResultsInsight = result && result.insight
const NewResultsTab = (): JSX.Element => {
const { experiment, metricResults } = useValues(experimentLogic)
const hasSomeResults = metricResults?.some((result) => result?.insight)

const hasSinglePrimaryMetric = experiment.metrics.length === 1

return (
<div className="space-y-8">
{featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? (
<MetricsView />
) : hasResultsInsight ? (
<>
{!hasSomeResults && (
<>
{experiment.type === 'web' ? (
<WebExperimentImplementationDetails experiment={experiment} />
) : (
<ExperimentImplementationDetails experiment={experiment} />
)}
</>
)}
{/* Show overview if there's only a single primary metric */}
{hasSinglePrimaryMetric && (
<div className="mb-4">
<Overview />
</div>
)}
<MetricsView isSecondary={false} />
{/* Show detailed results if there's only a single primary metric */}
{hasSomeResults && hasSinglePrimaryMetric && (
<div>
<div className="pb-4">
<SummaryTable metric={experiment.metrics[0]} metricIndex={0} isSecondary={false} />
</div>
<div className="flex justify-end">
<ExploreButton result={metricResults?.[0] || null} size="xsmall" />
</div>
<div className="pb-4">
<ResultsQuery result={metricResults?.[0] || null} showTable={true} />
</div>
</div>
)}
<MetricsView isSecondary={true} />
</>
)
}

const OldResultsTab = (): JSX.Element => {
const { experiment, metricResults } = useValues(experimentLogic)
const hasSomeResults = metricResults?.some((result) => result?.insight)

return (
<>
{hasSomeResults ? (
<Results />
) : (
<>
Expand All @@ -54,15 +97,16 @@ const ResultsTab = (): JSX.Element => {
)}
</>
)}
{featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? (
<MetricsView isSecondary={true} />
) : (
<SecondaryMetricsTable experimentId={experiment.id} />
)}
</div>
<SecondaryMetricsTable experimentId={experiment.id} />
</>
)
}

const ResultsTab = (): JSX.Element => {
const { featureFlags } = useValues(experimentLogic)
return <>{featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? <NewResultsTab /> : <OldResultsTab />}</>
}

const VariantsTab = (): JSX.Element => {
return (
<div className="space-y-8">
Expand All @@ -87,8 +131,8 @@ export function ExperimentView(): JSX.Element {
} = useValues(experimentLogic)

const { setTabKey } = useActions(experimentLogic)
const result = metricResults?.[0]
const hasResultsInsight = result && result.insight
// Instead, check if any result in the array has an insight
const hasSomeResults = metricResults?.some((result) => result?.insight)

return (
<>
Expand All @@ -103,8 +147,9 @@ export function ExperimentView(): JSX.Element {
<ExperimentLoadingAnimation />
) : (
<>
{hasResultsInsight && !featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? (
{hasSomeResults && !featureFlags[FEATURE_FLAGS.EXPERIMENTS_MULTIPLE_METRICS] ? (
<div>
<h2 className="font-semibold text-lg">Summary</h2>
<Overview />
<LemonDivider className="mt-4" />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export function Overview({ metricIndex = 0 }: { metricIndex?: number }): JSX.Ele

return (
<div>
<h2 className="font-semibold text-lg">Summary</h2>
<div className="items-center inline-flex flex-wrap">
<WinningVariantText result={result} experimentId={experimentId} />
<SignificanceText metricIndex={metricIndex} />
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/experiments/ExperimentView/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function Results(): JSX.Element {
<div>
<ResultsHeader />
<SummaryTable metric={experiment.metrics[0]} />
<ResultsQuery targetResults={result} showTable={true} />
<ResultsQuery result={result} showTable={true} />
</div>
)
}
135 changes: 42 additions & 93 deletions frontend/src/scenes/experiments/ExperimentView/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,23 @@ import { FEATURE_FLAGS } from 'lib/constants'
import { dayjs } from 'lib/dayjs'
import { IconAreaChart } from 'lib/lemon-ui/icons'
import { More } from 'lib/lemon-ui/LemonButton/More'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { useEffect, useState } from 'react'
import { urls } from 'scenes/urls'

import { groupsModel } from '~/models/groupsModel'
import { filtersToQueryNode } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode'
import { queryFromFilters } from '~/queries/nodes/InsightViz/utils'
import { Query } from '~/queries/Query/Query'
import {
CachedExperimentFunnelsQueryResponse,
CachedExperimentTrendsQueryResponse,
ExperimentFunnelsQueryResponse,
ExperimentTrendsQueryResponse,
InsightQueryNode,
InsightVizNode,
NodeKind,
} from '~/queries/schema'
import {
Experiment,
Experiment as ExperimentType,
ExperimentIdType,
ExperimentResults,
InsightShortId,
InsightType,
} from '~/types'
import { Experiment, Experiment as ExperimentType, ExperimentIdType, InsightShortId, InsightType } from '~/types'

import { experimentLogic } from '../experimentLogic'
import { getExperimentStatus, getExperimentStatusColor } from '../experimentsLogic'
import { getExperimentInsightColour, transformResultFilters } from '../utils'
import { getExperimentInsightColour } from '../utils'

export function VariantTag({
experimentId,
Expand Down Expand Up @@ -128,79 +116,39 @@ export function ResultsTag({ metricIndex = 0 }: { metricIndex?: number }): JSX.E
}

export function ResultsQuery({
targetResults,
result,
showTable,
}: {
targetResults: ExperimentResults['result'] | ExperimentTrendsQueryResponse | ExperimentFunnelsQueryResponse | null
result: ExperimentTrendsQueryResponse | ExperimentFunnelsQueryResponse | null
showTable: boolean
}): JSX.Element {
const { featureFlags } = useValues(featureFlagLogic)
if (featureFlags[FEATURE_FLAGS.EXPERIMENTS_HOGQL]) {
const newQueryResults = targetResults as unknown as
| CachedExperimentTrendsQueryResponse
| CachedExperimentFunnelsQueryResponse

const query =
newQueryResults.kind === NodeKind.ExperimentTrendsQuery
? newQueryResults.count_query
: newQueryResults.funnels_query
const fakeInsightId = Math.random().toString(36).substring(2, 15)

return (
<Query
query={{
kind: NodeKind.InsightVizNode,
source: query,
showTable,
showLastComputation: true,
showLastComputationRefresh: false,
}}
context={{
insightProps: {
dashboardItemId: fakeInsightId as InsightShortId,
cachedInsight: {
short_id: fakeInsightId as InsightShortId,
query: {
kind: NodeKind.InsightVizNode,
source: query,
} as InsightVizNode,
result: newQueryResults?.insight,
disable_baseline: true,
},
doNotLoad: true,
},
}}
readOnly
/>
)
}

const oldQueryResults = targetResults as ExperimentResults['result']

if (!oldQueryResults?.filters) {
if (!result) {
return <></>
}

const query = result.kind === NodeKind.ExperimentTrendsQuery ? result.count_query : result.funnels_query
const fakeInsightId = Math.random().toString(36).substring(2, 15)

return (
<Query
query={{
kind: NodeKind.InsightVizNode,
source: filtersToQueryNode(transformResultFilters(oldQueryResults?.filters ?? {})),
source: query,
showTable,
showLastComputation: true,
showLastComputationRefresh: false,
}}
context={{
insightProps: {
dashboardItemId: oldQueryResults?.fakeInsightId as InsightShortId,
dashboardItemId: fakeInsightId as InsightShortId,
cachedInsight: {
short_id: oldQueryResults?.fakeInsightId as InsightShortId,
query: oldQueryResults?.filters
? queryFromFilters(transformResultFilters(oldQueryResults.filters))
: null,
result: oldQueryResults?.insight,
short_id: fakeInsightId as InsightShortId,
query: {
kind: NodeKind.InsightVizNode,
source: query,
} as InsightVizNode,
result: result?.insight,
disable_baseline: true,
last_refresh: oldQueryResults?.last_refresh,
},
doNotLoad: true,
},
Expand All @@ -211,15 +159,12 @@ export function ResultsQuery({
}

export function ExploreButton({
metricIndex = 0,
isSecondary = false,
result,
size = 'small',
}: {
metricIndex?: number
isSecondary?: boolean
result: ExperimentTrendsQueryResponse | ExperimentFunnelsQueryResponse | null
size?: 'xsmall' | 'small' | 'large'
}): JSX.Element {
const { metricResults, secondaryMetricResults } = useValues(experimentLogic)
const result = isSecondary ? secondaryMetricResults?.[metricIndex] : metricResults?.[metricIndex]

if (!result) {
return <></>
}
Expand All @@ -234,7 +179,7 @@ export function ExploreButton({
return (
<LemonButton
className="ml-auto -translate-y-2"
size="small"
size={size}
type="primary"
icon={<IconAreaChart />}
to={urls.insightNew(undefined, undefined, query)}
Expand All @@ -260,7 +205,7 @@ export function ResultsHeader(): JSX.Element {
</div>

<div className="w-1/2 flex flex-col justify-end">
<div className="ml-auto">{result && <ExploreButton />}</div>
<div className="ml-auto">{result && <ExploreButton result={result} />}</div>
</div>
</div>
)
Expand Down Expand Up @@ -580,12 +525,17 @@ export function PageHeaderCustom(): JSX.Element {
}

export function ShipVariantModal({ experimentId }: { experimentId: Experiment['id'] }): JSX.Element {
const { experiment, sortedWinProbabilities, isShipVariantModalOpen } = useValues(experimentLogic({ experimentId }))
const { experiment, isShipVariantModalOpen } = useValues(experimentLogic({ experimentId }))
const { closeShipVariantModal, shipVariant } = useActions(experimentLogic({ experimentId }))
const { aggregationLabel } = useValues(groupsModel)

const [selectedVariantKey, setSelectedVariantKey] = useState<string | null>()
useEffect(() => setSelectedVariantKey(sortedWinProbabilities(0)[0]?.key), [sortedWinProbabilities(0)])
useEffect(() => {
if (experiment.parameters?.feature_flag_variants?.length > 1) {
// First test variant selected by default
setSelectedVariantKey(experiment.parameters.feature_flag_variants[1].key)
}
}, [experiment])

const aggregationTargetName =
experiment.filters.aggregation_group_type_index != null
Expand Down Expand Up @@ -625,20 +575,19 @@ export function ShipVariantModal({ experimentId }: { experimentId: Experiment['i
className="w-full"
data-attr="metrics-selector"
value={selectedVariantKey}
onChange={(variantKey) => setSelectedVariantKey(variantKey)}
options={sortedWinProbabilities(0).map(({ key }) => ({
value: key,
label: (
<div className="space-x-2 inline-flex">
<VariantTag experimentId={experimentId} variantKey={key} />
{key === sortedWinProbabilities(0)[0]?.key && (
<LemonTag type="success">
<b className="uppercase">Winning</b>
</LemonTag>
)}
</div>
),
}))}
onChange={(variantKey) => {
setSelectedVariantKey(variantKey)
}}
options={
experiment.parameters?.feature_flag_variants?.map(({ key }) => ({
value: key,
label: (
<div className="space-x-2 inline-flex">
<VariantTag experimentId={experimentId} variantKey={key} />
</div>
),
})) || []
}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function SecondaryMetricChartModal({
</LemonButton>
}
>
<ResultsQuery targetResults={targetResults} showTable={false} />
<ResultsQuery result={targetResults} showTable={false} />
</LemonModal>
)
}
Loading

0 comments on commit 6463378

Please sign in to comment.