detailsTimeline.tsx 4.6 KB

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