detailsTimeline.tsx 4.5 KB

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