checkInTimeline.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import {css} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import DateTime from 'sentry/components/dateTime';
  4. import {Tooltip} from 'sentry/components/tooltip';
  5. import {CheckInStatus} from 'sentry/views/monitors/types';
  6. import {tickStyle} from 'sentry/views/monitors/utils';
  7. import {getAggregateStatus} from 'sentry/views/monitors/utils/getAggregateStatus';
  8. import {mergeBuckets} from 'sentry/views/monitors/utils/mergeBuckets';
  9. import {JobTickTooltip} from './jobTickTooltip';
  10. import type {MonitorBucketData, TimeWindowConfig} from './types';
  11. interface TimelineProps {
  12. timeWindowConfig: TimeWindowConfig;
  13. width: number;
  14. }
  15. export interface CheckInTimelineProps extends TimelineProps {
  16. bucketedData: MonitorBucketData;
  17. environment: string;
  18. }
  19. function getBucketedCheckInsPosition(
  20. timestamp: number,
  21. timelineStart: Date,
  22. msPerPixel: number
  23. ) {
  24. const elapsedSinceStart = new Date(timestamp).getTime() - timelineStart.getTime();
  25. return elapsedSinceStart / msPerPixel;
  26. }
  27. export function CheckInTimeline(props: CheckInTimelineProps) {
  28. const {bucketedData, timeWindowConfig, width, environment} = props;
  29. const {start, end} = timeWindowConfig;
  30. const elapsedMs = end.getTime() - start.getTime();
  31. const msPerPixel = elapsedMs / width;
  32. const jobTicks = mergeBuckets(bucketedData, environment);
  33. return (
  34. <TimelineContainer>
  35. {jobTicks.map(jobTick => {
  36. const {
  37. startTs,
  38. width: tickWidth,
  39. envMapping,
  40. roundedLeft,
  41. roundedRight,
  42. } = jobTick;
  43. const timestampMs = startTs * 1000;
  44. const left = getBucketedCheckInsPosition(timestampMs, start, msPerPixel);
  45. return (
  46. <JobTickTooltip
  47. jobTick={jobTick}
  48. timeWindowConfig={timeWindowConfig}
  49. skipWrapper
  50. key={startTs}
  51. >
  52. <JobTick
  53. style={{left, width: tickWidth}}
  54. status={getAggregateStatus(envMapping)}
  55. roundedLeft={roundedLeft}
  56. roundedRight={roundedRight}
  57. />
  58. </JobTickTooltip>
  59. );
  60. })}
  61. </TimelineContainer>
  62. );
  63. }
  64. export interface MockCheckInTimelineProps extends TimelineProps {
  65. mockTimestamps: Date[];
  66. }
  67. export function MockCheckInTimeline({
  68. mockTimestamps,
  69. timeWindowConfig,
  70. width,
  71. }: MockCheckInTimelineProps) {
  72. const {start, end} = timeWindowConfig;
  73. const elapsedMs = end.getTime() - start.getTime();
  74. const msPerPixel = elapsedMs / width;
  75. return (
  76. <TimelineContainer>
  77. {mockTimestamps.map(ts => {
  78. const timestampMs = ts.getTime();
  79. const left = getBucketedCheckInsPosition(timestampMs, start, msPerPixel);
  80. return (
  81. <Tooltip
  82. key={left}
  83. title={
  84. <DateTime date={timestampMs} format={timeWindowConfig.dateLabelFormat} />
  85. }
  86. skipWrapper
  87. >
  88. <JobTick
  89. style={{left}}
  90. status={CheckInStatus.IN_PROGRESS}
  91. roundedLeft
  92. roundedRight
  93. />
  94. </Tooltip>
  95. );
  96. })}
  97. </TimelineContainer>
  98. );
  99. }
  100. const TimelineContainer = styled('div')`
  101. position: relative;
  102. height: 100%;
  103. `;
  104. const JobTick = styled('div')<{
  105. roundedLeft: boolean;
  106. roundedRight: boolean;
  107. status: CheckInStatus;
  108. }>`
  109. position: absolute;
  110. top: calc(50% + 1px);
  111. width: 4px;
  112. height: 14px;
  113. transform: translateY(-50%);
  114. opacity: 0.7;
  115. ${p => {
  116. const style = tickStyle[p.status];
  117. if (style.hatchTick === undefined) {
  118. return css`
  119. background: ${p.theme[style.tickColor]};
  120. `;
  121. }
  122. return css`
  123. border: 1px solid ${p.theme[style.tickColor]};
  124. ${!p.roundedLeft && 'border-left-width: 0'};
  125. ${!p.roundedRight && 'border-right-width: 0'};
  126. background-size: 3px 3px;
  127. opacity: 0.5;
  128. background-image: linear-gradient(
  129. -45deg,
  130. ${p.theme[style.hatchTick]} 25%,
  131. transparent 25%,
  132. transparent 50%,
  133. ${p.theme[style.hatchTick]} 50%,
  134. ${p.theme[style.hatchTick]} 75%,
  135. transparent 75%,
  136. transparent
  137. ),
  138. linear-gradient(
  139. 45deg,
  140. ${p.theme[style.hatchTick]} 25%,
  141. transparent 25%,
  142. transparent 50%,
  143. ${p.theme[style.hatchTick]} 50%,
  144. ${p.theme[style.hatchTick]} 75%,
  145. transparent 75%,
  146. transparent
  147. );
  148. `;
  149. }};
  150. ${p =>
  151. p.roundedLeft &&
  152. `
  153. border-top-left-radius: 2px;
  154. border-bottom-left-radius: 2px;
  155. `};
  156. ${p =>
  157. p.roundedRight &&
  158. `
  159. border-top-right-radius: 2px;
  160. border-bottom-right-radius: 2px;
  161. `}
  162. `;