version.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import {css} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton';
  4. import GlobalSelectionLink from 'sentry/components/globalSelectionLink';
  5. import Link from 'sentry/components/links/link';
  6. import {Tooltip} from 'sentry/components/tooltip';
  7. import {Organization} from 'sentry/types';
  8. import {formatVersion} from 'sentry/utils/formatters';
  9. import theme from 'sentry/utils/theme';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. import withOrganization from 'sentry/utils/withOrganization';
  12. type Props = {
  13. /**
  14. * Organization injected by withOrganization HOC
  15. */
  16. organization: Organization;
  17. /**
  18. * Raw version (canonical release identifier)
  19. */
  20. version: string;
  21. /**
  22. * Should the version be a link to the release page
  23. */
  24. anchor?: boolean;
  25. className?: string;
  26. /**
  27. * Should link to release page preserve user's page filter values
  28. */
  29. preservePageFilters?: boolean;
  30. /**
  31. * Will add project ID to the linked url (can be overridden by preservePageFilters).
  32. * If not provided and user does not have global-views enabled, it will try to take it from current url query.
  33. */
  34. projectId?: string;
  35. /**
  36. * Should there be a tooltip with raw version on hover
  37. */
  38. tooltipRawVersion?: boolean;
  39. /**
  40. * Ellipsis on overflow
  41. */
  42. truncate?: boolean;
  43. /**
  44. * Should we also show package name
  45. */
  46. withPackage?: boolean;
  47. };
  48. function Version({
  49. version,
  50. organization,
  51. anchor = true,
  52. preservePageFilters,
  53. tooltipRawVersion,
  54. withPackage,
  55. projectId,
  56. truncate,
  57. className,
  58. }: Props) {
  59. const location = useLocation();
  60. const versionToDisplay = formatVersion(version, withPackage);
  61. let releaseDetailProjectId: null | undefined | string | string[];
  62. if (projectId) {
  63. // we can override preservePageFilters's project id
  64. releaseDetailProjectId = projectId;
  65. } else if (!organization?.features.includes('global-views')) {
  66. // we need this for users without global-views, otherwise they might get `This release may not be in your selected project`
  67. releaseDetailProjectId = location?.query.project;
  68. }
  69. const renderVersion = () => {
  70. if (anchor && organization?.slug) {
  71. const props = {
  72. to: {
  73. pathname: `/organizations/${organization?.slug}/releases/${encodeURIComponent(
  74. version
  75. )}/`,
  76. query: releaseDetailProjectId ? {project: releaseDetailProjectId} : undefined,
  77. },
  78. className,
  79. };
  80. if (preservePageFilters) {
  81. return (
  82. <GlobalSelectionLink {...props}>
  83. <VersionText truncate={truncate}>{versionToDisplay}</VersionText>
  84. </GlobalSelectionLink>
  85. );
  86. }
  87. return (
  88. <Link {...props}>
  89. <VersionText truncate={truncate}>{versionToDisplay}</VersionText>
  90. </Link>
  91. );
  92. }
  93. return (
  94. <VersionText className={className} truncate={truncate}>
  95. {versionToDisplay}
  96. </VersionText>
  97. );
  98. };
  99. const renderTooltipContent = () => (
  100. <TooltipContent
  101. onClick={e => {
  102. e.stopPropagation();
  103. }}
  104. >
  105. <TooltipVersionWrapper>{version}</TooltipVersionWrapper>
  106. <CopyToClipboardButton borderless text={version} size="zero" iconSize="xs" />
  107. </TooltipContent>
  108. );
  109. const getOverlayStyle = () => {
  110. // if the version name is not a hash (sha1 or sha265) and we are not on
  111. // mobile, allow tooltip to be as wide as 500px
  112. if (/(^[a-f0-9]{40}$)|(^[a-f0-9]{64}$)/.test(version)) {
  113. return undefined;
  114. }
  115. return css`
  116. @media (min-width: ${theme.breakpoints.small}) {
  117. max-width: 500px;
  118. }
  119. `;
  120. };
  121. return (
  122. <Tooltip
  123. title={renderTooltipContent()}
  124. disabled={!tooltipRawVersion}
  125. isHoverable
  126. containerDisplayMode={truncate ? 'block' : 'inline-block'}
  127. overlayStyle={getOverlayStyle()}
  128. >
  129. {renderVersion()}
  130. </Tooltip>
  131. );
  132. }
  133. // TODO(matej): try to wrap version with this when truncate prop is true (in separate PR)
  134. // const VersionWrapper = styled('div')`
  135. // ${p => p.theme.overflowEllipsis};
  136. // max-width: 100%;
  137. // width: auto;
  138. // display: inline-block;
  139. // `;
  140. const VersionText = styled('span')<{truncate?: boolean}>`
  141. ${p =>
  142. p.truncate &&
  143. `max-width: 100%;
  144. display: block;
  145. overflow: hidden;
  146. font-variant-numeric: tabular-nums;
  147. text-overflow: ellipsis;
  148. white-space: nowrap;`}
  149. `;
  150. const TooltipContent = styled('span')`
  151. display: flex;
  152. align-items: center;
  153. `;
  154. const TooltipVersionWrapper = styled('span')`
  155. ${p => p.theme.overflowEllipsis}
  156. `;
  157. export default withOrganization(Version);