Skip to content

Commit e668dda

Browse files
authoredFeb 19, 2025··
feat(dashboards): More robust documentation for TimeSeriesWidgetVisualization (#85286)
Instead of sparse and duplicated documentation for `LineChartWidget`, `AreaChartWidget`, and `BarChartWidget`, I'm directing that documentation to a new page, for `TimeSeriesWidgetVisualization`. First of all, we are going to be discouraging people from using `LineChartWidget`, but rather asking them to compose widgets from the `Widget` component. Second of all, the `TimeSeriesWidgetVisualization` page needed to list really all the options and their significance. Once _this_ is merged, I'll be in touch with the affected teams about their code, if needed, but realistically I'll be the one who replaces uses of `XWidget` components with composed versions.
1 parent 4fb28df commit e668dda

9 files changed

+1294
-1007
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -1,221 +1,20 @@
11
import {Fragment} from 'react';
2-
import {useTheme} from '@emotion/react';
3-
import styled from '@emotion/styled';
4-
import moment from 'moment-timezone';
52

63
import JSXNode from 'sentry/components/stories/jsxNode';
7-
import SideBySide from 'sentry/components/stories/sideBySide';
8-
import SizingWindow from 'sentry/components/stories/sizingWindow';
94
import storyBook from 'sentry/stories/storyBook';
10-
import type {DateString} from 'sentry/types/core';
11-
import usePageFilters from 'sentry/utils/usePageFilters';
12-
13-
import type {Release, TimeSeries} from '../common/types';
14-
import {shiftTimeserieToNow} from '../timeSeriesWidget/shiftTimeserieToNow';
15-
16-
import {sampleLatencyTimeSeries} from './fixtures/sampleLatencyTimeSeries';
17-
import {sampleSpanDurationTimeSeries} from './fixtures/sampleSpanDurationTimeSeries';
18-
import {AreaChartWidget} from './areaChartWidget';
195

206
export default storyBook('AreaChartWidget', story => {
217
story('Getting Started', () => {
228
return (
239
<Fragment>
2410
<p>
25-
<JSXNode name="AreaChartWidget" /> is a Dashboard Widget Component. It displays
26-
a timeseries chart with multiple timeseries, and the timeseries are stacked.
27-
Each timeseries is shown using a solid block of color. This chart is used to
28-
visualize multiple timeseries that represent parts of something. For example, a
29-
chart that shows time spent in the app broken down by component. In all other
30-
ways, it behaves like <JSXNode name="LineChartWidget" />, though it doesn't
31-
support features like "Previous Period Data".
32-
</p>
33-
<p>
34-
<em>NOTE:</em> This chart is not appropriate for showing a single timeseries!
35-
You should use <JSXNode name="LineChartWidget" /> instead.
11+
🚨 <JSXNode name="AreaChartWidget" /> is deprecated! Instead, see the stories
12+
for <JSXNode name="Widget" />, which explain in detail how to compose your own
13+
widgets from standard components. If you want information on how to render a
14+
time series visualization, see the stories for
15+
<JSXNode name="TimeSeriesWidgetVisualization" />.
3616
</p>
3717
</Fragment>
3818
);
3919
});
40-
41-
story('Visualization', () => {
42-
const {selection} = usePageFilters();
43-
const {datetime} = selection;
44-
const {start, end} = datetime;
45-
46-
const latencyTimeSeries = toTimeSeriesSelection(sampleLatencyTimeSeries, start, end);
47-
48-
const spanDurationTimeSeries = toTimeSeriesSelection(
49-
sampleSpanDurationTimeSeries,
50-
start,
51-
end
52-
);
53-
54-
return (
55-
<Fragment>
56-
<p>
57-
The visualization of <JSXNode name="AreaChartWidget" /> a stacked area chart. It
58-
has some bells and whistles including automatic axes labels, and a hover
59-
tooltip. Like other widgets, it automatically fills the parent element.
60-
</p>
61-
<SmallSizingWindow>
62-
<AreaChartWidget
63-
title="Duration Breakdown"
64-
description="Explains what proportion of total duration is taken up by latency vs. span duration"
65-
timeSeries={[latencyTimeSeries, spanDurationTimeSeries]}
66-
/>
67-
</SmallSizingWindow>
68-
69-
<p>
70-
The <code>dataCompletenessDelay</code> prop indicates that this data is live,
71-
and the last few buckets might not have complete data. The delay is a number in
72-
seconds. Any data bucket that happens in that delay window will be plotted with
73-
a fainter fill. By default the delay is <code>0</code>.
74-
</p>
75-
76-
<SideBySide>
77-
<MediumWidget>
78-
<AreaChartWidget
79-
title="span.duration"
80-
dataCompletenessDelay={60 * 60 * 3}
81-
timeSeries={[
82-
shiftTimeserieToNow(latencyTimeSeries),
83-
shiftTimeserieToNow(spanDurationTimeSeries),
84-
]}
85-
/>
86-
</MediumWidget>
87-
</SideBySide>
88-
</Fragment>
89-
);
90-
});
91-
92-
story('State', () => {
93-
return (
94-
<Fragment>
95-
<p>
96-
<JSXNode name="AreaChartWidget" /> supports the usual loading and error states.
97-
The loading state shows a spinner. The error state shows a message, and an
98-
optional "Retry" button.
99-
</p>
100-
101-
<SideBySide>
102-
<SmallWidget>
103-
<AreaChartWidget title="Loading Count" isLoading />
104-
</SmallWidget>
105-
<SmallWidget>
106-
<AreaChartWidget title="Missing Count" />
107-
</SmallWidget>
108-
<SmallWidget>
109-
<AreaChartWidget
110-
title="Count Error"
111-
error={new Error('Something went wrong!')}
112-
/>
113-
</SmallWidget>
114-
<SmallWidget>
115-
<AreaChartWidget
116-
title="Data Error"
117-
error={new Error('Something went wrong!')}
118-
onRetry={() => {}}
119-
/>
120-
</SmallWidget>
121-
</SideBySide>
122-
</Fragment>
123-
);
124-
});
125-
126-
story('Colors', () => {
127-
const theme = useTheme();
128-
129-
return (
130-
<Fragment>
131-
<p>
132-
You can control the color of each timeseries by setting the <code>color</code>{' '}
133-
attribute to a string that contains a valid hex color code.
134-
</p>
135-
136-
<MediumWidget>
137-
<AreaChartWidget
138-
title="error_rate()"
139-
description="Rate of Errors"
140-
timeSeries={[
141-
{...sampleLatencyTimeSeries, color: theme.error},
142-
143-
{...sampleSpanDurationTimeSeries, color: theme.warning},
144-
]}
145-
/>
146-
</MediumWidget>
147-
</Fragment>
148-
);
149-
});
150-
151-
story('Releases', () => {
152-
const releases = [
153-
{
154-
version: 'ui@0.1.2',
155-
timestamp: sampleLatencyTimeSeries.data.at(2)?.timestamp,
156-
},
157-
{
158-
version: 'ui@0.1.3',
159-
timestamp: sampleLatencyTimeSeries.data.at(20)?.timestamp,
160-
},
161-
].filter(hasTimestamp);
162-
163-
return (
164-
<Fragment>
165-
<p>
166-
<JSXNode name="AreaChartWidget" /> supports the <code>releases</code> prop. If
167-
passed in, the widget will plot every release as a vertical line that overlays
168-
the chart data. Clicking on a release line will open the release details page.
169-
</p>
170-
171-
<MediumWidget>
172-
<AreaChartWidget
173-
title="error_rate()"
174-
timeSeries={[sampleLatencyTimeSeries, sampleSpanDurationTimeSeries]}
175-
releases={releases}
176-
/>
177-
</MediumWidget>
178-
</Fragment>
179-
);
180-
});
18120
});
182-
183-
const MediumWidget = styled('div')`
184-
width: 420px;
185-
height: 250px;
186-
`;
187-
188-
const SmallWidget = styled('div')`
189-
width: 360px;
190-
height: 160px;
191-
`;
192-
193-
const SmallSizingWindow = styled(SizingWindow)`
194-
width: 50%;
195-
height: 300px;
196-
`;
197-
198-
function toTimeSeriesSelection(
199-
timeSeries: TimeSeries,
200-
start: DateString | null,
201-
end: DateString | null
202-
): TimeSeries {
203-
return {
204-
...timeSeries,
205-
data: timeSeries.data.filter(datum => {
206-
if (start && moment(datum.timestamp).isBefore(moment.utc(start))) {
207-
return false;
208-
}
209-
210-
if (end && moment(datum.timestamp).isAfter(moment.utc(end))) {
211-
return false;
212-
}
213-
214-
return true;
215-
}),
216-
};
217-
}
218-
219-
function hasTimestamp(release: Partial<Release>): release is Release {
220-
return Boolean(release?.timestamp);
221-
}
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,20 @@
11
import {Fragment} from 'react';
2-
import styled from '@emotion/styled';
3-
import moment from 'moment-timezone';
42

53
import JSXNode from 'sentry/components/stories/jsxNode';
6-
import SideBySide from 'sentry/components/stories/sideBySide';
7-
import SizingWindow from 'sentry/components/stories/sizingWindow';
84
import storyBook from 'sentry/stories/storyBook';
9-
import type {DateString} from 'sentry/types/core';
10-
import usePageFilters from 'sentry/utils/usePageFilters';
11-
12-
import type {TimeSeries} from '../common/types';
13-
import {shiftTimeserieToNow} from '../timeSeriesWidget/shiftTimeserieToNow';
14-
15-
import {sampleLatencyTimeSeries} from './fixtures/sampleLatencyTimeSeries';
16-
import {sampleSpanDurationTimeSeries} from './fixtures/sampleSpanDurationTimeSeries';
17-
import {BarChartWidget} from './barChartWidget';
185

196
export default storyBook('BarChartWidget', story => {
207
story('Getting Started', () => {
218
return (
229
<Fragment>
2310
<p>
24-
<JSXNode name="BarChartWidget" /> is a Dashboard Widget Component. It displays a
25-
timeseries chart with multiple timeseries, and the timeseries and discontinuous.
26-
In all other ways, it behaves like <JSXNode name="AreaChartWidget" />
27-
</p>
28-
<p>
29-
<em>NOTE:</em> Prefer <JSXNode name="LineChartWidget" /> and{' '}
30-
<JSXNode name="AreaChartWidget" /> for timeseries visualizations! This should be
31-
used for discontinuous categorized data.
32-
</p>
33-
</Fragment>
34-
);
35-
});
36-
37-
story('Visualization', () => {
38-
const {selection} = usePageFilters();
39-
const {datetime} = selection;
40-
const {start, end} = datetime;
41-
42-
const latencyTimeSeries = toTimeSeriesSelection(sampleLatencyTimeSeries, start, end);
43-
44-
const spanDurationTimeSeries = toTimeSeriesSelection(
45-
sampleSpanDurationTimeSeries,
46-
start,
47-
end
48-
);
49-
50-
return (
51-
<Fragment>
52-
<p>
53-
The visualization of <JSXNode name="BarChartWidget" /> is a bar chart. It has
54-
some bells and whistles including automatic axes labels, and a hover tooltip.
55-
Like other widgets, it automatically fills the parent element.
11+
🚨 <JSXNode name="BarChartWidget" /> is deprecated! Instead, see the stories for{' '}
12+
<JSXNode name="Widget" />, which explain in detail how to compose your own
13+
widgets from standard components. If you want information on how to render a
14+
time series visualization, see the stories for
15+
<JSXNode name="TimeSeriesWidgetVisualization" />.
5616
</p>
57-
<p>
58-
The <code>stacked</code> boolean prop controls stacking. Bar charts are not
59-
stacked by default.
60-
</p>
61-
<SideBySide>
62-
<SmallSizingWindow>
63-
<BarChartWidget
64-
title="Duration Breakdown"
65-
description="Explains what proportion of total duration is taken up by latency vs. span duration"
66-
timeSeries={[
67-
shiftTimeserieToNow(latencyTimeSeries),
68-
shiftTimeserieToNow(spanDurationTimeSeries),
69-
]}
70-
dataCompletenessDelay={600}
71-
/>
72-
</SmallSizingWindow>
73-
<SmallSizingWindow>
74-
<BarChartWidget
75-
title="Duration Breakdown"
76-
description="Explains what proportion of total duration is taken up by latency vs. span duration"
77-
timeSeries={[
78-
shiftTimeserieToNow(latencyTimeSeries),
79-
shiftTimeserieToNow(spanDurationTimeSeries),
80-
]}
81-
stacked
82-
/>
83-
</SmallSizingWindow>
84-
</SideBySide>
8517
</Fragment>
8618
);
8719
});
8820
});
89-
90-
const SmallSizingWindow = styled(SizingWindow)`
91-
width: 50%;
92-
height: 300px;
93-
`;
94-
95-
function toTimeSeriesSelection(
96-
timeSeries: TimeSeries,
97-
start: DateString | null,
98-
end: DateString | null
99-
): TimeSeries {
100-
return {
101-
...timeSeries,
102-
data: timeSeries.data.filter(datum => {
103-
if (start && moment(datum.timestamp).isBefore(moment.utc(start))) {
104-
return false;
105-
}
106-
107-
if (end && moment(datum.timestamp).isAfter(moment.utc(end))) {
108-
return false;
109-
}
110-
111-
return true;
112-
}),
113-
};
114-
}

0 commit comments

Comments
 (0)
Please sign in to comment.