monitorStats.tsx 4.4 KB

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