Skip to content

Commit

Permalink
feat(experiments): add delta timeseries chart UI (#27960)
Browse files Browse the repository at this point in the history
  • Loading branch information
jurajmajerik authored and adamleithp committed Jan 29, 2025
1 parent 1bf5843 commit 6d8e38b
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export const FEATURE_FLAGS = {
WEB_REVENUE_TRACKING: 'web-revenue-tracking', // owner: @robbie-c #team-web-analytics
LLM_OBSERVABILITY: 'llm-observability', // owner: #team-ai-product-manager
ONBOARDING_SESSION_REPLAY_SEPERATE_STEP: 'onboarding-session-replay-separate-step', // owner: @joshsny #team-growth
EXPERIMENT_INTERVAL_TIMESERIES: 'experiments-interval-timeseries', // owner: @jurajmajerik #team-experiments
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { MetricModal } from '../Metrics/MetricModal'
import { MetricSourceModal } from '../Metrics/MetricSourceModal'
import { SharedMetricModal } from '../Metrics/SharedMetricModal'
import { MetricsView } from '../MetricsView/MetricsView'
import { VariantDeltaTimeseries } from '../MetricsView/VariantDeltaTimeseries'
import { ExperimentLoadingAnimation, ExploreButton, LoadingState, PageHeaderCustom, ResultsQuery } from './components'
import { CumulativeExposuresChart } from './CumulativeExposuresChart'
import { DataCollection } from './DataCollection'
Expand Down Expand Up @@ -127,6 +128,8 @@ export function ExperimentView(): JSX.Element {

<DistributionModal experimentId={experimentId} />
<ReleaseConditionsModal experimentId={experimentId} />

<VariantDeltaTimeseries />
</>
)}
</div>
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/scenes/experiments/MetricsView/DeltaChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { LemonBanner, LemonButton, LemonModal, LemonTag, LemonTagType, Tooltip } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { FEATURE_FLAGS } from 'lib/constants'
import { LemonProgress } from 'lib/lemon-ui/LemonProgress'
import { humanFriendlyNumber } from 'lib/utils'
import { useEffect, useRef, useState } from 'react'
Expand Down Expand Up @@ -104,6 +105,7 @@ export function DeltaChart({
countDataForVariant,
exposureCountDataForVariant,
metricResultsLoading,
featureFlags,
} = useValues(experimentLogic)

const { experiment } = useValues(experimentLogic)
Expand All @@ -112,6 +114,7 @@ export function DeltaChart({
openSecondaryMetricModal,
openPrimarySharedMetricModal,
openSecondarySharedMetricModal,
openVariantDeltaTimeseriesModal,
} = useActions(experimentLogic)
const [tooltipData, setTooltipData] = useState<{ x: number; y: number; variant: string } | null>(null)
const [emptyStateTooltipVisible, setEmptyStateTooltipVisible] = useState(true)
Expand Down Expand Up @@ -401,6 +404,16 @@ export function DeltaChart({
})
}}
onMouseLeave={() => setTooltipData(null)}
onClick={() => {
if (featureFlags[FEATURE_FLAGS.EXPERIMENT_INTERVAL_TIMESERIES]) {
openVariantDeltaTimeseriesModal()
}
}}
className={
featureFlags[FEATURE_FLAGS.EXPERIMENT_INTERVAL_TIMESERIES]
? 'cursor-pointer'
: ''
}
>
{/* Add variant name using VariantTag */}
<foreignObject
Expand Down
151 changes: 151 additions & 0 deletions frontend/src/scenes/experiments/MetricsView/VariantDeltaTimeseries.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Chart, ChartConfiguration } from 'chart.js/auto'
import { useActions, useValues } from 'kea'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonModal } from 'lib/lemon-ui/LemonModal'
import { useEffect } from 'react'

import { experimentLogic } from '../experimentLogic'

const DELTA = [0.16, 0.17, 0.15, 0.16, 0.14, 0.15, 0.145, 0.15, 0.155, 0.148, 0.15, 0.147, 0.152, 0.15]
const UPPER_BOUND = [0.26, 0.27, 0.24, 0.24, 0.21, 0.21, 0.2, 0.2, 0.195, 0.183, 0.182, 0.177, 0.182, 0.18]
const LOWER_BOUND = [0.06, 0.07, 0.06, 0.08, 0.07, 0.09, 0.09, 0.1, 0.115, 0.113, 0.118, 0.117, 0.122, 0.12]

