-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dashboards): More robust documentation for `TimeSeriesWidgetVisu…
…alization` (#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.
- Loading branch information
Showing
9 changed files
with
1,294 additions
and
1,007 deletions.
There are no files selected for viewing
211 changes: 5 additions & 206 deletions
211
static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,221 +1,20 @@ | ||
import {Fragment} from 'react'; | ||
import {useTheme} from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
import moment from 'moment-timezone'; | ||
|
||
import JSXNode from 'sentry/components/stories/jsxNode'; | ||
import SideBySide from 'sentry/components/stories/sideBySide'; | ||
import SizingWindow from 'sentry/components/stories/sizingWindow'; | ||
import storyBook from 'sentry/stories/storyBook'; | ||
import type {DateString} from 'sentry/types/core'; | ||
import usePageFilters from 'sentry/utils/usePageFilters'; | ||
|
||
import type {Release, TimeSeries} from '../common/types'; | ||
import {shiftTimeserieToNow} from '../timeSeriesWidget/shiftTimeserieToNow'; | ||
|
||
import {sampleLatencyTimeSeries} from './fixtures/sampleLatencyTimeSeries'; | ||
import {sampleSpanDurationTimeSeries} from './fixtures/sampleSpanDurationTimeSeries'; | ||
import {AreaChartWidget} from './areaChartWidget'; | ||
|
||
export default storyBook('AreaChartWidget', story => { | ||
story('Getting Started', () => { | ||
return ( | ||
<Fragment> | ||
<p> | ||
<JSXNode name="AreaChartWidget" /> is a Dashboard Widget Component. It displays | ||
a timeseries chart with multiple timeseries, and the timeseries are stacked. | ||
Each timeseries is shown using a solid block of color. This chart is used to | ||
visualize multiple timeseries that represent parts of something. For example, a | ||
chart that shows time spent in the app broken down by component. In all other | ||
ways, it behaves like <JSXNode name="LineChartWidget" />, though it doesn't | ||
support features like "Previous Period Data". | ||
</p> | ||
<p> | ||
<em>NOTE:</em> This chart is not appropriate for showing a single timeseries! | ||
You should use <JSXNode name="LineChartWidget" /> instead. | ||
🚨 <JSXNode name="AreaChartWidget" /> is deprecated! Instead, see the stories | ||
for <JSXNode name="Widget" />, which explain in detail how to compose your own | ||
widgets from standard components. If you want information on how to render a | ||
time series visualization, see the stories for | ||
<JSXNode name="TimeSeriesWidgetVisualization" />. | ||
</p> | ||
</Fragment> | ||
); | ||
}); | ||
|
||
story('Visualization', () => { | ||
const {selection} = usePageFilters(); | ||
const {datetime} = selection; | ||
const {start, end} = datetime; | ||
|
||
const latencyTimeSeries = toTimeSeriesSelection(sampleLatencyTimeSeries, start, end); | ||
|
||
const spanDurationTimeSeries = toTimeSeriesSelection( | ||
sampleSpanDurationTimeSeries, | ||
start, | ||
end | ||
); | ||
|
||
return ( | ||
<Fragment> | ||
<p> | ||
The visualization of <JSXNode name="AreaChartWidget" /> a stacked area chart. It | ||
has some bells and whistles including automatic axes labels, and a hover | ||
tooltip. Like other widgets, it automatically fills the parent element. | ||
</p> | ||
<SmallSizingWindow> | ||
<AreaChartWidget | ||
title="Duration Breakdown" | ||
description="Explains what proportion of total duration is taken up by latency vs. span duration" | ||
timeSeries={[latencyTimeSeries, spanDurationTimeSeries]} | ||
/> | ||
</SmallSizingWindow> | ||
|
||
<p> | ||
The <code>dataCompletenessDelay</code> prop indicates that this data is live, | ||
and the last few buckets might not have complete data. The delay is a number in | ||
seconds. Any data bucket that happens in that delay window will be plotted with | ||
a fainter fill. By default the delay is <code>0</code>. | ||
</p> | ||
|
||
<SideBySide> | ||
<MediumWidget> | ||
<AreaChartWidget | ||
title="span.duration" | ||
dataCompletenessDelay={60 * 60 * 3} | ||
timeSeries={[ | ||
shiftTimeserieToNow(latencyTimeSeries), | ||
shiftTimeserieToNow(spanDurationTimeSeries), | ||
]} | ||
/> | ||
</MediumWidget> | ||
</SideBySide> | ||
</Fragment> | ||
); | ||
}); | ||
|
||
story('State', () => { | ||
return ( | ||
<Fragment> | ||
<p> | ||
<JSXNode name="AreaChartWidget" /> supports the usual loading and error states. | ||
The loading state shows a spinner. The error state shows a message, and an | ||
optional "Retry" button. | ||
</p> | ||
|
||
<SideBySide> | ||
<SmallWidget> | ||
<AreaChartWidget title="Loading Count" isLoading /> | ||
</SmallWidget> | ||
<SmallWidget> | ||
<AreaChartWidget title="Missing Count" /> | ||
</SmallWidget> | ||
<SmallWidget> | ||
<AreaChartWidget | ||
title="Count Error" | ||
error={new Error('Something went wrong!')} | ||
/> | ||
</SmallWidget> | ||
<SmallWidget> | ||
<AreaChartWidget | ||
title="Data Error" | ||
error={new Error('Something went wrong!')} | ||
onRetry={() => {}} | ||
/> | ||
</SmallWidget> | ||
</SideBySide> | ||
</Fragment> | ||
); | ||
}); | ||
|
||
story('Colors', () => { | ||
const theme = useTheme(); | ||
|
||
return ( | ||
<Fragment> | ||
<p> | ||
You can control the color of each timeseries by setting the <code>color</code>{' '} | ||
attribute to a string that contains a valid hex color code. | ||
</p> | ||
|
||
<MediumWidget> | ||
<AreaChartWidget | ||
title="error_rate()" | ||
description="Rate of Errors" | ||
timeSeries={[ | ||
{...sampleLatencyTimeSeries, color: theme.error}, | ||
|
||
{...sampleSpanDurationTimeSeries, color: theme.warning}, | ||
]} | ||
/> | ||
</MediumWidget> | ||
</Fragment> | ||
); | ||
}); | ||
|
||
story('Releases', () => { | ||
const releases = [ | ||
{ | ||
version: 'ui@0.1.2', | ||
timestamp: sampleLatencyTimeSeries.data.at(2)?.timestamp, | ||
}, | ||
{ | ||
version: 'ui@0.1.3', | ||
timestamp: sampleLatencyTimeSeries.data.at(20)?.timestamp, | ||
}, | ||
].filter(hasTimestamp); | ||
|
||
return ( | ||
<Fragment> | ||
<p> | ||
<JSXNode name="AreaChartWidget" /> supports the <code>releases</code> prop. If | ||
passed in, the widget will plot every release as a vertical line that overlays | ||
the chart data. Clicking on a release line will open the release details page. | ||
</p> | ||
|
||
<MediumWidget> | ||
<AreaChartWidget | ||
title="error_rate()" | ||
timeSeries={[sampleLatencyTimeSeries, sampleSpanDurationTimeSeries]} | ||
releases={releases} | ||
/> | ||
</MediumWidget> | ||
</Fragment> | ||
); | ||
}); | ||
}); | ||
|
||
const MediumWidget = styled('div')` | ||
width: 420px; | ||
height: 250px; | ||
`; | ||
|
||
const SmallWidget = styled('div')` | ||
width: 360px; | ||
height: 160px; | ||
`; | ||
|
||
const SmallSizingWindow = styled(SizingWindow)` | ||
width: 50%; | ||
height: 300px; | ||
`; | ||
|
||
function toTimeSeriesSelection( | ||
timeSeries: TimeSeries, | ||
start: DateString | null, | ||
end: DateString | null | ||
): TimeSeries { | ||
return { | ||
...timeSeries, | ||
data: timeSeries.data.filter(datum => { | ||
if (start && moment(datum.timestamp).isBefore(moment.utc(start))) { | ||
return false; | ||
} | ||
|
||
if (end && moment(datum.timestamp).isAfter(moment.utc(end))) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}), | ||
}; | ||
} | ||
|
||
function hasTimestamp(release: Partial<Release>): release is Release { | ||
return Boolean(release?.timestamp); | ||
} |
104 changes: 5 additions & 99 deletions
104
static/app/views/dashboards/widgets/barChartWidget/barChartWidget.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,114 +1,20 @@ | ||
import {Fragment} from 'react'; | ||
import styled from '@emotion/styled'; | ||
import moment from 'moment-timezone'; | ||
|
||
import JSXNode from 'sentry/components/stories/jsxNode'; | ||
import SideBySide from 'sentry/components/stories/sideBySide'; | ||
import SizingWindow from 'sentry/components/stories/sizingWindow'; | ||
import storyBook from 'sentry/stories/storyBook'; | ||
import type {DateString} from 'sentry/types/core'; | ||
import usePageFilters from 'sentry/utils/usePageFilters'; | ||
|
||
import type {TimeSeries} from '../common/types'; | ||
import {shiftTimeserieToNow} from '../timeSeriesWidget/shiftTimeserieToNow'; | ||
|
||
import {sampleLatencyTimeSeries} from './fixtures/sampleLatencyTimeSeries'; | ||
import {sampleSpanDurationTimeSeries} from './fixtures/sampleSpanDurationTimeSeries'; | ||
import {BarChartWidget} from './barChartWidget'; | ||
|
||
export default storyBook('BarChartWidget', story => { | ||
story('Getting Started', () => { | ||
return ( | ||
<Fragment> | ||
<p> | ||
<JSXNode name="BarChartWidget" /> is a Dashboard Widget Component. It displays a | ||
timeseries chart with multiple timeseries, and the timeseries and discontinuous. | ||
In all other ways, it behaves like <JSXNode name="AreaChartWidget" /> | ||
</p> | ||
<p> | ||
<em>NOTE:</em> Prefer <JSXNode name="LineChartWidget" /> and{' '} | ||
<JSXNode name="AreaChartWidget" /> for timeseries visualizations! This should be | ||
used for discontinuous categorized data. | ||
</p> | ||
</Fragment> | ||
); | ||
}); | ||
|
||
story('Visualization', () => { | ||
const {selection} = usePageFilters(); | ||
const {datetime} = selection; | ||
const {start, end} = datetime; | ||
|
||
const latencyTimeSeries = toTimeSeriesSelection(sampleLatencyTimeSeries, start, end); | ||
|
||
const spanDurationTimeSeries = toTimeSeriesSelection( | ||
sampleSpanDurationTimeSeries, | ||
start, | ||
end | ||
); | ||
|
||
return ( | ||
<Fragment> | ||
<p> | ||
The visualization of <JSXNode name="BarChartWidget" /> is a bar chart. It has | ||
some bells and whistles including automatic axes labels, and a hover tooltip. | ||
Like other widgets, it automatically fills the parent element. | ||
🚨 <JSXNode name="BarChartWidget" /> is deprecated! Instead, see the stories for{' '} | ||
<JSXNode name="Widget" />, which explain in detail how to compose your own | ||
widgets from standard components. If you want information on how to render a | ||
time series visualization, see the stories for | ||
<JSXNode name="TimeSeriesWidgetVisualization" />. | ||
</p> | ||
<p> | ||
The <code>stacked</code> boolean prop controls stacking. Bar charts are not | ||
stacked by default. | ||
</p> | ||
<SideBySide> | ||
<SmallSizingWindow> | ||
<BarChartWidget | ||
title="Duration Breakdown" | ||
description="Explains what proportion of total duration is taken up by latency vs. span duration" | ||
timeSeries={[ | ||
shiftTimeserieToNow(latencyTimeSeries), | ||
shiftTimeserieToNow(spanDurationTimeSeries), | ||
]} | ||
dataCompletenessDelay={600} | ||
/> | ||
</SmallSizingWindow> | ||
<SmallSizingWindow> | ||
<BarChartWidget | ||
title="Duration Breakdown" | ||
description="Explains what proportion of total duration is taken up by latency vs. span duration" | ||
timeSeries={[ | ||
shiftTimeserieToNow(latencyTimeSeries), | ||
shiftTimeserieToNow(spanDurationTimeSeries), | ||
]} | ||
stacked | ||
/> | ||
</SmallSizingWindow> | ||
</SideBySide> | ||
</Fragment> | ||
); | ||
}); | ||
}); | ||
|
||
const SmallSizingWindow = styled(SizingWindow)` | ||
width: 50%; | ||
height: 300px; | ||
`; | ||
|
||
function toTimeSeriesSelection( | ||
timeSeries: TimeSeries, | ||
start: DateString | null, | ||
end: DateString | null | ||
): TimeSeries { | ||
return { | ||
...timeSeries, | ||
data: timeSeries.data.filter(datum => { | ||
if (start && moment(datum.timestamp).isBefore(moment.utc(start))) { | ||
return false; | ||
} | ||
|
||
if (end && moment(datum.timestamp).isAfter(moment.utc(end))) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}), | ||
}; | ||
} |
Oops, something went wrong.