associations.tsx 3.2 KB

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