issueSummary.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import ErrorBoundary from 'sentry/components/errorBoundary';
  4. import EventOrGroupTitle from 'sentry/components/eventOrGroupTitle';
  5. import EventMessage from 'sentry/components/events/eventMessage';
  6. import EventTitleError from 'sentry/components/eventTitleError';
  7. import GlobalSelectionLink from 'sentry/components/globalSelectionLink';
  8. import {IconStar} from 'sentry/icons';
  9. import {tct} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import type {Group} from 'sentry/types/group';
  12. import type {Organization} from 'sentry/types/organization';
  13. import {getLocation, getMessage, isTombstone} from 'sentry/utils/events';
  14. import useOrganization from 'sentry/utils/useOrganization';
  15. interface EventOrGroupHeaderProps {
  16. data: Group;
  17. event_id: string;
  18. organization: Organization;
  19. }
  20. /**
  21. * Displays an event or group/issue title (i.e. in Stream)
  22. */
  23. interface IssueTitleChildrenProps {
  24. data: Group;
  25. organization: Organization;
  26. }
  27. function IssueTitleChildren(props: IssueTitleChildrenProps) {
  28. const {isBookmarked, hasSeen} = props.data;
  29. return (
  30. <Fragment>
  31. {isBookmarked && (
  32. <IconWrapper>
  33. <IconStar isSolid color="yellow400" />
  34. </IconWrapper>
  35. )}
  36. <ErrorBoundary customComponent={<EventTitleError />} mini>
  37. <StyledEventOrGroupTitle
  38. data={props.data}
  39. // hasSeen is undefined for GroupTombstone
  40. hasSeen={hasSeen === undefined ? true : hasSeen}
  41. withStackTracePreview
  42. />
  43. </ErrorBoundary>
  44. </Fragment>
  45. );
  46. }
  47. interface IssueTitleProps {
  48. data: Group;
  49. event_id: string;
  50. }
  51. function IssueTitle(props: IssueTitleProps) {
  52. const organization = useOrganization();
  53. const commonEleProps = {
  54. 'data-test-id': status === 'resolved' ? 'resolved-issue' : null,
  55. };
  56. if (isTombstone(props.data)) {
  57. return (
  58. <TitleWithoutLink {...commonEleProps}>
  59. <IssueTitleChildren data={props.data} organization={organization} />
  60. </TitleWithoutLink>
  61. );
  62. }
  63. return (
  64. <TitleWithLink
  65. {...commonEleProps}
  66. to={{
  67. pathname: `/organizations/${organization.slug}/issues/${props.data.id}/events/${props.event_id}/`,
  68. }}
  69. >
  70. <IssueTitleChildren data={props.data} organization={organization} />
  71. </TitleWithLink>
  72. );
  73. }
  74. export function IssueSummary({data, event_id}: EventOrGroupHeaderProps) {
  75. const eventLocation = getLocation(data);
  76. return (
  77. <div data-test-id="event-issue-header">
  78. <Title>
  79. <IssueTitle data={data} event_id={event_id} />
  80. </Title>
  81. {eventLocation ? <Location>{eventLocation}</Location> : null}
  82. <StyledEventMessage
  83. level={'level' in data ? data.level : undefined}
  84. message={getMessage(data)}
  85. type={data.type}
  86. levelIndicatorSize="9px"
  87. />
  88. </div>
  89. );
  90. }
  91. const Title = styled('div')`
  92. margin-bottom: ${space(0.25)};
  93. & em {
  94. font-size: ${p => p.theme.fontSizeMedium};
  95. font-style: normal;
  96. font-weight: ${p => p.theme.fontWeightNormal};
  97. color: ${p => p.theme.subText};
  98. }
  99. `;
  100. const LocationWrapper = styled('div')`
  101. overflow: hidden;
  102. max-width: 100%;
  103. text-overflow: ellipsis;
  104. white-space: nowrap;
  105. margin: 0 0 5px;
  106. direction: rtl;
  107. text-align: left;
  108. font-size: ${p => p.theme.fontSizeMedium};
  109. color: ${p => p.theme.subText};
  110. span {
  111. direction: ltr;
  112. }
  113. `;
  114. function Location(props) {
  115. const {children, ...rest} = props;
  116. return (
  117. <LocationWrapper {...rest}>
  118. {tct('in [location]', {
  119. location: <span>{children}</span>,
  120. })}
  121. </LocationWrapper>
  122. );
  123. }
  124. const StyledEventMessage = styled(EventMessage)`
  125. margin: 0 0 5px;
  126. gap: ${space(0.5)};
  127. `;
  128. const IconWrapper = styled('span')`
  129. position: relative;
  130. margin-right: 5px;
  131. `;
  132. const TitleWithLink = styled(GlobalSelectionLink)`
  133. display: inline-flex;
  134. align-items: center;
  135. `;
  136. const TitleWithoutLink = styled('span')`
  137. display: inline-flex;
  138. `;
  139. const StyledEventOrGroupTitle = styled(EventOrGroupTitle)<{
  140. hasSeen: boolean;
  141. }>`
  142. font-weight: ${p => (p.hasSeen ? 400 : 600)};
  143. `;