Skip to content

Commit ca2a460

Browse files
DominikB2014claude
andcommitted
feat(chartcuterie): Support multiple display types in Explore chart rendering
Refactor Explore chartcuterie rendering to use the plottable infrastructure instead of hardcoded LineSeries, ensuring Slack unfurl charts match the frontend rendering for line, bar, and area charts. Extract createPlottableFromTimeSeries to accept a DisplayType directly, and rename the Widget-dependent variant to createPlottableFromTimeSeriesAndWidget. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 884ade1 commit ca2a460

File tree

4 files changed

+59
-37
lines changed

4 files changed

+59
-37
lines changed

static/app/chartcuterie/explore.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import type {Theme} from '@emotion/react';
22

33
import {XAxis} from 'sentry/components/charts/components/xAxis';
4-
import {LineSeries} from 'sentry/components/charts/series/lineSeries';
5-
import {timeSeriesItemToEChartsDataPoint} from 'sentry/utils/timeSeries/timeSeriesItemToEChartsDataPoint';
4+
import {DisplayType} from 'sentry/views/dashboards/types';
65
import type {TimeSeries} from 'sentry/views/dashboards/widgets/common/types';
76
import {formatTimeSeriesLabel} from 'sentry/views/dashboards/widgets/timeSeriesWidget/formatters/formatTimeSeriesLabel';
7+
import type {ContinuousTimeSeriesPlottingOptions} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/continuousTimeSeries';
8+
import {createPlottableFromTimeSeries} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/createPlottableFromTimeSeries';
89

910
import {DEFAULT_FONT_FAMILY, makeSlackChartDefaults, slackChartSize} from './slack';
1011
import type {RenderDescriptor} from './types';
1112
import {ChartType} from './types';
1213

1314
type ExploreChartData = {
1415
timeSeries: TimeSeries[];
16+
type?: DisplayType;
1517
};
1618

