datasetSelectorTabs.tsx 5.0 KB

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