export const VariantDeltaTimeseries = (): JSX.Element => {
const { closeVariantDeltaTimeseriesModal } = useActions(experimentLogic)
const { isVariantDeltaTimeseriesModalOpen } = useValues(experimentLogic)

useEffect(() => {
if (isVariantDeltaTimeseriesModalOpen) {
setTimeout(() => {
const ctx = document.getElementById('variantDeltaChart') as HTMLCanvasElement
if (!ctx) {
console.error('Canvas element not found')
return
}

const existingChart = Chart.getChart(ctx)
if (existingChart) {
existingChart.destroy()
}

ctx.style.width = '100%'
ctx.style.height = '100%'

const data = {
labels: [
'Day 1',
'Day 2',
'Day 3',
'Day 4',
'Day 5',
'Day 6',
'Day 7',
'Day 8',
'Day 9',
'Day 10',
'Day 11',
'Day 12',
'Day 13',
'Day 14',
],
datasets: [
{
label: 'Upper Bound',
data: UPPER_BOUND,
borderColor: 'rgba(200, 200, 200, 1)',
fill: false,
tension: 0,
pointRadius: 0,
},
{
label: 'Lower Bound',
data: LOWER_BOUND,
borderColor: 'rgba(200, 200, 200, 1)',
fill: '-1',
backgroundColor: 'rgba(200, 200, 200, 0.2)',
tension: 0,
pointRadius: 0,
},
{
label: 'Delta',
data: DELTA,
borderColor: 'rgba(0, 100, 255, 1)',
borderWidth: 2,
fill: false,
tension: 0,
pointRadius: 0,
},
],
}

const config: ChartConfiguration = {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: 'nearest',
axis: 'x',
},
scales: {
y: {
beginAtZero: true,
grid: {
display: false,
},
ticks: {
count: 6,
callback: (value) => `${(Number(value) * 100).toFixed(1)}%`,
},
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
callbacks: {
labelPointStyle: function () {
return {
pointStyle: 'circle',
rotation: 0,
}
},
},
usePointStyle: true,
boxWidth: 16,
boxHeight: 1,
},
// @ts-expect-error Types of library are out of date
crosshair: false,
},
},
}

new Chart(ctx, config)
}, 0)
}
}, [isVariantDeltaTimeseriesModalOpen])

return (
<LemonModal
isOpen={isVariantDeltaTimeseriesModalOpen}
onClose={() => {
closeVariantDeltaTimeseriesModal()
}}
width={800}
title="Variant performance over time"
footer={
<LemonButton form="secondary-metric-modal-form" type="secondary" onClick={() => {}}>
Close
</LemonButton>
}
>
<div className="relative h-[400px]">
<canvas id="variantDeltaChart" />
</div>
</LemonModal>
)
}
9 changes: 9 additions & 0 deletions frontend/src/scenes/experiments/experimentLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ export const experimentLogic = kea<experimentLogicType>([
closePrimarySharedMetricModal: true,
openSecondarySharedMetricModal: (sharedMetricId: SharedMetric['id'] | null) => ({ sharedMetricId }),
closeSecondarySharedMetricModal: true,
openVariantDeltaTimeseriesModal: true,
closeVariantDeltaTimeseriesModal: true,
addSharedMetricsToExperiment: (
sharedMetricIds: SharedMetric['id'][],
metadata: { type: 'primary' | 'secondary' }
Expand Down Expand Up @@ -567,6 +569,13 @@ export const experimentLogic = kea<experimentLogicType>([
closeSecondarySharedMetricModal: () => false,
},
],
isVariantDeltaTimeseriesModalOpen: [
false,
{
openVariantDeltaTimeseriesModal: () => true,
closeVariantDeltaTimeseriesModal: () => false,
},
],
isCreatingExperimentDashboard: [
false,
{
Expand Down

0 comments on commit 6d8e38b

Please sign in to comment.