cronDetailsTimeline.tsx 4.9 KB

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