row.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  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. <Title data-test-id="alert-title">
  46. <Link to={alertLink}>{incident.title}</Link>
  47. </Title>
  48. <NoWrapNumeric>
  49. {getDynamicText({
  50. value: <TimeSince date={incident.dateStarted} extraShort />,
  51. fixed: '1w ago',
  52. })}
  53. </NoWrapNumeric>
  54. <NoWrapNumeric>
  55. {incident.status === IncidentStatus.CLOSED ? (
  56. <Duration seconds={getDynamicText({value: duration, fixed: 1200})} />
  57. ) : (
  58. <Tag type="warning">{t('Still Active')}</Tag>
  59. )}
  60. </NoWrapNumeric>
  61. <ProjectBadge avatarSize={18} project={!projectsLoaded ? {slug} : project} />
  62. <NoWrapNumeric>#{incident.id}</NoWrapNumeric>
  63. <FlexCenter>
  64. {teamActor ? (
  65. <Fragment>
  66. <StyledActorAvatar actor={teamActor} size={24} hasTooltip={false} />{' '}
  67. <TeamWrapper>{teamActor.name}</TeamWrapper>
  68. </Fragment>
  69. ) : (
  70. '-'
  71. )}
  72. </FlexCenter>
  73. </ErrorBoundary>
  74. );
  75. }
  76. const Title = styled('div')`
  77. ${p => p.theme.overflowEllipsis}
  78. min-width: 130px;
  79. `;
  80. const NoWrapNumeric = styled('div')`
  81. white-space: nowrap;
  82. font-variant-numeric: tabular-nums;
  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. `;
  92. const TeamWrapper = styled('span')`
  93. ${p => p.theme.overflowEllipsis}
  94. `;
  95. const StyledActorAvatar = styled(ActorAvatar)`
  96. margin-right: ${space(1)};
  97. `;
  98. export default AlertListRow;