shortIdBreadcrumb.tsx 3.3 KB

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