releaseSelector.tsx 4.7 KB

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