shortIdBreadcrumb.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import styled from '@emotion/styled';
  2. import {Chevron} from 'sentry/components/chevron';
  3. import {DropdownMenu} from 'sentry/components/dropdownMenu';
  4. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  5. import ShortId from 'sentry/components/shortId';
  6. import {Tooltip} from 'sentry/components/tooltip';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import type {Group} from 'sentry/types/group';
  10. import type {Organization} from 'sentry/types/organization';
  11. import type {Project} from 'sentry/types/project';
  12. import {trackAnalytics} from 'sentry/utils/analytics';
  13. import {getAnalyticsDataForGroup} from 'sentry/utils/events';
  14. import normalizeUrl from 'sentry/utils/url/normalizeUrl';
  15. import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
  16. import {useHasStreamlinedUI} from 'sentry/views/issueDetails/utils';
  17. interface ShortIdBreadcrumbProps {
  18. group: Group;
  19. organization: Organization;
  20. project: Project;
  21. }
  22. export function ShortIdBreadcrumb({
  23. organization,
  24. project,
  25. group,
  26. }: ShortIdBreadcrumbProps) {
  27. const hasStreamlinedUI = useHasStreamlinedUI();
  28. const {onClick: handleCopyShortId} = useCopyToClipboard({
  29. text: group.shortId,
  30. successMessage: t('Copied Short-ID to clipboard'),
  31. onCopy: () => {
  32. trackAnalytics('issue_details.copy_issue_short_id_clicked', {
  33. organization,
  34. ...getAnalyticsDataForGroup(group),
  35. streamline: hasStreamlinedUI,
  36. });
  37. },
  38. });
  39. const issueUrl =
  40. window.location.origin +
  41. normalizeUrl(`/organizations/${organization.slug}/issues/${group.id}/`);
  42. const {onClick: handleCopyUrl} = useCopyToClipboard({
  43. text: issueUrl,
  44. successMessage: t('Copied Issue URL to clipboard'),
  45. onCopy: () => {
  46. trackAnalytics('issue_details.copy_issue_url_clicked', {
  47. organization,
  48. ...getAnalyticsDataForGroup(group),
  49. streamline: hasStreamlinedUI,
  50. });
  51. },
  52. });
  53. const {onClick: handleCopyMarkdown} = useCopyToClipboard({
  54. text: `[${group.shortId}](${issueUrl})`,
  55. successMessage: t('Copied Markdown Issue Link to clipboard'),
  56. onCopy: () => {
  57. trackAnalytics('issue_details.copy_issue_markdown_link_clicked', {
  58. organization,
  59. ...getAnalyticsDataForGroup(group),
  60. streamline: hasStreamlinedUI,
  61. });
  62. },
  63. });
  64. if (!group.shortId) {
  65. return null;
  66. }
  67. return (
  68. <Wrapper>
  69. <ProjectBadge
  70. project={project}
  71. avatarSize={16}
  72. hideName
  73. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  74. />
  75. <ShortIdCopyable>
  76. <Tooltip
  77. title={t(
  78. 'This identifier is unique across your organization, and can be used to reference an issue in various places, like commit messages.'
  79. )}
  80. position="bottom"
  81. delay={1000}
  82. >
  83. <StyledShortId shortId={group.shortId} />
  84. </Tooltip>
  85. <DropdownMenu
  86. triggerProps={{
  87. 'aria-label': t('Issue copy actions'),
  88. icon: <Chevron direction="down" />,
  89. size: 'zero',
  90. borderless: true,
  91. showChevron: false,
  92. }}
  93. position="bottom"
  94. size="xs"
  95. items={[
  96. {
  97. key: 'copy-url',
  98. label: t('Copy Issue URL'),
  99. onAction: handleCopyUrl,
  100. },
  101. {
  102. key: 'copy-short-id',
  103. label: t('Copy Short-ID'),
  104. onAction: handleCopyShortId,
  105. },
  106. {
  107. key: 'copy-markdown-link',
  108. label: t('Copy Markdown Link'),
  109. onAction: handleCopyMarkdown,
  110. },
  111. ]}
  112. />
  113. </ShortIdCopyable>
  114. </Wrapper>
  115. );
  116. }
  117. const Wrapper = styled('div')`
  118. display: flex;
  119. gap: ${space(1)};
  120. align-items: center;
  121. `;
  122. const StyledShortId = styled(ShortId)`
  123. font-family: ${p => p.theme.text.family};
  124. font-size: ${p => p.theme.fontSizeMedium};
  125. line-height: 1;
  126. `;
  127. const ShortIdCopyable = styled('div')`
  128. display: flex;
  129. gap: ${space(0.25)};
  130. align-items: center;
  131. button[aria-haspopup] {
  132. display: block;
  133. opacity: 0;
  134. transition: opacity 50ms linear;
  135. }
  136. &:hover button[aria-haspopup],
  137. button[aria-expanded='true'],
  138. button[aria-haspopup]:focus-visible {
  139. opacity: 1;
  140. }
  141. `;