toolbarSortBy.tsx 4.8 KB

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