row.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import {deleteMonitor, deleteMonitorEnvironment} from 'sentry/actionCreators/monitors';
  4. import {openConfirmModal} from 'sentry/components/confirm';
  5. import {DropdownMenu, MenuItemProps} from 'sentry/components/dropdownMenu';
  6. import IdBadge from 'sentry/components/idBadge';
  7. import Link from 'sentry/components/links/link';
  8. import TextOverflow from 'sentry/components/textOverflow';
  9. import TimeSince from 'sentry/components/timeSince';
  10. import {IconEllipsis} from 'sentry/icons';
  11. import {t, tct} from 'sentry/locale';
  12. import {space} from 'sentry/styles/space';
  13. import {Organization} from 'sentry/types';
  14. import useApi from 'sentry/utils/useApi';
  15. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  16. import {scheduleAsText} from 'sentry/views/monitors/utils';
  17. import {Monitor, MonitorEnvironment, MonitorStatus} from '../types';
  18. import {MonitorBadge} from './monitorBadge';
  19. interface MonitorRowProps {
  20. monitor: Monitor;
  21. onDelete: (monitorEnv?: MonitorEnvironment) => void;
  22. organization: Organization;
  23. monitorEnv?: MonitorEnvironment;
  24. }
  25. function MonitorRow({monitor, monitorEnv, organization, onDelete}: MonitorRowProps) {
  26. const api = useApi();
  27. const lastCheckin = monitorEnv?.lastCheckIn ? (
  28. <TimeSince unitStyle="regular" date={monitorEnv.lastCheckIn} />
  29. ) : null;
  30. const deletionMessage = monitorEnv
  31. ? tct(
  32. 'Are you sure you want to permanently delete the "[envName]" environment from "[monitorName]"?',
  33. {monitorName: monitor.name, envName: monitorEnv.name}
  34. )
  35. : tct('Are you sure you want to permanently delete "[monitorName]"?', {
  36. monitorName: monitor.name,
  37. });
  38. const actions: MenuItemProps[] = [
  39. {
  40. key: 'edit',
  41. label: t('Edit'),
  42. // TODO(davidenwang): Right now we have to pass the environment
  43. // through the URL so that when we save the monitor and are
  44. // redirected back to the details page it queries the backend
  45. // for a monitor environment with check-in data
  46. to: normalizeUrl({
  47. pathname: `/organizations/${organization.slug}/crons/${monitor.slug}/edit/`,
  48. query: {environment: monitorEnv?.name},
  49. }),
  50. },
  51. {
  52. key: 'delete',
  53. label: t('Delete'),
  54. priority: 'danger',
  55. onAction: () => {
  56. openConfirmModal({
  57. onConfirm: async () => {
  58. if (monitorEnv) {
  59. await deleteMonitorEnvironment(
  60. api,
  61. organization.slug,
  62. monitor.slug,
  63. monitorEnv.name
  64. );
  65. } else {
  66. await deleteMonitor(api, organization.slug, monitor.slug);
  67. }
  68. onDelete(monitorEnv);
  69. },
  70. header: t('Delete Monitor?'),
  71. message: deletionMessage,
  72. confirmText: t('Delete Monitor'),
  73. priority: 'danger',
  74. });
  75. },
  76. },
  77. ];
  78. const monitorDetailUrl = `/organizations/${organization.slug}/crons/${monitor.slug}/${
  79. monitorEnv ? `?environment=${monitorEnv.name}` : ''
  80. }`;
  81. // TODO(davidenwang): Change accordingly when we have ObjectStatus on monitor
  82. const monitorStatus =
  83. monitor.status !== 'disabled' && monitorEnv ? monitorEnv.status : monitor.status;
  84. return (
  85. <Fragment>
  86. <MonitorName>
  87. <MonitorBadge status={monitorStatus} />
  88. <Link to={monitorDetailUrl}>{monitor.name}</Link>
  89. </MonitorName>
  90. <MonitorColumn>
  91. <TextOverflow>
  92. {monitorStatus === 'disabled'
  93. ? t('Paused')
  94. : monitorStatus === MonitorStatus.ACTIVE || !lastCheckin
  95. ? t('Waiting for first check-in')
  96. : monitorStatus === MonitorStatus.OK
  97. ? tct('Check-in [lastCheckin]', {lastCheckin})
  98. : monitorStatus === MonitorStatus.MISSED_CHECKIN
  99. ? tct('Missed [lastCheckin]', {lastCheckin})
  100. : monitorStatus === MonitorStatus.ERROR
  101. ? tct('Failed [lastCheckin]', {lastCheckin})
  102. : monitorStatus === MonitorStatus.TIMEOUT
  103. ? t('Timed out')
  104. : null}
  105. </TextOverflow>
  106. </MonitorColumn>
  107. <MonitorColumn>{scheduleAsText(monitor.config)}</MonitorColumn>
  108. <MonitorColumn>
  109. {monitorEnv?.nextCheckIn &&
  110. monitorEnv.status !== MonitorStatus.DISABLED &&
  111. monitorEnv.status !== MonitorStatus.ACTIVE ? (
  112. <TimeSince unitStyle="regular" date={monitorEnv.nextCheckIn} />
  113. ) : (
  114. '\u2014'
  115. )}
  116. </MonitorColumn>
  117. <MonitorColumn>
  118. <IdBadge
  119. project={monitor.project}
  120. avatarSize={18}
  121. avatarProps={{hasTooltip: true, tooltip: monitor.project.slug}}
  122. />
  123. </MonitorColumn>
  124. <MonitorColumn>{monitorEnv?.name ?? '\u2014'}</MonitorColumn>
  125. <ActionsColumn>
  126. <DropdownMenu
  127. items={actions}
  128. position="bottom-end"
  129. triggerProps={{
  130. 'aria-label': t('Actions'),
  131. size: 'xs',
  132. icon: <IconEllipsis size="xs" />,
  133. showChevron: false,
  134. }}
  135. />
  136. </ActionsColumn>
  137. </Fragment>
  138. );
  139. }
  140. export {MonitorRow};
  141. const MonitorName = styled('div')`
  142. display: flex;
  143. align-items: center;
  144. gap: ${space(2)};
  145. font-size: ${p => p.theme.fontSizeLarge};
  146. `;
  147. const MonitorColumn = styled('div')`
  148. display: flex;
  149. align-items: center;
  150. `;
  151. const ActionsColumn = styled('div')`
  152. display: flex;
  153. align-items: center;
  154. justify-content: center;
  155. `;