releaseSelector.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import {useState} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import debounce from 'lodash/debounce';
  5. import {CompactSelect, SelectOption} from 'sentry/components/compactSelect';
  6. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  7. import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants';
  8. import {IconReleases} from 'sentry/icons/iconReleases';
  9. import {t, tn} from 'sentry/locale';
  10. import {space} from 'sentry/styles/space';
  11. import {defined} from 'sentry/utils';
  12. import {getFormattedDate} from 'sentry/utils/dates';
  13. import {useLocation} from 'sentry/utils/useLocation';
  14. import {
  15. useReleases,
  16. useReleaseSelection,
  17. } from 'sentry/views/starfish/queries/useReleases';
  18. import {formatVersionAndCenterTruncate} from 'sentry/views/starfish/utils/centerTruncate';
  19. type Props = {
  20. selectorKey: string;
  21. selectorName?: string;
  22. selectorValue?: string;
  23. };
  24. export function ReleaseSelector({selectorKey, selectorValue}: Props) {
  25. const [searchTerm, setSearchTerm] = useState<string | undefined>(undefined);
  26. const {data, isLoading} = useReleases(searchTerm);
  27. const {primaryRelease, secondaryRelease} = useReleaseSelection();
  28. const location = useLocation();
  29. const options: SelectOption<string>[] = [];
  30. if (defined(selectorValue)) {
  31. const index = data?.findIndex(({version}) => version === selectorValue);
  32. const selectedRelease = defined(index) ? data?.[index] : undefined;
  33. let selectedReleaseSessionCount: number | undefined = undefined;
  34. let selectedReleaseDateCreated: string | undefined = undefined;
  35. if (defined(selectedRelease)) {
  36. selectedReleaseSessionCount = selectedRelease.count;
  37. selectedReleaseDateCreated = selectedRelease.dateCreated;
  38. }
  39. options.push({
  40. value: selectorValue,
  41. label: selectorValue,
  42. details: (
  43. <LabelDetails
  44. screenCount={selectedReleaseSessionCount}
  45. dateCreated={selectedReleaseDateCreated}
  46. />
  47. ),
  48. });
  49. }
  50. data
  51. ?.filter(({version}) => ![primaryRelease, secondaryRelease].includes(version))
  52. .forEach(release => {
  53. const option = {
  54. value: release.version,
  55. label: release.version,
  56. details: (
  57. <LabelDetails screenCount={release.count} dateCreated={release.dateCreated} />
  58. ),
  59. };
  60. options.push(option);
  61. });
  62. return (
  63. <StyledCompactSelect
  64. triggerProps={{
  65. icon: <IconReleases />,
  66. title: selectorValue,
  67. }}
  68. triggerLabel={
  69. selectorValue ? formatVersionAndCenterTruncate(selectorValue, 16) : selectorValue
  70. }
  71. menuTitle={t('Filter Release')}
  72. loading={isLoading}
  73. searchable
  74. value={selectorValue}
  75. options={[
  76. {
  77. value: '_releases',
  78. label: t('Sorted by date created'),
  79. options,
  80. },
  81. ]}
  82. onSearch={debounce(val => {
  83. setSearchTerm(val);
  84. }, DEFAULT_DEBOUNCE_DURATION)}
  85. onChange={newValue => {
  86. browserHistory.push({
  87. ...location,
  88. query: {
  89. ...location.query,
  90. [selectorKey]: newValue.value,
  91. },
  92. });
  93. }}
  94. onClose={() => {
  95. setSearchTerm(undefined);
  96. }}
  97. />
  98. );
  99. }
  100. type LabelDetailsProps = {
  101. dateCreated?: string;
  102. screenCount?: number;
  103. };
  104. function LabelDetails(props: LabelDetailsProps) {
  105. return (
  106. <DetailsContainer>
  107. <div>
  108. {defined(props.screenCount)
  109. ? tn('%s event', '%s events', props.screenCount)
  110. : t('No screens')}
  111. </div>
  112. <div>
  113. {defined(props.dateCreated)
  114. ? getFormattedDate(props.dateCreated, 'MMM D, YYYY')
  115. : null}
  116. </div>
  117. </DetailsContainer>
  118. );
  119. }
  120. export function ReleaseComparisonSelector() {
  121. const {primaryRelease, secondaryRelease} = useReleaseSelection();
  122. return (
  123. <StyledPageSelector condensed>
  124. <ReleaseSelector
  125. selectorKey="primaryRelease"
  126. selectorValue={primaryRelease}
  127. selectorName={t('Release 1')}
  128. key="primaryRelease"
  129. />
  130. <ReleaseSelector
  131. selectorKey="secondaryRelease"
  132. selectorName={t('Release 2')}
  133. selectorValue={secondaryRelease}
  134. key="secondaryRelease"
  135. />
  136. </StyledPageSelector>
  137. );
  138. }
  139. const StyledCompactSelect = styled(CompactSelect)`
  140. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  141. max-width: 275px;
  142. }
  143. `;
  144. const StyledPageSelector = styled(PageFilterBar)`
  145. & > * {
  146. min-width: 135px;
  147. &:last-child {
  148. min-width: 135px;
  149. }
  150. }
  151. `;
  152. const DetailsContainer = styled('div')`
  153. display: flex;
  154. flex-direction: row;
  155. justify-content: space-between;
  156. gap: ${space(1)};
  157. min-width: 200px;
  158. `;