releasesSelectControl.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import {useEffect, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import debounce from 'lodash/debounce';
  4. import Badge from 'sentry/components/badge/badge';
  5. import {CompactSelect} from 'sentry/components/compactSelect';
  6. import TextOverflow from 'sentry/components/textOverflow';
  7. import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
  8. import {IconReleases} from 'sentry/icons';
  9. import {t} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import {useReleases} from 'sentry/utils/releases/releasesProvider';
  12. import type {DashboardFilters} from './types';
  13. import {DashboardFilterKeys} from './types';
  14. type Props = {
  15. selectedReleases: string[];
  16. className?: string;
  17. handleChangeFilter?: (activeFilters: DashboardFilters) => void;
  18. isDisabled?: boolean;
  19. };
  20. const ALIASED_RELEASES = [
  21. {
  22. label: t('Latest Release(s)'),
  23. value: 'latest',
  24. tooltip: t(
  25. 'The highest version number for Semantic Versioning or the most recent release for commit SHA.'
  26. ),
  27. },
  28. ];
  29. function ReleasesSelectControl({
  30. handleChangeFilter,
  31. selectedReleases,
  32. className,
  33. isDisabled,
  34. }: Props) {
  35. const {releases, loading, onSearch} = useReleases();
  36. const [activeReleases, setActiveReleases] = useState<string[]>(selectedReleases);
  37. function resetSearch() {
  38. onSearch('');
  39. }
  40. useEffect(() => {
  41. setActiveReleases(selectedReleases);
  42. }, [selectedReleases]);
  43. const triggerLabel = activeReleases.length ? (
  44. <TextOverflow>{activeReleases[0]} </TextOverflow>
  45. ) : (
  46. t('All Releases')
  47. );
  48. const activeReleasesSet = new Set(activeReleases);
  49. return (
  50. <StyledCompactSelect
  51. multiple
  52. clearable
  53. searchable
  54. disabled={isDisabled}
  55. loading={loading}
  56. menuTitle={<MenuTitleWrapper>{t('Filter Releases')}</MenuTitleWrapper>}
  57. className={className}
  58. onSearch={debounce(val => {
  59. onSearch(val);
  60. }, DEFAULT_DEBOUNCE_DURATION)}
  61. options={[
  62. {
  63. value: '_releases',
  64. label: t('Sorted by date created'),
  65. options: [
  66. ...ALIASED_RELEASES,
  67. ...activeReleases
  68. .filter(version => version !== 'latest')
  69. .map(version => ({
  70. label: version,
  71. value: version,
  72. })),
  73. ...releases
  74. .filter(({version}) => !activeReleasesSet.has(version))
  75. .map(({version}) => ({
  76. label: version,
  77. value: version,
  78. })),
  79. ],
  80. },
  81. ]}
  82. onChange={opts => setActiveReleases(opts.map(opt => opt.value as string))}
  83. onClose={() => {
  84. resetSearch();
  85. handleChangeFilter?.({
  86. [DashboardFilterKeys.RELEASE]: activeReleases,
  87. });
  88. }}
  89. value={activeReleases}
  90. triggerLabel={
  91. <ButtonLabelWrapper>
  92. {triggerLabel}{' '}
  93. {activeReleases.length > 1 && (
  94. <StyledBadge text={`+${activeReleases.length - 1}`} />
  95. )}
  96. </ButtonLabelWrapper>
  97. }
  98. triggerProps={{icon: <IconReleases />}}
  99. />
  100. );
  101. }
  102. export default ReleasesSelectControl;
  103. const StyledBadge = styled(Badge)`
  104. flex-shrink: 0;
  105. `;
  106. const StyledCompactSelect = styled(CompactSelect)`
  107. @media (min-width: ${p => p.theme.breakpoints.small}) {
  108. max-width: 300px;
  109. }
  110. `;
  111. const ButtonLabelWrapper = styled('span')`
  112. width: 100%;
  113. text-align: left;
  114. align-items: center;
  115. display: inline-grid;
  116. grid-template-columns: 1fr auto;
  117. `;
  118. const MenuTitleWrapper = styled('span')`
  119. display: inline-block;
  120. padding-top: ${space(0.5)};
  121. padding-bottom: ${space(0.5)};
  122. `;