datasetSelectorTabs.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import {TabList, Tabs} from 'sentry/components/tabs';
  2. import {t} from 'sentry/locale';
  3. import type {SavedQuery} from 'sentry/types/organization';
  4. import type EventView from 'sentry/utils/discover/eventView';
  5. import {
  6. ERROR_ONLY_FIELDS,
  7. explodeField,
  8. getAggregations,
  9. type QueryFieldValue,
  10. TRANSACTION_ONLY_FIELDS,
  11. } from 'sentry/utils/discover/fields';
  12. import {DiscoverDatasets, SavedQueryDatasets} from 'sentry/utils/discover/types';
  13. import type {FieldKey, SpanOpBreakdown} from 'sentry/utils/fields';
  14. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  15. import {useLocation} from 'sentry/utils/useLocation';
  16. import {useNavigate} from 'sentry/utils/useNavigate';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import {
  19. getDatasetFromLocationOrSavedQueryDataset,
  20. getSavedQueryDataset,
  21. } from 'sentry/views/discover/savedQuery/utils';
  22. export const DATASET_PARAM = 'queryDataset';
  23. export const DATASET_LABEL_MAP = {
  24. [SavedQueryDatasets.ERRORS]: t('Errors'),
  25. [SavedQueryDatasets.TRANSACTIONS]: t('Transactions'),
  26. [SavedQueryDatasets.DISCOVER]: t('Unknown'),
  27. };
  28. type Props = {
  29. eventView: EventView;
  30. isHomepage: boolean | undefined;
  31. savedQuery: SavedQuery | undefined;
  32. splitDecision?: SavedQueryDatasets;
  33. };
  34. function getValidEventViewForDataset(eventView: EventView, toDataset: DiscoverDatasets) {
  35. let modifiedQuery: boolean = false;
  36. let to = eventView.clone();
  37. const allowedAggregations = Object.keys(getAggregations(toDataset));
  38. let newColumns: QueryFieldValue[] = [];
  39. const search = new MutableSearch(eventView.query);
  40. const denylistedFields =
  41. toDataset === DiscoverDatasets.ERRORS ? TRANSACTION_ONLY_FIELDS : ERROR_ONLY_FIELDS;
  42. const removedFields: string[] = [];
  43. const equationsToCheck: string[] = [];
  44. eventView.fields.forEach(field => {
  45. const column = explodeField(field);
  46. if (
  47. column.kind === 'field' &&
  48. denylistedFields.includes(column.field as FieldKey | SpanOpBreakdown)
  49. ) {
  50. search.removeFilter(field.field);
  51. removedFields.push(field.field);
  52. modifiedQuery = true;
  53. return;
  54. }
  55. if (column.kind === 'function') {
  56. if (!allowedAggregations.includes(column.function[0])) {
  57. search.removeFilter(field.field);
  58. removedFields.push(field.field);
  59. modifiedQuery = true;
  60. return;
  61. }
  62. if (denylistedFields.includes(column.function[1] as FieldKey | SpanOpBreakdown)) {
  63. search.removeFilter(field.field);
  64. removedFields.push(field.field);
  65. modifiedQuery = true;
  66. return;
  67. }
  68. }
  69. if (column.kind === 'equation') {
  70. equationsToCheck.push(field.field);
  71. }
  72. newColumns.push(column);
  73. });
  74. newColumns = newColumns.filter(column => {
  75. if (column.kind !== 'equation') {
  76. return true;
  77. }
  78. return removedFields.some(f => !column.field.includes(f));
  79. });
  80. const remainingSearchFilter = search.formatString();
  81. for (let index = 0; index < denylistedFields.length; index++) {
  82. const element = denylistedFields[index];
  83. if (remainingSearchFilter.includes(element)) {
  84. search.removeFilter(element);
  85. modifiedQuery = true;
  86. }
  87. }
  88. to = to.withColumns(newColumns);
  89. to.query = search.formatString();
  90. return {to, modifiedQuery};
  91. }
  92. export function DatasetSelectorTabs(props: Props) {
  93. const {savedQuery, isHomepage, splitDecision, eventView} = props;
  94. const location = useLocation();
  95. const organization = useOrganization();
  96. const navigate = useNavigate();
  97. const value = getSavedQueryDataset(organization, location, savedQuery, splitDecision);
  98. const options = [
  99. {
  100. value: SavedQueryDatasets.ERRORS,
  101. label: DATASET_LABEL_MAP[SavedQueryDatasets.ERRORS],
  102. },
  103. {
  104. value: SavedQueryDatasets.TRANSACTIONS,
  105. label: DATASET_LABEL_MAP[SavedQueryDatasets.TRANSACTIONS],
  106. },
  107. ];
  108. if (value === 'discover') {
  109. options.push({
  110. value: SavedQueryDatasets.DISCOVER,
  111. label: DATASET_LABEL_MAP[SavedQueryDatasets.DISCOVER],
  112. });
  113. }
  114. return (
  115. <Tabs
  116. value={value}
  117. onChange={newValue => {
  118. const {to: nextEventView, modifiedQuery} = getValidEventViewForDataset(
  119. eventView.withDataset(
  120. getDatasetFromLocationOrSavedQueryDataset(undefined, newValue)
  121. ),
  122. newValue === SavedQueryDatasets.ERRORS
  123. ? DiscoverDatasets.ERRORS
  124. : DiscoverDatasets.TRANSACTIONS
  125. );
  126. const nextLocation = nextEventView.getResultsViewUrlTarget(
  127. organization.slug,
  128. isHomepage
  129. );
  130. navigate({
  131. ...location,
  132. query: {
  133. ...nextLocation.query,
  134. [DATASET_PARAM]: newValue,
  135. incompatible: modifiedQuery ? modifiedQuery : undefined,
  136. },
  137. });
  138. }}
  139. >
  140. <TabList variant="filled" hideBorder>
  141. {options.map(option => (
  142. <TabList.Item key={option.value}>{option.label}</TabList.Item>
  143. ))}
  144. </TabList>
  145. </Tabs>
  146. );
  147. }