releaseSelector.tsx 5.0 KB

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