releaseSelector.tsx 4.4 KB

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