cronDetailsTimeline.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. import {useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import moment from 'moment';
  4. import {
  5. deleteMonitorEnvironment,
  6. setEnvironmentIsMuted,
  7. } from 'sentry/actionCreators/monitors';
  8. import Panel from 'sentry/components/panels/panel';
  9. import Text from 'sentry/components/text';
  10. import {t} from 'sentry/locale';
  11. import {space} from 'sentry/styles/space';
  12. import type {Organization} from 'sentry/types';
  13. import {parsePeriodToHours} from 'sentry/utils/dates';
  14. import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient';
  15. import useApi from 'sentry/utils/useApi';
  16. import {useDimensions} from 'sentry/utils/useDimensions';
  17. import usePageFilters from 'sentry/utils/usePageFilters';
  18. import useRouter from 'sentry/utils/useRouter';
  19. import {
  20. GridLineOverlay,
  21. GridLineTimeLabels,
  22. } from 'sentry/views/monitors/components/overviewTimeline/gridLines';
  23. import {TimelineTableRow} from 'sentry/views/monitors/components/overviewTimeline/timelineTableRow';
  24. import type {MonitorBucketData} from 'sentry/views/monitors/components/overviewTimeline/types';
  25. import {getConfigFromTimeRange} from 'sentry/views/monitors/components/overviewTimeline/utils';
  26. import type {Monitor} from 'sentry/views/monitors/types';
  27. import {makeMonitorDetailsQueryKey} from 'sentry/views/monitors/utils';
  28. interface Props {
  29. monitor: Monitor;
  30. organization: Organization;
  31. }
  32. export function CronDetailsTimeline({monitor, organization}: Props) {
  33. const {location} = useRouter();
  34. const api = useApi();
  35. const queryClient = useQueryClient();
  36. const nowRef = useRef<Date>(new Date());
  37. const {selection} = usePageFilters();
  38. const {period} = selection.datetime;
  39. let {end, start} = selection.datetime;
  40. if (!start || !end) {
  41. end = nowRef.current;
  42. start = moment(end)
  43. .subtract(parsePeriodToHours(period ?? '24h'), 'hour')
  44. .toDate();
  45. } else {
  46. start = new Date(start);
  47. end = new Date(end);
  48. }
  49. const elementRef = useRef<HTMLDivElement>(null);
  50. const {width: timelineWidth} = useDimensions<HTMLDivElement>({elementRef});
  51. const config = getConfigFromTimeRange(start, end, timelineWidth);
  52. const elapsedMinutes = config.elapsedMinutes;
  53. const rollup = Math.floor((elapsedMinutes * 60) / timelineWidth);
  54. const monitorStatsQueryKey = `/organizations/${organization.slug}/monitors-stats/`;
  55. const {data: monitorStats, isLoading} = useApiQuery<Record<string, MonitorBucketData>>(
  56. [
  57. monitorStatsQueryKey,
  58. {
  59. query: {
  60. until: Math.floor(end.getTime() / 1000),
  61. since: Math.floor(start.getTime() / 1000),
  62. monitor: monitor.slug,
  63. resolution: `${rollup}s`,
  64. ...location.query,
  65. },
  66. },
  67. ],
  68. {
  69. staleTime: 0,
  70. enabled: timelineWidth > 0,
  71. }
  72. );
  73. const monitorDetailsQueryKey = makeMonitorDetailsQueryKey(
  74. organization,
  75. monitor.project.slug,
  76. monitor.slug,
  77. {...location.query}
  78. );
  79. const handleDeleteEnvironment = async (env: string) => {
  80. const success = await deleteMonitorEnvironment(api, organization.slug, monitor, env);
  81. if (!success) {
  82. return;
  83. }
  84. setApiQueryData(queryClient, monitorDetailsQueryKey, (oldMonitorDetails: Monitor) => {
  85. const newEnvList = oldMonitorDetails.environments.filter(e => e.name !== env);
  86. const newMonitorDetails = {
  87. ...oldMonitorDetails,
  88. environments: newEnvList,
  89. };
  90. return newMonitorDetails;
  91. });
  92. };
  93. const handleToggleMuteEnvironment = async (env: string, isMuted: boolean) => {
  94. const resp = await setEnvironmentIsMuted(
  95. api,
  96. organization.slug,
  97. monitor,
  98. env,
  99. isMuted
  100. );
  101. if (resp === null) {
  102. return;
  103. }
  104. setApiQueryData(queryClient, monitorDetailsQueryKey, (oldMonitorDetails: Monitor) => {
  105. const oldMonitorEnvIdx = oldMonitorDetails.environments.findIndex(
  106. monitorEnv => monitorEnv.name === env
  107. );
  108. if (oldMonitorEnvIdx < 0) {
  109. return oldMonitorDetails;
  110. }
  111. oldMonitorDetails.environments[oldMonitorEnvIdx] = {
  112. ...oldMonitorDetails.environments[oldMonitorEnvIdx],
  113. isMuted,
  114. };
  115. return oldMonitorDetails;
  116. });
  117. };
  118. return (
  119. <TimelineContainer>
  120. <TimelineWidthTracker ref={elementRef} />
  121. <Header>
  122. <TimelineTitle>{t('Check-Ins')}</TimelineTitle>
  123. <GridLineTimeLabels
  124. timeWindowConfig={config}
  125. start={start}
  126. end={end}
  127. width={timelineWidth}
  128. />
  129. </Header>
  130. <StyledGridLineOverlay
  131. showCursor={!isLoading}
  132. timeWindowConfig={config}
  133. start={start}
  134. end={end}
  135. width={timelineWidth}
  136. />
  137. <TimelineTableRow
  138. monitor={monitor}
  139. bucketedData={monitorStats?.[monitor.slug]}
  140. timeWindowConfig={config}
  141. end={end}
  142. start={start}
  143. width={timelineWidth}
  144. onDeleteEnvironment={handleDeleteEnvironment}
  145. onToggleMuteEnvironment={handleToggleMuteEnvironment}
  146. singleMonitorView
  147. />
  148. </TimelineContainer>
  149. );
  150. }
  151. const TimelineContainer = styled(Panel)`
  152. display: grid;
  153. grid-template-columns: 135px 1fr;
  154. `;
  155. const Header = styled('div')`
  156. grid-column: 1/-1;
  157. display: grid;
  158. grid-template-columns: subgrid;
  159. border-bottom: 1px solid ${p => p.theme.border};
  160. `;
  161. const StyledGridLineOverlay = styled(GridLineOverlay)`
  162. grid-column: 2;
  163. `;
  164. const TimelineWidthTracker = styled('div')`
  165. position: absolute;
  166. width: 100%;
  167. grid-row: 1;
  168. grid-column: 2;
  169. `;
  170. const TimelineTitle = styled(Text)`
  171. ${p => p.theme.text.cardTitle};
  172. padding: ${space(2)};
  173. grid-column: 1;
  174. `;