issueLink.tsx 3.5 KB

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