associations.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import styled from '@emotion/styled';
  2. import ClippedBox from 'sentry/components/clippedBox';
  3. import {CopyToClipboardButton} from 'sentry/components/copyToClipboardButton';
  4. import {Hovercard} from 'sentry/components/hovercard';
  5. import Link from 'sentry/components/links/link';
  6. import List from 'sentry/components/list';
  7. import ListItem from 'sentry/components/list/listItem';
  8. import Placeholder from 'sentry/components/placeholder';
  9. import TextOverflow from 'sentry/components/textOverflow';
  10. import {t, tn} from 'sentry/locale';
  11. import {space} from 'sentry/styles/space';
  12. import useOrganization from 'sentry/utils/useOrganization';
  13. import {makeReleasesPathname} from 'sentry/views/releases/utils/pathnames';
  14. import type {ProguardMappingAssociation} from 'sentry/views/settings/projectProguard';
  15. function ProguardAssociationsBody({
  16. associations,
  17. }: {
  18. associations: ProguardMappingAssociation;
  19. }) {
  20. const organization = useOrganization();
  21. return (
  22. <ClippedBoxWithoutPadding
  23. clipHeight={210}
  24. btnText={t(
  25. '+ %s more',
  26. associations.releases.length - associations.releases.slice(0, 4).length
  27. )}
  28. buttonProps={{
  29. priority: 'default',
  30. borderless: true,
  31. }}
  32. >
  33. <List symbol="bullet">
  34. {associations.releases.map(release => (
  35. <ListItem key={release}>
  36. <ReleaseContent>
  37. <ReleaseLink
  38. to={makeReleasesPathname({
  39. organization,
  40. path: `/${release}/`,
  41. })}
  42. >
  43. <TextOverflow>{release}</TextOverflow>
  44. </ReleaseLink>
  45. <CopyToClipboardButton
  46. text={release}
  47. borderless
  48. size="zero"
  49. iconSize="sm"
  50. />
  51. </ReleaseContent>
  52. </ListItem>
  53. ))}
  54. </List>
  55. </ClippedBoxWithoutPadding>
  56. );
  57. }
  58. type Props = {
  59. associations: ProguardMappingAssociation;
  60. loading?: boolean;
  61. };
  62. export function ProguardAssociations({associations, loading}: Props) {
  63. if (loading) {
  64. return <Placeholder width="200px" height="20px" />;
  65. }
  66. if (!associations.releases.length) {
  67. return (
  68. <NoAssociations>
  69. {t('No releases associated with this proguard mapping file')}
  70. </NoAssociations>
  71. );
  72. }
  73. return (
  74. <div>
  75. <WiderHovercard
  76. position="right"
  77. body={<ProguardAssociationsBody associations={associations} />}
  78. header={t('Releases')}
  79. displayTimeout={0}
  80. showUnderline
  81. >
  82. {tn('%s Release', '%s Releases', associations.releases.length)}
  83. </WiderHovercard>{' '}
  84. {t('associated')}
  85. </div>
  86. );
  87. }
  88. const NoAssociations = styled('div')`
  89. color: ${p => p.theme.disabled};
  90. `;
  91. const ReleaseContent = styled('div')`
  92. display: grid;
  93. grid-template-columns: 1fr max-content;
  94. gap: ${space(1)};
  95. align-items: center;
  96. `;
  97. const ReleaseLink = styled(Link)`
  98. overflow: hidden;
  99. `;
  100. const WiderHovercard = styled(Hovercard)`
  101. width: 320px;
  102. /* "Body" element */
  103. > div:last-child {
  104. transition: all 5s ease-in-out;
  105. overflow-x: hidden;
  106. overflow-y: scroll;
  107. max-height: 300px;
  108. }
  109. `;
  110. const ClippedBoxWithoutPadding = styled(ClippedBox)`
  111. padding: 0;
  112. /* "ClipFade" element */
  113. > div:last-child {
  114. background: ${p => p.theme.background};
  115. border-bottom: 0;
  116. padding: 0;
  117. }
  118. `;