detailsTimeline.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {useEffect, useRef} from 'react';
  2. import styled from '@emotion/styled';
  3. import {
  4. deleteMonitorEnvironment,
  5. setEnvironmentIsMuted,
  6. } from 'sentry/actionCreators/monitors';
  7. import {
  8. GridLineLabels,
  9. GridLineOverlay,
  10. } from 'sentry/components/checkInTimeline/gridLines';
  11. import {useTimeWindowConfig} from 'sentry/components/checkInTimeline/hooks/useTimeWindowConfig';
  12. import Panel from 'sentry/components/panels/panel';
  13. import Text from 'sentry/components/text';
  14. import {t} from 'sentry/locale';
  15. import {space} from 'sentry/styles/space';
  16. import {setApiQueryData, useQueryClient} from 'sentry/utils/queryClient';
  17. import useApi from 'sentry/utils/useApi';
  18. import {useDebouncedValue} from 'sentry/utils/useDebouncedValue';
  19. import {useDimensions} from 'sentry/utils/useDimensions';
  20. import {useLocation} from 'sentry/utils/useLocation';
  21. import useOrganization from 'sentry/utils/useOrganization';
  22. import type {Monitor, MonitorBucket} from 'sentry/views/monitors/types';
  23. import {makeMonitorDetailsQueryKey} from 'sentry/views/monitors/utils';
  24. import {useMonitorStats} from './../utils/useMonitorStats';
  25. import {OverviewRow} from './overviewTimeline/overviewRow';
  26. import {CronServiceIncidents} from './serviceIncidents';
  27. interface Props {
  28. monitor: Monitor;
  29. /**
  30. * Called when monitor stats have been loaded for this timeline.
  31. */
  32. onStatsLoaded: (stats: MonitorBucket[]) => void;
  33. }
  34. export function DetailsTimeline({monitor, onStatsLoaded}: Props) {
  35. const organization = useOrganization();
  36. const location = useLocation();
  37. const api = useApi();
  38. const queryClient = useQueryClient();
  39. const elementRef = useRef<HTMLDivElement>(null);
  40. const {width: containerWidth} = useDimensions<HTMLDivElement>({elementRef});
  41. const timelineWidth = useDebouncedValue(containerWidth, 500);
  42. const timeWindowConfig = useTimeWindowConfig({timelineWidth});
  43. const monitorDetailsQueryKey = makeMonitorDetailsQueryKey(
  44. organization,
  45. monitor.project.slug,
  46. monitor.slug,
  47. {...location.query}
  48. );
  49. const {data: monitorStats} = useMonitorStats({
  50. monitors: [monitor.id],
  51. timeWindowConfig,
  52. });
  53. useEffect(
  54. () => monitorStats?.[monitor.id] && onStatsLoaded?.(monitorStats[monitor.id]!),
  55. [onStatsLoaded, monitorStats, monitor.id]
  56. );
  57. const handleDeleteEnvironment = async (env: string) => {
  58. const success = await deleteMonitorEnvironment(api, organization.slug, monitor, env);
  59. if (!success) {
  60. return;
  61. }
  62. setApiQueryData(queryClient, monitorDetailsQueryKey, (oldMonitorDetails: Monitor) => {
  63. const newEnvList = oldMonitorDetails.environments.filter(e => e.name !== env);
  64. const newMonitorDetails = {
  65. ...oldMonitorDetails,
  66. environments: newEnvList,
  67. };
  68. return newMonitorDetails;
  69. });
  70. };
  71. const handleToggleMuteEnvironment = async (env: string, isMuted: boolean) => {
  72. const resp = await setEnvironmentIsMuted(
  73. api,
  74. organization.slug,
  75. monitor,
  76. env,
  77. isMuted
  78. );
  79. if (resp === null) {
  80. return;
  81. }
  82. setApiQueryData(queryClient, monitorDetailsQueryKey, (oldMonitorDetails: Monitor) => {
  83. const oldMonitorEnvIdx = oldMonitorDetails.environments.findIndex(
  84. monitorEnv => monitorEnv.name === env
  85. );
  86. if (oldMonitorEnvIdx < 0) {
  87. return oldMonitorDetails;
  88. }
  89. oldMonitorDetails.environments[oldMonitorEnvIdx] = {
  90. ...oldMonitorDetails.environments[oldMonitorEnvIdx]!,
  91. isMuted,
  92. };
  93. return oldMonitorDetails;
  94. });
  95. };
  96. return (
  97. <TimelineContainer>
  98. <TimelineWidthTracker ref={elementRef} />
  99. <Header>
  100. <TimelineTitle>{t('Check-Ins')}</TimelineTitle>
  101. <GridLineLabels timeWindowConfig={timeWindowConfig} />
  102. </Header>
  103. <AlignedGridLineOverlay
  104. allowZoom
  105. showCursor
  106. timeWindowConfig={timeWindowConfig}
  107. additionalUi={<CronServiceIncidents timeWindowConfig={timeWindowConfig} />}
  108. />
  109. <OverviewRow
  110. monitor={monitor}
  111. timeWindowConfig={timeWindowConfig}
  112. onDeleteEnvironment={handleDeleteEnvironment}
  113. onToggleMuteEnvironment={handleToggleMuteEnvironment}
  114. singleMonitorView
  115. />
  116. </TimelineContainer>
  117. );
  118. }
  119. const TimelineContainer = styled(Panel)`
  120. display: grid;
  121. grid-template-columns: 135px 1fr;
  122. `;
  123. const Header = styled('div')`
  124. grid-column: 1/-1;
  125. display: grid;
  126. grid-template-columns: subgrid;
  127. border-bottom: 1px solid ${p => p.theme.border};
  128. z-index: 1;
  129. `;
  130. const TimelineWidthTracker = styled('div')`
  131. position: absolute;
  132. width: 100%;
  133. grid-row: 1;
  134. grid-column: 2;
  135. `;
  136. const AlignedGridLineOverlay = styled(GridLineOverlay)`
  137. grid-column: 2;
  138. `;
  139. const TimelineTitle = styled(Text)`
  140. padding: ${space(2)};
  141. grid-column: 1;
  142. line-height: 1.2;
  143. font-weight: bold;
  144. `;