Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import React, { useMemo } from 'react'

import { computeVisibleXLabels, useChartLayout } from '@posthog/quill-charts'

import { AnnotationsOverlay, type AnnotationsChartGeometry } from 'lib/components/AnnotationsOverlay'
import { AnnotationsOverlay } from './AnnotationsOverlay'
import type { AnnotationsChartGeometry } from './useAnnotationsPositioning'

interface AnnotationsLayerProps {
/** Numeric insight id used by the annotations logic. Pass `'new'` for unsaved insights. */
Expand Down
1 change: 1 addition & 0 deletions frontend/src/queries/Query/Query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export function Query<Q extends Node>(props: QueryProps<Q>): JSX.Element | null
context={queryContext}
readOnly={readOnly}
embedded={embedded}
inSharedMode={inSharedMode}
editMode={!!editMode}
variablesOverride={props.variablesOverride}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useCallback } from 'react'

import { TimeSeriesBarChart, type PointClickData } from '@posthog/quill-charts'

import { AnnotationsLayer } from 'lib/components/AnnotationsOverlay/AnnotationsLayer'

import { makeChartErrorHandler } from 'products/product_analytics/frontend/insights/trends/shared/chartErrorHandler'

import { type SqlChartProps } from './SqlChart'
Expand Down Expand Up @@ -38,7 +40,11 @@ export const SqlBarGraph = (props: SqlChartProps): JSX.Element => {
config={model.config}
onPointClick={onPointClickProp ? onPointClick : undefined}
onError={handleChartError}
/>
>
{props.showAnnotations && props.insightNumericId && (
<AnnotationsLayer insightNumericId={props.insightNumericId} dates={model.labels} />
)}
</TimeSeriesBarChart>
)}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export type SqlChartProps = {
presetChartHeight?: boolean
dashboardId?: string
goalLines?: GoalLine[]
insightNumericId?: number | 'new'
showAnnotations?: boolean
className?: string
/** Called when the user clicks a data point. Receives the series key, x-axis index, and label.
* When provided, the SQL chart shows a "click to inspect" hint in the tooltip. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import clsx from 'clsx'

import { TimeSeriesComboChart } from '@posthog/quill-charts'

import { AnnotationsLayer } from 'lib/components/AnnotationsOverlay/AnnotationsLayer'

import { makeChartErrorHandler } from 'products/product_analytics/frontend/insights/trends/shared/chartErrorHandler'

import { type SqlChartProps } from './SqlChart'
Expand Down Expand Up @@ -34,7 +36,11 @@ export const SqlComboGraph = (props: SqlChartProps): JSX.Element => {
theme={model.theme}
config={model.config}
onError={handleChartError}
/>
>
{props.showAnnotations && props.insightNumericId && (
<AnnotationsLayer insightNumericId={props.insightNumericId} dates={model.labels} />
)}
</TimeSeriesComboChart>
)}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
renderWithInsights,
sqlChart,
} from '~/test/insight-testing'
import { ChartDisplayType } from '~/types'
import { buildAnnotation } from '~/test/insight-testing/test-data'
import { AnnotationScope, ChartDisplayType } from '~/types'

import { AxisSeries } from '../../dataVisualizationLogic'
import { SqlChartProps } from './SqlChart'
Expand Down Expand Up @@ -115,6 +116,58 @@ const renderLine = (
})

describe('SqlLineGraph', () => {
describe('annotations', () => {
it('renders annotations when enabled', async () => {
renderDataVisualization({
query: buildDataVisualizationQuery({
display: ChartDisplayType.ActionsLineGraph,
chartSettings: { xAxis: { column: 'month' }, showAnnotations: true },
}),
response: twoSeries(),
mocks: {
annotations: [
buildAnnotation({
scope: AnnotationScope.Project,
content: 'Feature released',
date_marker: '2025-12-01T12:00:00Z',
}),
],
},
})

await waitFor(() => {
expect(
[...document.querySelectorAll<HTMLButtonElement>('.AnnotationsBadge')].some(
(element) => element.textContent === '1'
)
).toBe(true)
})
})

it('does not render annotations for a categorical X axis', async () => {
renderDataVisualization({
query: buildDataVisualizationQuery({
display: ChartDisplayType.ActionsLineGraph,
chartSettings: { xAxis: { column: 'category' }, showAnnotations: true },
}),
response: {
columns: ['category', 'accounts'],
types: [
['category', 'String'],
['accounts', 'UInt64'],
],
results: [
['small', 1],
['large', 2],
],
},
})

await screen.findByLabelText(/chart with/i)
expect(document.querySelectorAll('.AnnotationsBadge')).toHaveLength(0)
})
})

describe('y-axis tick formatting', () => {
const waitForYTicks = async (): Promise<string[]> => {
await waitFor(() => expect(getHogChart().yTicks().length).toBeGreaterThan(0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { useCallback } from 'react'

import { DefaultTooltip, TimeSeriesLineChart, type PointClickData, type TooltipContext } from '@posthog/quill-charts'

import { AnnotationsLayer } from 'lib/components/AnnotationsOverlay/AnnotationsLayer'

import { makeChartErrorHandler } from 'products/product_analytics/frontend/insights/trends/shared/chartErrorHandler'

import { type SqlChartProps } from './SqlChart'
Expand Down Expand Up @@ -73,7 +75,11 @@ export const SqlLineGraph = (props: SqlChartProps): JSX.Element => {
tooltip={onPointClickProp ? renderTooltip : undefined}
onPointClick={onPointClickProp ? onPointClick : undefined}
onError={handleChartError}
/>
>
{props.showAnnotations && props.insightNumericId && (
<AnnotationsLayer insightNumericId={props.insightNumericId} dates={model.labels} />
)}
</TimeSeriesLineChart>
)}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { cleanup, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { BindLogic } from 'kea'

import { DataVisualizationNode, NodeKind } from '~/queries/schema/schema-general'
import { DataVisualizationNode, HogQLQueryResponse, NodeKind } from '~/queries/schema/schema-general'
import { initKeaTests } from '~/test/init'
import { ChartDisplayType } from '~/types'

Expand Down Expand Up @@ -60,6 +60,63 @@ describe('DisplayTab', () => {
expect(screen.queryByText('Show labels')).not.toBeInTheDocument()
})

it('keeps annotations off by default and persists the toggle', async () => {
initKeaTests()

const key = 'display-tab-annotations-test'
let query: DataVisualizationNode = {
kind: NodeKind.DataVisualizationNode,
source: {
kind: NodeKind.HogQLQuery,
query: 'select day, accounts from numbers(2)',
},
display: ChartDisplayType.ActionsLineGraph,
chartSettings: { xAxis: { column: 'day' } },
}

const props: DataVisualizationLogicProps = {
key,
query,
dataNodeCollectionId: key,
cachedResults: {
columns: ['day', 'accounts'],
types: [
['day', 'Date'],
['accounts', 'UInt64'],
],
results: [
['2026-01-01', 1],
['2026-01-02', 2],
],
hogql: '',
hasMore: false,
} as HogQLQueryResponse,
setQuery: (setter) => {
query = setter(query)
},
}

dataVisualizationLogic(props).mount()
displayLogic({ key }).mount()

render(
<BindLogic logic={dataVisualizationLogic} props={props}>
<BindLogic logic={displayLogic} props={{ key }}>
<DisplayTab />
</BindLogic>
</BindLogic>
)

const annotationsToggle = await screen.findByLabelText('Show annotations')
expect(annotationsToggle).not.toBeChecked()

await userEvent.click(annotationsToggle)

await waitFor(() => {
expect(query.chartSettings?.showAnnotations).toBe(true)
})
})

it('persists chart axis labels without dropping existing axis settings', async () => {
initKeaTests()

Expand Down Expand Up @@ -170,6 +227,7 @@ describe('DisplayTab', () => {
expect(screen.queryByText('Right Y-axis')).not.toBeInTheDocument()
expect(screen.queryByText('Goals')).not.toBeInTheDocument()
expect(screen.queryByText('Show total row')).not.toBeInTheDocument()
expect(screen.queryByText('Show annotations')).not.toBeInTheDocument()

await user.click(screen.getByText('Exclude outliers'))
await user.click(screen.getByText('Y-axis'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const LINE_STYLE_OPTIONS: { value: 'smooth' | 'linear'; label: string }[] = [
]

export const DisplayTab = (): JSX.Element => {
const { effectiveVisualizationType } = useValues(dataVisualizationLogic)
const { effectiveVisualizationType, xData } = useValues(dataVisualizationLogic)
const { goalLines, chartSettings } = useValues(displayLogic)
const { addGoalLine, updateGoalLine, removeGoalLine, updateChartSettings } = useActions(displayLogic)

Expand All @@ -49,6 +49,12 @@ export const DisplayTab = (): JSX.Element => {
const isLineChart =
effectiveVisualizationType === ChartDisplayType.ActionsLineGraph ||
effectiveVisualizationType === ChartDisplayType.ActionsAreaGraph
const isDateXAxis = xData?.column.type.name === 'DATE' || xData?.column.type.name === 'DATETIME'
const supportsAnnotations =
isDateXAxis &&
(isLineChart ||
effectiveVisualizationType === ChartDisplayType.ActionsBar ||
effectiveVisualizationType === ChartDisplayType.ActionsStackedBar)

const renderYAxisSettings = (name: 'leftYAxisSettings' | 'rightYAxisSettings'): JSX.Element => {
const leftPlaceholder = isSingleAxisChart ? 'Y-axis label' : 'Left Y-axis label'
Expand Down Expand Up @@ -146,6 +152,17 @@ export const DisplayTab = (): JSX.Element => {
}}
/>
)}
{supportsAnnotations && (
<LemonSwitch
className="flex-1 w-full"
data-attr="data-visualization-show-annotations"
label="Show annotations"
checked={chartSettings.showAnnotations ?? false}
onChange={(value) => {
updateChartSettings({ showAnnotations: value })
}}
/>
)}
{isPieChart ? (
<>
<div className="flex flex-col gap-1">
Expand Down
29 changes: 19 additions & 10 deletions frontend/src/queries/nodes/DataVisualization/DataVisualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface DataTableVisualizationProps {
editMode?: boolean
readOnly?: boolean
embedded?: boolean
inSharedMode?: boolean
exportContext?: ExportContext
/** Dashboard variables to override the ones in the query */
variablesOverride?: Record<string, HogQLVariable> | null
Expand All @@ -82,6 +83,7 @@ export function DataTableVisualization({
attachTo,
editMode,
embedded,
inSharedMode,
}: DataTableVisualizationProps): JSX.Element {
const [key] = useState(`DataVisualizationNode.${uniqueKey ?? uniqueNode++}`)
const queryRef = useRef(query)
Expand Down Expand Up @@ -158,6 +160,7 @@ export function DataTableVisualization({
exportContext={exportContext}
editMode={editMode}
embedded={embedded}
inSharedMode={inSharedMode}
/>
</BindLogic>
</BindLogic>
Expand Down Expand Up @@ -219,6 +222,8 @@ function InternalDataTableVisualization(props: DataTableVisualizationProps): JSX
[props.setQuery, props.query] // oxlint-disable-line react-hooks/exhaustive-deps
)

const isDateXAxis = xData?.column.type.name === 'DATE' || xData?.column.type.name === 'DATETIME'

let component: JSX.Element | null = null

if (responseError) {
Expand Down Expand Up @@ -263,16 +268,20 @@ function InternalDataTableVisualization(props: DataTableVisualizationProps): JSX
const _xData = seriesBreakdownData.xData.data.length ? seriesBreakdownData.xData : xData
const _yData = seriesBreakdownData.xData.data.length ? seriesBreakdownData.seriesData : yData
component = (
<SqlChart
className="p-3"
xData={_xData}
yData={_yData}
visualizationType={effectiveVisualizationType}
chartSettings={chartSettings}
dashboardId={dashboardId}
goalLines={[...alertThresholdLines, ...goalLines]}
presetChartHeight={presetChartHeight}
/>
<BindLogic logic={insightLogic} props={alertsInsightProps}>
<SqlChart
className="p-3"
xData={_xData}
yData={_yData}
visualizationType={effectiveVisualizationType}
chartSettings={chartSettings}
dashboardId={dashboardId}
goalLines={[...alertThresholdLines, ...goalLines]}
insightNumericId={insight?.id || 'new'}
showAnnotations={!props.inSharedMode && isDateXAxis && chartSettings.showAnnotations === true}
presetChartHeight={presetChartHeight}
/>
</BindLogic>
)
} else if (effectiveVisualizationType === ChartDisplayType.ActionsPie) {
const _xData = seriesBreakdownData.xData.data.length ? seriesBreakdownData.xData : xData
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/queries/schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/src/queries/schema/schema-general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,7 @@ export interface ChartSettings {
showXAxisBorder?: boolean
showYAxisBorder?: boolean
showLegend?: boolean
showAnnotations?: boolean
showValuesOnSeries?: boolean
// Deprecated: superseded by `pie.showTotal`. Retained so pre-existing pie-chart insights still
// validate (ChartSettings is `extra="forbid"`). Read as a fallback in the pie chart components.
Expand Down
Loading
Loading