checkInTimeline.tsx 4.6 KB

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