monitorStats.tsx 3.6 KB

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