issueLink.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import classNames from 'classnames';
  4. import Count from 'sentry/components/count';
  5. import EventOrGroupTitle from 'sentry/components/eventOrGroupTitle';
  6. import EventAnnotation from 'sentry/components/events/eventAnnotation';
  7. import EventMessage from 'sentry/components/events/eventMessage';
  8. import {Hovercard} from 'sentry/components/hovercard';
  9. import Link from 'sentry/components/links/link';
  10. import TimeSince from 'sentry/components/timeSince';
  11. import {t} from 'sentry/locale';
  12. import space from 'sentry/styles/space';
  13. import {Group} from 'sentry/types';
  14. import {getMessage} from 'sentry/utils/events';
  15. type Props = {
  16. card: boolean;
  17. children: React.ReactNode;
  18. issue: Group;
  19. orgId: string;
  20. to: string;
  21. };
  22. const IssueLink = ({children, orgId, issue, to, card = true}: Props) => {
  23. if (!card) {
  24. return <Link to={to}>{children}</Link>;
  25. }
  26. const message = getMessage(issue);
  27. const className = classNames({
  28. isBookmarked: issue.isBookmarked,
  29. hasSeen: issue.hasSeen,
  30. isResolved: issue.status === 'resolved',
  31. });
  32. const streamPath = `/organizations/${orgId}/issues/`;
  33. const hovercardBody = (
  34. <div className={className}>
  35. <Section>
  36. <Title>
  37. <StyledEventOrGroupTitle data={issue} />
  38. </Title>
  39. <HovercardEventMessage
  40. level={issue.level}
  41. levelIndicatorSize="9px"
  42. message={message}
  43. annotations={
  44. <Fragment>
  45. {issue.logger && (
  46. <EventAnnotation>
  47. <Link
  48. to={{
  49. pathname: streamPath,
  50. query: {query: `logger:${issue.logger}`, referrer: 'issue-link'},
  51. }}
  52. >
  53. {issue.logger}
  54. </Link>
  55. </EventAnnotation>
  56. )}
  57. {issue.annotations.map((annotation, i) => (
  58. <EventAnnotation key={i} dangerouslySetInnerHTML={{__html: annotation}} />
  59. ))}
  60. </Fragment>
  61. }
  62. />
  63. </Section>
  64. <Grid>
  65. <div>
  66. <GridHeader>{t('First Seen')}</GridHeader>
  67. <StyledTimeSince date={issue.firstSeen} />
  68. </div>
  69. <div>
  70. <GridHeader>{t('Last Seen')}</GridHeader>
  71. <StyledTimeSince date={issue.lastSeen} />
  72. </div>
  73. <div>
  74. <GridHeader>{t('Occurrences')}</GridHeader>
  75. <Count value={issue.count} />
  76. </div>
  77. <div>
  78. <GridHeader>{t('Users Affected')}</GridHeader>
  79. <Count value={issue.userCount} />
  80. </div>
  81. </Grid>
  82. </div>
  83. );
  84. return (
  85. <Hovercard body={hovercardBody} header={issue.shortId}>
  86. <Link to={to}>{children}</Link>
  87. </Hovercard>
  88. );
  89. };
  90. export default IssueLink;
  91. const Title = styled('div')`
  92. ${p => p.theme.overflowEllipsis};
  93. margin: 0 0 ${space(0.5)};
  94. `;
  95. const StyledEventOrGroupTitle = styled(EventOrGroupTitle)`
  96. font-weight: 600;
  97. font-size: ${p => p.theme.fontSizeMedium};
  98. em {
  99. font-style: normal;
  100. font-weight: 400;
  101. font-size: ${p => p.theme.fontSizeSmall};
  102. }
  103. `;
  104. const Section = styled('section')`
  105. margin-bottom: ${space(2)};
  106. `;
  107. const Grid = styled('div')`
  108. display: grid;
  109. grid-template-columns: 1fr 1fr;
  110. gap: ${space(2)};
  111. `;
  112. const HovercardEventMessage = styled(EventMessage)`
  113. font-size: 12px;
  114. `;
  115. const GridHeader = styled('h5')`
  116. color: ${p => p.theme.gray300};
  117. font-size: 11px;
  118. margin-bottom: ${space(0.5)};
  119. text-transform: uppercase;
  120. `;
  121. const StyledTimeSince = styled(TimeSince)`
  122. color: inherit;
  123. `;