areaChartWidget.stories.tsx 6.8 KB

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