monitorStats.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import type {LineSeriesOption} from 'echarts';
  2. import {BarChart, BarChartSeries} from 'sentry/components/charts/barChart';
  3. import {getYAxisMaxFn} from 'sentry/components/charts/miniBarChart';
  4. import LineSeries from 'sentry/components/charts/series/lineSeries';
  5. import EmptyMessage from 'sentry/components/emptyMessage';
  6. import {Panel, PanelBody} from 'sentry/components/panels';
  7. import {t} from 'sentry/locale';
  8. import {intervalToMilliseconds} from 'sentry/utils/dates';
  9. import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts';
  10. import {AggregationOutputType} from 'sentry/utils/discover/fields';
  11. import theme from 'sentry/utils/theme';
  12. import useApiRequests from 'sentry/utils/useApiRequests';
  13. import usePageFilters from 'sentry/utils/usePageFilters';
  14. import {Monitor, MonitorStat} from '../types';
  15. type Props = {
  16. monitor: Monitor;
  17. orgId: string;
  18. };
  19. type State = {
  20. stats: MonitorStat[] | null;
  21. };
  22. const MonitorStats = ({monitor, orgId}: Props) => {
  23. const {selection} = usePageFilters();
  24. const {start, end, period} = selection.datetime;
  25. let since: number, until: number;
  26. if (start && end) {
  27. until = new Date(end).getTime() / 1000;
  28. since = new Date(start).getTime() / 1000;
  29. } else {
  30. until = Math.floor(new Date().getTime() / 1000);
  31. const intervalSeconds = intervalToMilliseconds(period ?? '30d') / 1000;
  32. since = until - intervalSeconds;
  33. }
  34. const {data, renderComponent} = useApiRequests<State>({
  35. endpoints: [
  36. [
  37. 'stats',
  38. `/organizations/${orgId}/monitors/${monitor.slug}/stats/`,
  39. {
  40. query: {
  41. since: since.toString(),
  42. until: until.toString(),
  43. resolution: '1d',
  44. },
  45. },
  46. ],
  47. ],
  48. });
  49. let emptyStats = true;
  50. const success: BarChartSeries = {
  51. seriesName: t('Successful'),
  52. yAxisIndex: 0,
  53. data: [],
  54. };
  55. const failed: BarChartSeries = {
  56. seriesName: t('Failed'),
  57. yAxisIndex: 0,
  58. data: [],
  59. };
  60. const missed: BarChartSeries = {
  61. seriesName: t('Missed'),
  62. yAxisIndex: 0,
  63. data: [],
  64. };
  65. const durationData = [] as [number, number][];
  66. data.stats?.forEach(p => {
  67. if (p.ok || p.error || p.missed) {
  68. emptyStats = false;
  69. }
  70. const timestamp = p.ts * 1000;
  71. success.data.push({name: timestamp, value: p.ok});
  72. failed.data.push({name: timestamp, value: p.error});
  73. missed.data.push({name: timestamp, value: p.missed});
  74. durationData.push([timestamp, Math.trunc(p.duration)]);
  75. });
  76. const colors = [theme.green200, theme.red200, theme.yellow200];
  77. const durationTitle = t('Average Duration');
  78. const additionalSeries: LineSeriesOption[] = [
  79. LineSeries({
  80. name: durationTitle,
  81. data: durationData,
  82. lineStyle: {color: theme.purple300, width: 2},
  83. itemStyle: {color: theme.purple300},
  84. yAxisIndex: 1,
  85. animation: false,
  86. }),
  87. ];
  88. const height = 150;
  89. const getYAxisOptions = (aggregateType: AggregationOutputType) => ({
  90. max: getYAxisMaxFn(height),
  91. splitLine: {
  92. show: false,
  93. },
  94. axisLabel: {
  95. formatter: (value: number) => axisLabelFormatter(value, aggregateType, true),
  96. showMaxLabel: false,
  97. },
  98. });
  99. return renderComponent(
  100. <Panel>
  101. <PanelBody withPadding>
  102. {!emptyStats ? (
  103. <BarChart
  104. isGroupedByDate
  105. showTimeInTooltip
  106. useShortDate
  107. series={[success, failed, missed]}
  108. stacked
  109. additionalSeries={additionalSeries}
  110. height={height}
  111. colors={colors}
  112. tooltip={{
  113. trigger: 'axis',
  114. valueFormatter: (value: number, label?: string) => {
  115. return label === durationTitle
  116. ? tooltipFormatter(value, 'duration')
  117. : tooltipFormatter(value, 'number');
  118. },
  119. }}
  120. yAxes={[{...getYAxisOptions('number')}, {...getYAxisOptions('duration')}]}
  121. grid={{
  122. top: 6,
  123. bottom: 0,
  124. left: 4,
  125. right: 0,
  126. }}
  127. animation={false}
  128. />
  129. ) : (
  130. <EmptyMessage
  131. title={t('No check-ins have been recorded for this time period.')}
  132. />
  133. )}
  134. </PanelBody>
  135. </Panel>
  136. );
  137. };
  138. export default MonitorStats;