quickContextHovercard.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import {ComponentProps} from 'react';
  2. import styled from '@emotion/styled';
  3. import {Location} from 'history';
  4. import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton';
  5. import {Body, Hovercard} from 'sentry/components/hovercard';
  6. import Version from 'sentry/components/version';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {Organization, Project} from 'sentry/types';
  10. import {trackAnalytics} from 'sentry/utils/analytics';
  11. import EventView, {EventData} from 'sentry/utils/discover/eventView';
  12. import {getShortEventId} from 'sentry/utils/events';
  13. import {useLocation} from 'sentry/utils/useLocation';
  14. import EventContext from './eventContext';
  15. import IssueContext from './issueContext';
  16. import ReleaseContext from './releaseContext';
  17. import {NoContextWrapper} from './styles';
  18. import {ContextType} from './utils';
  19. const HOVER_DELAY: number = 400;
  20. function getHoverBody(
  21. dataRow: EventData,
  22. contextType: ContextType,
  23. organization: Organization,
  24. location?: Location,
  25. projects?: Project[],
  26. eventView?: EventView
  27. ) {
  28. switch (contextType) {
  29. case ContextType.ISSUE:
  30. return <IssueContext dataRow={dataRow} organization={organization} />;
  31. case ContextType.RELEASE:
  32. return <ReleaseContext dataRow={dataRow} organization={organization} />;
  33. case ContextType.EVENT:
  34. return (
  35. <EventContext
  36. dataRow={dataRow}
  37. organization={organization}
  38. location={location}
  39. projects={projects}
  40. eventView={eventView}
  41. />
  42. );
  43. default:
  44. return <NoContextWrapper>{t('There is no context available.')}</NoContextWrapper>;
  45. }
  46. }
  47. // NOTE: Will be adding switch cases as more contexts require headers.
  48. function getHoverHeader(
  49. dataRow: EventData,
  50. contextType: ContextType,
  51. organization: Organization
  52. ) {
  53. switch (contextType) {
  54. case ContextType.RELEASE:
  55. return (
  56. <HoverHeader
  57. title={t('Release')}
  58. organization={organization}
  59. copyLabel={<StyledVersion version={dataRow.release} truncate anchor={false} />}
  60. copyContent={dataRow.release}
  61. />
  62. );
  63. case ContextType.ISSUE:
  64. return (
  65. <HoverHeader
  66. title={t('Issue')}
  67. organization={organization}
  68. copyLabel={dataRow.issue}
  69. copyContent={dataRow.issue}
  70. />
  71. );
  72. case ContextType.EVENT:
  73. return (
  74. dataRow.id && (
  75. <HoverHeader
  76. title={t('Event ID')}
  77. organization={organization}
  78. copyLabel={getShortEventId(dataRow.id)}
  79. copyContent={dataRow.id}
  80. />
  81. )
  82. );
  83. default:
  84. return null;
  85. }
  86. }
  87. type HoverHeaderProps = {
  88. organization: Organization;
  89. title: string;
  90. copyContent?: string;
  91. copyLabel?: React.ReactNode;
  92. hideCopy?: boolean;
  93. };
  94. function HoverHeader({
  95. title,
  96. hideCopy = false,
  97. copyLabel,
  98. copyContent,
  99. organization,
  100. }: HoverHeaderProps) {
  101. return (
  102. <HoverHeaderWrapper>
  103. {title}
  104. <HoverHeaderContent>
  105. {copyLabel}
  106. {!hideCopy && copyContent && (
  107. <CopyToClipboardButton
  108. borderless
  109. data-test-id="quick-context-hover-header-copy-button"
  110. iconSize="xs"
  111. onCopy={() => {
  112. trackAnalytics('discover_v2.quick_context_header_copy', {
  113. organization,
  114. clipBoardTitle: title,
  115. });
  116. }}
  117. size="zero"
  118. text={copyContent}
  119. />
  120. )}
  121. </HoverHeaderContent>
  122. </HoverHeaderWrapper>
  123. );
  124. }
  125. interface ContextProps extends ComponentProps<typeof Hovercard> {
  126. children: React.ReactNode;
  127. contextType: ContextType;
  128. dataRow: EventData;
  129. organization: Organization;
  130. eventView?: EventView;
  131. projects?: Project[];
  132. }
  133. export function QuickContextHovercard(props: ContextProps) {
  134. const location = useLocation();
  135. const {
  136. children,
  137. dataRow,
  138. contextType,
  139. organization,
  140. projects,
  141. eventView,
  142. ...hovercardProps
  143. } = props;
  144. return (
  145. <StyledHovercard
  146. {...hovercardProps}
  147. showUnderline
  148. delay={HOVER_DELAY}
  149. header={getHoverHeader(dataRow, contextType, organization)}
  150. body={getHoverBody(
  151. dataRow,
  152. contextType,
  153. organization,
  154. location,
  155. projects,
  156. eventView
  157. )}
  158. >
  159. {children}
  160. </StyledHovercard>
  161. );
  162. }
  163. const StyledHovercard = styled(Hovercard)`
  164. ${Body} {
  165. padding: 0;
  166. }
  167. min-width: max-content;
  168. `;
  169. const HoverHeaderWrapper = styled('div')`
  170. display: flex;
  171. align-items: center;
  172. justify-content: space-between;
  173. `;
  174. const HoverHeaderContent = styled('div')`
  175. display: flex;
  176. flex: 1;
  177. align-items: center;
  178. justify-content: flex-end;
  179. gap: ${space(0.5)};
  180. `;
  181. const StyledVersion = styled(Version)`
  182. max-width: 190px;
  183. `;