version.tsx 5.0 KB

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