1719
export const makeExploreCharts = (theme: Theme): Array<RenderDescriptor<ChartType>> => {
@@ -28,7 +30,7 @@ export const makeExploreCharts = (theme: Theme): Array<RenderDescriptor<ChartTyp
2830
exploreCharts.push({
2931
key: ChartType.SLACK_EXPLORE_LINE,
3032
getOption: (data: ExploreChartData) => {
31-
const {timeSeries} = data;
33+
const {timeSeries, type: displayType = DisplayType.LINE} = data;
3234

3335
if (timeSeries.length === 0) {
3436
return {
@@ -44,19 +46,21 @@ export const makeExploreCharts = (theme: Theme): Array<RenderDescriptor<ChartTyp
4446
if (!hasGroups) {
4547
const ts = timeSeries[0]!;
4648
const color = theme.chart.getColorPalette(0);
47-
const lineSeries = LineSeries({
48-
name: ts.yAxis,
49-
data: ts.values.map(timeSeriesItemToEChartsDataPoint),
50-
lineStyle: {color: color?.[0], opacity: 1},
51-
itemStyle: {color: color?.[0]},
49+
const plottingOptions: ContinuousTimeSeriesPlottingOptions = {
50+
color: color?.[0] ?? '',
51+
unit: ts.meta.valueUnit,
52+
yAxisPosition: 'left',
53+
};
54+
const plottable = createPlottableFromTimeSeries(displayType, ts, {
55+
color: color?.[0],
5256
});
5357

5458
return {
5559
...slackChartDefaults,
5660
xAxis: exploreXAxis,
5761
useUTC: true,
5862
color,
59-
series: [lineSeries],
63+
series: plottable?.toSeries(plottingOptions) ?? [],
6064
};
6165
}
6266

@@ -71,13 +75,17 @@ export const makeExploreCharts = (theme: Theme): Array<RenderDescriptor<ChartTyp
7175
color.push(theme.tokens.content.secondary);
7276
}
7377

74-
const series = sorted.map((ts, i) => {
75-
return LineSeries({
78+
const series = sorted.flatMap((ts, i) => {
79+
const plottingOptions: ContinuousTimeSeriesPlottingOptions = {
80+
color: color?.[i] ?? '',
81+
unit: ts.meta.valueUnit,
82+
yAxisPosition: 'left',
83+
};
84+
const plottable = createPlottableFromTimeSeries(displayType, ts, {
7685
name: formatTimeSeriesLabel(ts),
77-
data: ts.values.map(timeSeriesItemToEChartsDataPoint),
78-
lineStyle: {color: color?.[i], opacity: 1},
79-
itemStyle: {color: color?.[i]},
86+
color: color?.[i],
8087
});
88+
return plottable?.toSeries(plottingOptions) ?? [];
8189
});
8290

8391
return {

static/app/views/dashboards/widgetCard/visualizationWidget.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import type {
4242
TimeSeriesGroupBy,
4343
} from 'sentry/views/dashboards/widgets/common/types';
4444
import {formatBreakdownLegendValue} from 'sentry/views/dashboards/widgets/timeSeriesWidget/formatters/formatBreakdownLegendValue';
45-
import {createPlottableFromTimeSeries} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/createPlottableFromTimeSeries';
45+
import {createPlottableFromTimeSeriesAndWidget} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/createPlottableFromTimeSeries';
4646
import type {Plottable} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/plottable';
4747
import {Thresholds} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/thresholds';
4848
import {TimeSeriesWidgetVisualization} from 'sentry/views/dashboards/widgets/timeSeriesWidget/timeSeriesWidgetVisualization';
@@ -232,7 +232,7 @@ function VisualizationWidgetContent({
232232
}
233233

234234
const {timeSeries, label, seriesName, widgetQuery} = transformed;
235-
const plottable = createPlottableFromTimeSeries(
235+
const plottable = createPlottableFromTimeSeriesAndWidget(
236236
timeSeries,
237237
widget,
238238
label,

static/app/views/dashboards/widgets/timeSeriesWidget/plottables/createPlottableFromTimeSeries.spec.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import {Area} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/
55
import {Bars} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/bars';
66
import {Line} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/line';
77

8-
import {createPlottableFromTimeSeries} from './createPlottableFromTimeSeries';
8+
import {createPlottableFromTimeSeriesAndWidget} from './createPlottableFromTimeSeries';
99

10-
describe('createPlottableFromTimeSeries', () => {
10+
describe('createPlottableFromTimeSeriesAndWidget', () => {
1111
const mockTimeSeries = {
1212
yAxis: 'count()',
1313
values: [
@@ -23,28 +23,28 @@ describe('createPlottableFromTimeSeries', () => {
2323

2424
it('creates Line instance for LINE display type', () => {
2525
const widget = WidgetFixture({displayType: DisplayType.LINE});
26-
const plottable = createPlottableFromTimeSeries(mockTimeSeries, widget);
26+
const plottable = createPlottableFromTimeSeriesAndWidget(mockTimeSeries, widget);
2727

2828
expect(plottable).toBeInstanceOf(Line);
2929
});
3030

3131
it('creates Area instance for AREA display type', () => {
3232
const widget = WidgetFixture({displayType: DisplayType.AREA});
33-
const plottable = createPlottableFromTimeSeries(mockTimeSeries, widget);
33+
const plottable = createPlottableFromTimeSeriesAndWidget(mockTimeSeries, widget);
3434

3535
expect(plottable).toBeInstanceOf(Area);
3636
});
3737

3838
it('creates Bars instance for BAR display type', () => {
3939
const widget = WidgetFixture({displayType: DisplayType.BAR});
40-
const plottable = createPlottableFromTimeSeries(mockTimeSeries, widget);
40+
const plottable = createPlottableFromTimeSeriesAndWidget(mockTimeSeries, widget);
4141

4242
expect(plottable).toBeInstanceOf(Bars);
4343
});
4444

4545
it('returns null for TABLE display type', () => {
4646
const widget = WidgetFixture({displayType: DisplayType.TABLE});
47-
const plottable = createPlottableFromTimeSeries(mockTimeSeries, widget);
47+
const plottable = createPlottableFromTimeSeriesAndWidget(mockTimeSeries, widget);
4848

4949
expect(plottable).toBeNull();
5050
});

static/app/views/dashboards/widgets/timeSeriesWidget/plottables/createPlottableFromTimeSeries.tsx

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,43 @@ import {Bars} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/
66
import {Line} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/line';
77
import type {Plottable} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/plottable';
88

9+
type PlottableConfig = {
10+
alias?: string;
11+
color?: string;
12+
name?: string;
13+
stack?: string;
14+
};
15+
916
export function createPlottableFromTimeSeries(
17+
displayType: DisplayType,
1018
timeSeries: TimeSeries,
11-
widget: Widget,
12-
alias?: string,
13-
name?: string,
14-
color?: string
19+
config?: PlottableConfig
1520
): Plottable | null {
16-
const shouldStack = widget.queries[0]?.columns.length! > 0;
17-
18-
const {displayType, title} = widget;
1921
switch (displayType) {
2022
case DisplayType.LINE:
21-
return new Line(timeSeries, {alias, name, color});
23+
return new Line(timeSeries, config);
2224
case DisplayType.AREA:
23-
return new Area(timeSeries, {alias, name, color});
25+
return new Area(timeSeries, config);
2426
case DisplayType.BAR:
25-
return new Bars(timeSeries, {
26-
stack: shouldStack ? title : undefined,
27-
alias,
28-
name,
29-
color,
30-
});
27+
return new Bars(timeSeries, config);
3128
default:
3229
return null;
3330
}
3431
}
32+
33+
export function createPlottableFromTimeSeriesAndWidget(
34+
timeSeries: TimeSeries,
35+
widget: Widget,
36+
alias?: string,
37+
name?: string,
38+
color?: string
39+
): Plottable | null {
40+
const shouldStack = widget.queries[0]?.columns.length! > 0;
41+
42+
return createPlottableFromTimeSeries(widget.displayType, timeSeries, {
43+
alias,
44+
name,
45+
color,
46+
stack: shouldStack ? widget.title : undefined,
47+
});
48+
}

0 commit comments

Comments
 (0)