row.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import {Fragment, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import moment from 'moment';
  4. import ActorAvatar from 'sentry/components/avatar/actorAvatar';
  5. import Duration from 'sentry/components/duration';
  6. import ErrorBoundary from 'sentry/components/errorBoundary';
  7. import IdBadge from 'sentry/components/idBadge';
  8. import Link from 'sentry/components/links/link';
  9. import Tag from 'sentry/components/tag';
  10. import TimeSince from 'sentry/components/timeSince';
  11. import {t} from 'sentry/locale';
  12. import TeamStore from 'sentry/stores/teamStore';
  13. import {space} from 'sentry/styles/space';
  14. import {Actor, Organization, Project} from 'sentry/types';
  15. import getDynamicText from 'sentry/utils/getDynamicText';
  16. import {Incident, IncidentStatus} from 'sentry/views/alerts/types';
  17. import {alertDetailsLink} from 'sentry/views/alerts/utils';
  18. type Props = {
  19. incident: Incident;
  20. organization: Organization;
  21. projects: Project[];
  22. projectsLoaded: boolean;
  23. };
  24. function AlertListRow({incident, projectsLoaded, projects, organization}: Props) {
  25. const slug = incident.projects[0];
  26. const started = moment(incident.dateStarted);
  27. const duration = moment
  28. .duration(moment(incident.dateClosed || new Date()).diff(started))
  29. .as('seconds');
  30. const project = useMemo(() => projects.find(p => p.slug === slug), [slug, projects]);
  31. const alertLink = {
  32. pathname: alertDetailsLink(organization, incident),
  33. query: {alert: incident.identifier},
  34. };
  35. const ownerId = incident.alertRule.owner?.split(':')[1];
  36. let teamName = '';
  37. if (ownerId) {
  38. teamName = TeamStore.getById(ownerId)?.name ?? '';
  39. }
  40. const teamActor = ownerId
  41. ? {type: 'team' as Actor['type'], id: ownerId, name: teamName}
  42. : null;
  43. return (
  44. <ErrorBoundary>
  45. <FlexCenter>
  46. <Title data-test-id="alert-title">
  47. <Link to={alertLink}>{incident.title}</Link>
  48. </Title>
  49. </FlexCenter>
  50. <NoWrapNumeric>
  51. {getDynamicText({
  52. value: <TimeSince date={incident.dateStarted} unitStyle="extraShort" />,
  53. fixed: '1w ago',
  54. })}
  55. </NoWrapNumeric>
  56. <NoWrapNumeric>
  57. {incident.status === IncidentStatus.CLOSED ? (
  58. <Duration seconds={getDynamicText({value: duration, fixed: 1200})} />
  59. ) : (
  60. <Tag type="warning">{t('Still Active')}</Tag>
  61. )}
  62. </NoWrapNumeric>
  63. <FlexCenter>
  64. <ProjectBadge avatarSize={18} project={!projectsLoaded ? {slug} : project} />
  65. </FlexCenter>
  66. <NoWrapNumeric>#{incident.id}</NoWrapNumeric>
  67. <FlexCenter>
  68. {teamActor ? (
  69. <Fragment>
  70. <StyledActorAvatar actor={teamActor} size={18} hasTooltip={false} />{' '}
  71. <TeamWrapper>{teamActor.name}</TeamWrapper>
  72. </Fragment>
  73. ) : (
  74. '-'
  75. )}
  76. </FlexCenter>
  77. </ErrorBoundary>
  78. );
  79. }
  80. const Title = styled('div')`
  81. ${p => p.theme.overflowEllipsis}
  82. min-width: 130px;
  83. `;
  84. const ProjectBadge = styled(IdBadge)`
  85. flex-shrink: 0;
  86. `;
  87. const FlexCenter = styled('div')`
  88. ${p => p.theme.overflowEllipsis}
  89. display: flex;
  90. align-items: center;
  91. line-height: 1.6;
  92. `;
  93. const NoWrapNumeric = styled(FlexCenter)`
  94. white-space: nowrap;
  95. font-variant-numeric: tabular-nums;
  96. `;
  97. const TeamWrapper = styled('span')`
  98. ${p => p.theme.overflowEllipsis}
  99. `;
  100. const StyledActorAvatar = styled(ActorAvatar)`
  101. margin-right: ${space(1)};
  102. `;
  103. export default AlertListRow;