issueIdBreadcrumb.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import {useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import {openModal} from 'sentry/actionCreators/modal';
  4. import {Button} from 'sentry/components/button';
  5. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  6. import ExternalLink from 'sentry/components/links/externalLink';
  7. import ShortId from 'sentry/components/shortId';
  8. import {Tooltip} from 'sentry/components/tooltip';
  9. import {IconCopy, IconGlobe} from 'sentry/icons';
  10. import {t, tct} from 'sentry/locale';
  11. import {space} from 'sentry/styles/space';
  12. import type {Group} from 'sentry/types/group';
  13. import type {Project} from 'sentry/types/project';
  14. import {trackAnalytics} from 'sentry/utils/analytics';
  15. import {getAnalyticsDataForGroup} from 'sentry/utils/events';
  16. import useCopyToClipboard from 'sentry/utils/useCopyToClipboard';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import PublishIssueModal from 'sentry/views/issueDetails/actions/publishModal';
  19. import {getShareUrl} from 'sentry/views/issueDetails/actions/shareModal';
  20. interface ShortIdBreadcrumbProps {
  21. group: Group;
  22. project: Project;
  23. }
  24. export function IssueIdBreadcrumb({project, group}: ShortIdBreadcrumbProps) {
  25. const organization = useOrganization();
  26. const [isHovered, setIsHovered] = useState(false);
  27. const shareUrl = group?.shareId ? getShareUrl(group) : null;
  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: true,
  36. });
  37. },
  38. });
  39. if (!group.shortId) {
  40. return null;
  41. }
  42. return (
  43. <BreadcrumbContainer>
  44. <Wrapper>
  45. <ProjectBadge
  46. project={project}
  47. avatarSize={16}
  48. hideName
  49. avatarProps={{hasTooltip: true, tooltip: project.slug}}
  50. />
  51. <ShortIdCopyable
  52. onMouseEnter={() => setIsHovered(true)}
  53. onMouseLeave={() => setIsHovered(false)}
  54. >
  55. <Tooltip
  56. title={t(
  57. 'This identifier is unique across your organization, and can be used to reference an issue in various places, like commit messages.'
  58. )}
  59. position="bottom"
  60. delay={1000}
  61. >
  62. <StyledShortId onClick={handleCopyShortId} shortId={group.shortId} />
  63. </Tooltip>
  64. {isHovered && (
  65. <Button
  66. title={t('Copy Issue Short-ID')}
  67. aria-label={t('Copy Issue Short-ID')}
  68. onClick={handleCopyShortId}
  69. size="zero"
  70. borderless
  71. icon={<IconCopy size="xs" color="subText" />}
  72. />
  73. )}
  74. </ShortIdCopyable>
  75. </Wrapper>
  76. {!isHovered && group.isPublic && shareUrl && (
  77. <Button
  78. size="zero"
  79. borderless
  80. aria-label={t('View issue share settings')}
  81. title={tct('This issue has been shared [link:with a public link].', {
  82. link: <ExternalLink href={shareUrl} />,
  83. })}
  84. tooltipProps={{isHoverable: true}}
  85. icon={
  86. <IconGlobe
  87. size="xs"
  88. color="subText"
  89. onClick={() =>
  90. openModal(modalProps => (
  91. <PublishIssueModal
  92. {...modalProps}
  93. organization={organization}
  94. projectSlug={group.project.slug}
  95. groupId={group.id}
  96. onToggle={() =>
  97. trackAnalytics('issue.shared_publicly', {
  98. organization,
  99. })
  100. }
  101. />
  102. ))
  103. }
  104. />
  105. }
  106. />
  107. )}
  108. </BreadcrumbContainer>
  109. );
  110. }
  111. const BreadcrumbContainer = styled('div')`
  112. display: flex;
  113. gap: ${space(0.5)};
  114. `;
  115. const Wrapper = styled('div')`
  116. display: flex;
  117. gap: ${space(1)};
  118. align-items: center;
  119. `;
  120. const StyledShortId = styled(ShortId)`
  121. font-family: ${p => p.theme.text.family};
  122. font-size: ${p => p.theme.fontSizeMedium};
  123. line-height: 1;
  124. `;
  125. const ShortIdCopyable = styled('div')`
  126. display: flex;
  127. gap: ${space(0.5)};
  128. align-items: center;
  129. button[aria-haspopup] {
  130. display: block;
  131. opacity: 0;
  132. transition: opacity 50ms linear;
  133. }
  134. `;