monitorStats.tsx 5.0 KB

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