releasesSelectControl.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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';
  5. import FeatureBadge from 'sentry/components/featureBadge';
  6. import CompactSelect from 'sentry/components/forms/compactSelect';
  7. import TextOverflow from 'sentry/components/textOverflow';
  8. import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
  9. import {IconReleases} from 'sentry/icons';
  10. import {t} from 'sentry/locale';
  11. import space from 'sentry/styles/space';
  12. import {useReleases} from 'sentry/utils/releases/releasesProvider';
  13. import {DashboardFilterKeys, DashboardFilters} 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. },
  25. ];
  26. function ReleasesSelectControl({
  27. handleChangeFilter,
  28. selectedReleases,
  29. className,
  30. isDisabled,
  31. }: Props) {
  32. const {releases, loading, onSearch} = useReleases();
  33. const [activeReleases, setActiveReleases] = useState<string[]>(selectedReleases);
  34. function resetSearch() {
  35. onSearch('');
  36. }
  37. useEffect(() => {
  38. setActiveReleases(selectedReleases);
  39. }, [selectedReleases]);
  40. const triggerLabel = activeReleases.length ? (
  41. <TextOverflow>{activeReleases[0]} </TextOverflow>
  42. ) : (
  43. t('All Releases')
  44. );
  45. return (
  46. <CompactSelect
  47. multiple
  48. isClearable
  49. isSearchable
  50. isDisabled={isDisabled}
  51. isLoading={loading}
  52. menuTitle={
  53. <MenuTitleWrapper>
  54. {t('Filter Releases')}
  55. <FeatureBadge type="new" />
  56. </MenuTitleWrapper>
  57. }
  58. className={className}
  59. onInputChange={debounce(val => {
  60. onSearch(val);
  61. }, DEFAULT_DEBOUNCE_DURATION)}
  62. options={[
  63. {
  64. value: '_releases',
  65. label: t('Sorted by date created'),
  66. options: releases.length
  67. ? [
  68. ...ALIASED_RELEASES,
  69. ...releases.map(release => {
  70. return {
  71. label: release.shortVersion ?? release.version,
  72. value: release.version,
  73. };
  74. }),
  75. ]
  76. : [],
  77. },
  78. ]}
  79. onChange={opts => setActiveReleases(opts.map(opt => opt.value))}
  80. onClose={() => {
  81. resetSearch();
  82. const activeReleasesVersions = new Set(activeReleases);
  83. const activeReleasesById = releases
  84. .filter(release => activeReleasesVersions.has(release.version))
  85. .map(release => release.id);
  86. if (activeReleasesVersions.has('latest')) {
  87. activeReleasesById.push('latest');
  88. }
  89. handleChangeFilter?.({
  90. [DashboardFilterKeys.RELEASE]: activeReleases,
  91. [DashboardFilterKeys.RELEASE_ID]: activeReleasesById,
  92. });
  93. }}
  94. value={activeReleases}
  95. triggerLabel={
  96. <ButtonLabelWrapper>
  97. {triggerLabel}{' '}
  98. {activeReleases.length > 1 && (
  99. <StyledBadge text={`+${activeReleases.length - 1}`} />
  100. )}
  101. </ButtonLabelWrapper>
  102. }
  103. triggerProps={{icon: <IconReleases />}}
  104. />
  105. );
  106. }
  107. export default ReleasesSelectControl;
  108. const StyledBadge = styled(Badge)`
  109. flex-shrink: 0;
  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. `;