linkedIssue.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import styled from '@emotion/styled';
  2. import {Alert} from 'sentry/components/alert';
  3. import {SectionHeading} from 'sentry/components/charts/styles';
  4. import Times from 'sentry/components/group/times';
  5. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  6. import Link from 'sentry/components/links/link';
  7. import LoadingError from 'sentry/components/loadingError';
  8. import Placeholder from 'sentry/components/placeholder';
  9. import SeenByList from 'sentry/components/seenByList';
  10. import ShortId from 'sentry/components/shortId';
  11. import GroupChart from 'sentry/components/stream/groupChart';
  12. import {t} from 'sentry/locale';
  13. import {space} from 'sentry/styles/space';
  14. import type {Group} from 'sentry/types';
  15. import {useApiQuery} from 'sentry/utils/queryClient';
  16. type Props = {
  17. eventId: string;
  18. groupId: string;
  19. };
  20. function LinkedIssue({eventId, groupId}: Props) {
  21. const groupUrl = `/issues/${groupId}/`;
  22. const {
  23. data: group,
  24. isLoading,
  25. isError,
  26. error,
  27. } = useApiQuery<Group>([groupUrl], {staleTime: 0});
  28. if (isLoading) {
  29. return <Placeholder height="120px" bottomGutter={2} />;
  30. }
  31. if (isError || !group) {
  32. const hasNotFound = error.status === 404;
  33. if (hasNotFound) {
  34. return (
  35. <Alert type="warning" showIcon>
  36. {t('The linked issue cannot be found. It may have been deleted, or merged.')}
  37. </Alert>
  38. );
  39. }
  40. return <LoadingError />;
  41. }
  42. const issueUrl = `${group.permalink}events/${eventId}/`;
  43. return (
  44. <Section>
  45. <SectionHeading>{t('Event Issue')}</SectionHeading>
  46. <StyledIssueCard>
  47. <IssueCardHeader>
  48. <StyledLink to={issueUrl} data-test-id="linked-issue">
  49. <StyledShortId
  50. shortId={group.shortId}
  51. avatar={
  52. <ProjectBadge
  53. project={group.project}
  54. avatarSize={16}
  55. hideName
  56. disableLink
  57. />
  58. }
  59. />
  60. </StyledLink>
  61. <SeenByList seenBy={group.seenBy} maxVisibleAvatars={5} />
  62. </IssueCardHeader>
  63. <IssueCardBody>
  64. <GroupChart statsPeriod="30d" data={group} height={56} />
  65. </IssueCardBody>
  66. <IssueCardFooter>
  67. <Times lastSeen={group.lastSeen} firstSeen={group.firstSeen} />
  68. </IssueCardFooter>
  69. </StyledIssueCard>
  70. </Section>
  71. );
  72. }
  73. const Section = styled('div')`
  74. margin-bottom: ${space(4)};
  75. `;
  76. const StyledIssueCard = styled('div')`
  77. border: 1px solid ${p => p.theme.border};
  78. border-radius: ${p => p.theme.borderRadius};
  79. `;
  80. const IssueCardHeader = styled('div')`
  81. display: flex;
  82. align-items: center;
  83. justify-content: space-between;
  84. padding: ${space(1)};
  85. `;
  86. const StyledLink = styled(Link)`
  87. justify-content: flex-start;
  88. `;
  89. const IssueCardBody = styled('div')`
  90. background: ${p => p.theme.backgroundSecondary};
  91. padding-top: ${space(1)};
  92. `;
  93. const StyledShortId = styled(ShortId)`
  94. font-size: ${p => p.theme.fontSizeMedium};
  95. color: ${p => p.theme.textColor};
  96. `;
  97. const IssueCardFooter = styled('div')`
  98. color: ${p => p.theme.gray300};
  99. font-size: ${p => p.theme.fontSizeSmall};
  100. padding: ${space(0.5)} ${space(1)};
  101. `;
  102. export default LinkedIssue;