toolbarSortBy.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import {useCallback, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {SelectKey, SelectOption} from 'sentry/components/compactSelect';
  4. import {CompactSelect} from 'sentry/components/compactSelect';
  5. import {Tooltip} from 'sentry/components/tooltip';
  6. import {t} from 'sentry/locale';
  7. import type {Sort} from 'sentry/utils/discover/fields';
  8. import {
  9. classifyTagKey,
  10. parseFunction,
  11. prettifyParsedFunction,
  12. prettifyTagKey,
  13. } from 'sentry/utils/discover/fields';
  14. import {TypeBadge} from 'sentry/views/explore/components/typeBadge';
  15. import {useExploreMode} from 'sentry/views/explore/contexts/pageParamsContext';
  16. import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
  17. import type {Visualize} from 'sentry/views/explore/contexts/pageParamsContext/visualizes';
  18. import {useSpanTags} from 'sentry/views/explore/contexts/spanTagsContext';
  19. import {Tab, useTab} from 'sentry/views/explore/hooks/useTab';
  20. import {ToolbarHeader, ToolbarLabel, ToolbarRow, ToolbarSection} from './styles';
  21. interface ToolbarSortByProps {
  22. fields: string[];
  23. groupBys: string[];
  24. setSorts: (newSorts: Sort[]) => void;
  25. sorts: Sort[];
  26. visualizes: Visualize[];
  27. }
  28. export function ToolbarSortBy({
  29. fields,
  30. groupBys,
  31. setSorts,
  32. sorts,
  33. visualizes,
  34. }: ToolbarSortByProps) {
  35. const mode = useExploreMode();
  36. const [tab] = useTab();
  37. // traces table is only sorted by timestamp so disable the sort by
  38. const disabled = mode === Mode.SAMPLES && tab === Tab.TRACE;
  39. const numberTags = useSpanTags('number');
  40. const stringTags = useSpanTags('string');
  41. const fieldOptions: SelectOption<string>[] = useMemo(() => {
  42. const uniqueOptions: string[] = [];
  43. if (mode === Mode.SAMPLES) {
  44. for (const field of fields) {
  45. if (!uniqueOptions.includes(field)) {
  46. uniqueOptions.push(field);
  47. }
  48. }
  49. } else {
  50. for (const visualize of visualizes) {
  51. for (const yAxis of visualize.yAxes) {
  52. if (!uniqueOptions.includes(yAxis)) {
  53. uniqueOptions.push(yAxis);
  54. }
  55. }
  56. }
  57. for (const groupBy of groupBys) {
  58. if (!uniqueOptions.includes(groupBy)) {
  59. uniqueOptions.push(groupBy);
  60. }
  61. }
  62. }
  63. const options = uniqueOptions.filter(Boolean).map(field => {
  64. const tag = stringTags[field] ?? numberTags[field] ?? null;
  65. if (tag) {
  66. return {
  67. label: tag.name,
  68. value: field,
  69. textValue: tag.name,
  70. trailingItems: <TypeBadge kind={tag?.kind} />,
  71. };
  72. }
  73. const func = parseFunction(field);
  74. if (func) {
  75. const formatted = prettifyParsedFunction(func);
  76. return {
  77. label: formatted,
  78. value: field,
  79. textValue: formatted,
  80. trailingItems: <TypeBadge func={func} />,
  81. };
  82. }
  83. return {
  84. label: prettifyTagKey(field),
  85. value: field,
  86. textValue: field,
  87. trailingItems: <TypeBadge kind={classifyTagKey(field)} />,
  88. };
  89. });
  90. options.sort((a, b) => {
  91. if (a.label < b.label) {
  92. return -1;
  93. }
  94. if (a.label > b.label) {
  95. return 1;
  96. }
  97. return 0;
  98. });
  99. return options;
  100. }, [fields, groupBys, mode, numberTags, stringTags, visualizes]);
  101. const setSortField = useCallback(
  102. (i: number, {value}: SelectOption<SelectKey>) => {
  103. if (sorts[i] && typeof value === 'string') {
  104. setSorts([
  105. {
  106. field: value,
  107. kind: sorts[i].kind,
  108. },
  109. ]);
  110. }
  111. },
  112. [setSorts, sorts]
  113. );
  114. const kindOptions: SelectOption<Sort['kind']>[] = useMemo(() => {
  115. return [
  116. {
  117. label: 'Desc',
  118. value: 'desc',
  119. textValue: t('Descending'),
  120. },
  121. {
  122. label: 'Asc',
  123. value: 'asc',
  124. textValue: t('Ascending'),
  125. },
  126. ];
  127. }, []);
  128. const setSortKind = useCallback(
  129. (i: number, {value}: SelectOption<SelectKey>) => {
  130. if (sorts[i]) {
  131. setSorts([
  132. {
  133. field: sorts[i].field,
  134. kind: value as Sort['kind'],
  135. },
  136. ]);
  137. }
  138. },
  139. [setSorts, sorts]
  140. );
  141. let toolbarRow = (
  142. <ToolbarRow>
  143. <ColumnCompactSelect
  144. options={fieldOptions}
  145. value={sorts[0]?.field}
  146. onChange={newSortField => setSortField(0, newSortField)}
  147. disabled={disabled}
  148. />
  149. <DirectionCompactSelect
  150. options={kindOptions}
  151. value={sorts[0]?.kind}
  152. onChange={newSortKind => setSortKind(0, newSortKind)}
  153. disabled={disabled}
  154. />
  155. </ToolbarRow>
  156. );
  157. if (disabled) {
  158. toolbarRow = (
  159. <FullWidthTooltip
  160. position="top"
  161. title={t('Sort by is not applicable to trace results.')}
  162. >
  163. {toolbarRow}
  164. </FullWidthTooltip>
  165. );
  166. }
  167. return (
  168. <ToolbarSection data-test-id="section-sort-by">
  169. <ToolbarHeader>
  170. <Tooltip
  171. position="right"
  172. title={t('Results you see first and last in your samples or aggregates.')}
  173. >
  174. <ToolbarLabel disabled={disabled}>{t('Sort By')}</ToolbarLabel>
  175. </Tooltip>
  176. </ToolbarHeader>
  177. <div>{toolbarRow}</div>
  178. </ToolbarSection>
  179. );
  180. }
  181. const FullWidthTooltip = styled(Tooltip)`
  182. width: 100%;
  183. `;
  184. const ColumnCompactSelect = styled(CompactSelect)`
  185. flex: 1 1;
  186. min-width: 0;
  187. > button {
  188. width: 100%;
  189. }
  190. `;
  191. const DirectionCompactSelect = styled(CompactSelect)`
  192. width: 90px;
  193. > button {
  194. width: 100%;
  195. }
  196. `;