timelineTableRow.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import {useState} from 'react';
  2. import {Link} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {Button} from 'sentry/components/button';
  5. import {tct} from 'sentry/locale';
  6. import {fadeIn} from 'sentry/styles/animations';
  7. import {space} from 'sentry/styles/space';
  8. import useOrganization from 'sentry/utils/useOrganization';
  9. import {Monitor, MonitorStatus} from 'sentry/views/monitors/types';
  10. import {scheduleAsText} from 'sentry/views/monitors/utils';
  11. import {statusIconColorMap} from 'sentry/views/monitors/utils/constants';
  12. import {CheckInTimeline, CheckInTimelineProps} from './checkInTimeline';
  13. import {TimelinePlaceholder} from './timelinePlaceholder';
  14. import {MonitorBucket} from './types';
  15. interface Props extends Omit<CheckInTimelineProps, 'bucketedData' | 'environment'> {
  16. monitor: Monitor;
  17. bucketedData?: MonitorBucket[];
  18. }
  19. const MAX_SHOWN_ENVIRONMENTS = 4;
  20. export function TimelineTableRow({monitor, bucketedData, ...timelineProps}: Props) {
  21. const [isExpanded, setExpanded] = useState(
  22. monitor.environments.length <= MAX_SHOWN_ENVIRONMENTS
  23. );
  24. const environments = isExpanded
  25. ? monitor.environments
  26. : monitor.environments.slice(0, MAX_SHOWN_ENVIRONMENTS);
  27. return (
  28. <TimelineRow key={monitor.id}>
  29. <MonitorDetails monitor={monitor} />
  30. <MonitorEnvContainer>
  31. {environments.map(({name, status}) => (
  32. <EnvWithStatus key={name}>
  33. <MonitorEnvLabel status={status}>{name}</MonitorEnvLabel>
  34. {statusIconColorMap[status].icon}
  35. </EnvWithStatus>
  36. ))}
  37. {!isExpanded && (
  38. <Button size="xs" onClick={() => setExpanded(true)}>
  39. {tct('Show [num] More', {
  40. num: monitor.environments.length - MAX_SHOWN_ENVIRONMENTS,
  41. })}
  42. </Button>
  43. )}
  44. </MonitorEnvContainer>
  45. <TimelineContainer>
  46. {environments.map(({name}) => {
  47. return (
  48. <TimelineEnvOuterContainer key={name}>
  49. {!bucketedData ? (
  50. <TimelinePlaceholder />
  51. ) : (
  52. <TimelineEnvContainer>
  53. <CheckInTimeline
  54. {...timelineProps}
  55. bucketedData={bucketedData}
  56. environment={name}
  57. />
  58. </TimelineEnvContainer>
  59. )}
  60. </TimelineEnvOuterContainer>
  61. );
  62. })}
  63. </TimelineContainer>
  64. </TimelineRow>
  65. );
  66. }
  67. function MonitorDetails({monitor}: {monitor: Monitor}) {
  68. const organization = useOrganization();
  69. const schedule = scheduleAsText(monitor.config);
  70. const monitorDetailUrl = `/organizations/${organization.slug}/crons/${monitor.slug}/`;
  71. return (
  72. <DetailsContainer to={monitorDetailUrl}>
  73. <Name>{monitor.name}</Name>
  74. <Schedule>{schedule}</Schedule>
  75. </DetailsContainer>
  76. );
  77. }
  78. const TimelineRow = styled('div')`
  79. display: contents;
  80. &:nth-child(odd) > * {
  81. background: ${p => p.theme.backgroundSecondary};
  82. }
  83. &:hover > * {
  84. background: ${p => p.theme.backgroundTertiary};
  85. }
  86. > * {
  87. transition: background 50ms ease-in-out;
  88. }
  89. `;
  90. const DetailsContainer = styled(Link)`
  91. color: ${p => p.theme.textColor};
  92. padding: ${space(3)};
  93. border-right: 1px solid ${p => p.theme.border};
  94. border-radius: 0;
  95. `;
  96. const Name = styled('h3')`
  97. font-size: ${p => p.theme.fontSizeLarge};
  98. margin-bottom: ${space(0.25)};
  99. `;
  100. const Schedule = styled('small')`
  101. color: ${p => p.theme.subText};
  102. font-size: ${p => p.theme.fontSizeSmall};
  103. `;
  104. const MonitorEnvContainer = styled('div')`
  105. display: flex;
  106. padding: ${space(3)} ${space(2)};
  107. flex-direction: column;
  108. gap: ${space(4)};
  109. border-right: 1px solid ${p => p.theme.innerBorder};
  110. text-align: right;
  111. `;
  112. const EnvWithStatus = styled('div')`
  113. display: flex;
  114. gap: ${space(1)};
  115. align-items: center;
  116. `;
  117. const MonitorEnvLabel = styled('div')<{status: MonitorStatus}>`
  118. text-overflow: ellipsis;
  119. overflow: hidden;
  120. white-space: nowrap;
  121. flex: 1;
  122. color: ${p => p.theme[statusIconColorMap[p.status].color]};
  123. `;
  124. const TimelineContainer = styled('div')`
  125. display: flex;
  126. padding: ${space(3)} 0;
  127. flex-direction: column;
  128. gap: ${space(4)};
  129. contain: content;
  130. `;
  131. const TimelineEnvOuterContainer = styled('div')`
  132. position: relative;
  133. height: calc(${p => p.theme.fontSizeLarge} * ${p => p.theme.text.lineHeightHeading});
  134. `;
  135. const TimelineEnvContainer = styled('div')`
  136. position: absolute;
  137. inset: 0;
  138. opacity: 0;
  139. animation: ${fadeIn} 1.5s ease-out forwards;
  140. contain: content;
  141. `;