Browse Source

ref(dashboards): Add sort by options to config (#35965)

Move sort by steps to the config. This includes:

- getting the sort options for table widgets
- getting sort options for timeseries widgets
- filtering sort options on time series widgets
- filtering aggregate params on time series sorts
- enabling/disabling sort options
Shruthi 2 years ago
parent
commit
8c58794670

+ 35 - 2
static/app/views/dashboardsV2/datasetConfig/base.tsx

@@ -59,6 +59,15 @@ export interface DatasetConfig<SeriesResponse, TableResponse> {
     organization: Organization,
     pageFilters: PageFilters
   ) => TableData;
+  /**
+   * Configure enabling/disabling sort/direction options with an
+   * optional message for why it is disabled.
+   */
+  disableSortOptions?: (widgetQuery: WidgetQuery) => {
+    disableSort: boolean;
+    disableSortDirection: boolean;
+    disableSortReason?: string;
+  };
   /**
    * Used for mapping column names to more desirable
    * values in tables.
@@ -66,10 +75,17 @@ export interface DatasetConfig<SeriesResponse, TableResponse> {
   fieldHeaderMap?: Record<string, string>;
   /**
    * Filter the options available to the parameters list
-   * of an aggregate function in a table widget column on the
+   * of an aggregate function in QueryField component on the
    * Widget Builder.
    */
-  filterTableAggregateParams?: (option: FieldValueOption) => boolean;
+  filterAggregateParams?: (option: FieldValueOption) => boolean;
+  /**
+   * Refine the options available in the sort options for timeseries
+   * displays on the 'Sort by' step of the Widget Builder.
+   */
+  filterSeriesSortOptions?: (
+    columns: Set<string>
+  ) => (option: FieldValueOption) => boolean;
   /**
    * Filter the primary options available in a table widget
    * columns on the Widget Builder.
@@ -115,6 +131,23 @@ export interface DatasetConfig<SeriesResponse, TableResponse> {
     cursor?: string,
     referrer?: string
   ) => ReturnType<Client['requestPromise']>;
+  /**
+   * Generate the list of sort options for table
+   * displays on the 'Sort by' step of the Widget Builder.
+   */
+  getTableSortOptions?: (
+    organization: Organization,
+    widgetQuery: WidgetQuery
+  ) => SelectValue<string>[];
+  /**
+   * Generate the list of sort options for timeseries
+   * displays on the 'Sort by' step of the Widget Builder.
+   */
+  getTimeseriesSortOptions?: (
+    organization: Organization,
+    widgetQuery: WidgetQuery,
+    tags?: TagCollection
+  ) => Record<string, SelectValue<FieldValue>>;
   /**
    * Generate the request promises for fetching
    * world map data.

+ 86 - 0
static/app/views/dashboardsV2/datasetConfig/errorsAndTransactions.tsx

@@ -11,6 +11,7 @@ import {
   MultiSeriesEventsStats,
   Organization,
   PageFilters,
+  SelectValue,
   TagCollection,
 } from 'sentry/types';
 import {Series} from 'sentry/types/echarts';
@@ -24,6 +25,7 @@ import {
   isEquation,
   isEquationAlias,
   SPAN_OP_BREAKDOWN_FIELDS,
+  stripEquationPrefix,
 } from 'sentry/utils/discover/fields';
 import {
   DiscoverQueryRequestParams,
@@ -37,6 +39,8 @@ import {
 } from 'sentry/utils/discover/urls';
 import {getShortEventId} from 'sentry/utils/events';
 import {getMeasurements} from 'sentry/utils/measurements/measurements';
+import {FieldValueOption} from 'sentry/views/eventsV2/table/queryField';
+import {FieldValue, FieldValueKind} from 'sentry/views/eventsV2/table/types';
 import {generateFieldOptions} from 'sentry/views/eventsV2/utils';
 import {getTraceDetailsUrl} from 'sentry/views/performance/traceDetails/utils';
 
@@ -48,6 +52,7 @@ import {
   getWidgetInterval,
 } from '../utils';
 import {EventsSearchBar} from '../widgetBuilder/buildSteps/filterResultsStep/eventsSearchBar';
+import {CUSTOM_EQUATION_VALUE} from '../widgetBuilder/buildSteps/sortByStep';
 import {
   flattenMultiSeriesDataWithGrouping,
   transformSeries,
@@ -74,7 +79,10 @@ export const ErrorsAndTransactionsConfig: DatasetConfig<
   defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
   getCustomFieldRenderer: getCustomEventsFieldRenderer,
   SearchBar: EventsSearchBar,
+  filterSeriesSortOptions,
   getTableFieldOptions: getEventsTableFieldOptions,
+  getTimeseriesSortOptions,
+  getTableSortOptions,
   getGroupByFieldOptions: getEventsTableFieldOptions,
   handleOrderByReset,
   supportedDisplayTypes: [
@@ -137,6 +145,84 @@ export const ErrorsAndTransactionsConfig: DatasetConfig<
   transformTable: transformEventsResponseToTable,
 };
 
+function getTableSortOptions(_organization: Organization, widgetQuery: WidgetQuery) {
+  const {columns, aggregates} = widgetQuery;
+  const options: SelectValue<string>[] = [];
+  let equations = 0;
+  [...aggregates, ...columns]
+    .filter(field => !!field)
+    .forEach(field => {
+      let alias;
+      const label = stripEquationPrefix(field);
+      // Equations are referenced via a standard alias following this pattern
+      if (isEquation(field)) {
+        alias = `equation[${equations}]`;
+        equations += 1;
+      }
+
+      options.push({label, value: alias ?? field});
+    });
+
+  return options;
+}
+
+function filterSeriesSortOptions(columns: Set<string>) {
+  return (option: FieldValueOption) => {
+    if (
+      option.value.kind === FieldValueKind.FUNCTION ||
+      option.value.kind === FieldValueKind.EQUATION
+    ) {
+      return true;
+    }
+
+    return (
+      columns.has(option.value.meta.name) ||
+      option.value.meta.name === CUSTOM_EQUATION_VALUE
+    );
+  };
+}
+
+function getTimeseriesSortOptions(
+  organization: Organization,
+  widgetQuery: WidgetQuery,
+  tags?: TagCollection
+) {
+  const options: Record<string, SelectValue<FieldValue>> = {};
+  options[`field:${CUSTOM_EQUATION_VALUE}`] = {
+    label: 'Custom Equation',
+    value: {
+      kind: FieldValueKind.EQUATION,
+      meta: {name: CUSTOM_EQUATION_VALUE},
+    },
+  };
+
+  let equations = 0;
+  [...widgetQuery.aggregates, ...widgetQuery.columns]
+    .filter(field => !!field)
+    .forEach(field => {
+      let alias;
+      const label = stripEquationPrefix(field);
+      // Equations are referenced via a standard alias following this pattern
+      if (isEquation(field)) {
+        alias = `equation[${equations}]`;
+        equations += 1;
+        options[`equation:${alias}`] = {
+          label,
+          value: {
+            kind: FieldValueKind.EQUATION,
+            meta: {
+              name: alias ?? field,
+            },
+          },
+        };
+      }
+    });
+
+  const fieldOptions = getEventsTableFieldOptions(organization, tags);
+
+  return {...options, ...fieldOptions};
+}
+
 function getEventsTableFieldOptions(organization: Organization, tags?: TagCollection) {
   const measurements = getMeasurements();
 

+ 31 - 2
static/app/views/dashboardsV2/datasetConfig/issues.tsx

@@ -1,16 +1,24 @@
 import {Client} from 'sentry/api';
+import {t} from 'sentry/locale';
 import GroupStore from 'sentry/stores/groupStore';
 import {Group, Organization, PageFilters} from 'sentry/types';
 import {getIssueFieldRenderer} from 'sentry/utils/dashboards/issueFieldRenderers';
 import {getUtcDateString} from 'sentry/utils/dates';
 import {TableData, TableDataRow} from 'sentry/utils/discover/discoverQuery';
 import {queryToObj} from 'sentry/utils/stream';
-import {DISCOVER_EXCLUSION_FIELDS, IssueSortOptions} from 'sentry/views/issueList/utils';
+import {
+  DISCOVER_EXCLUSION_FIELDS,
+  getSortLabel,
+  IssueSortOptions,
+} from 'sentry/views/issueList/utils';
 
 import {DEFAULT_TABLE_LIMIT, DisplayType, WidgetQuery} from '../types';
 import {IssuesSearchBar} from '../widgetBuilder/buildSteps/filterResultsStep/issuesSearchBar';
 import {ISSUE_FIELD_TO_HEADER_MAP} from '../widgetBuilder/issueWidget/fields';
-import {generateIssueWidgetFieldOptions} from '../widgetBuilder/issueWidget/utils';
+import {
+  generateIssueWidgetFieldOptions,
+  ISSUE_WIDGET_SORT_OPTIONS,
+} from '../widgetBuilder/issueWidget/utils';
 
 import {DatasetConfig} from './base';
 
@@ -43,9 +51,11 @@ type EndpointParams = Partial<PageFilters['datetime']> & {
 
 export const IssuesConfig: DatasetConfig<never, Group[]> = {
   defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
+  disableSortOptions,
   getTableRequest,
   getCustomFieldRenderer: getIssueFieldRenderer,
   SearchBar: IssuesSearchBar,
+  getTableSortOptions,
   getTableFieldOptions: (_organization: Organization) =>
     generateIssueWidgetFieldOptions(),
   fieldHeaderMap: ISSUE_FIELD_TO_HEADER_MAP,
@@ -53,6 +63,25 @@ export const IssuesConfig: DatasetConfig<never, Group[]> = {
   transformTable: transformIssuesResponseToTable,
 };
 
+function disableSortOptions(_widgetQuery: WidgetQuery) {
+  return {
+    disableSort: false,
+    disableSortDirection: true,
+    disableSortReason: t('Issues dataset does not yet support descending order'),
+  };
+}
+
+function getTableSortOptions(organization: Organization, _widgetQuery: WidgetQuery) {
+  const sortOptions = [...ISSUE_WIDGET_SORT_OPTIONS];
+  if (organization.features.includes('issue-list-trend-sort')) {
+    sortOptions.push(IssueSortOptions.TREND);
+  }
+  return sortOptions.map(sortOption => ({
+    label: getSortLabel(sortOption),
+    value: sortOption,
+  }));
+}
+
 export function transformIssuesResponseToTable(
   data: Group[],
   widgetQuery: WidgetQuery,

+ 73 - 2
static/app/views/dashboardsV2/datasetConfig/releases.tsx

@@ -9,6 +9,7 @@ import {
   MetricsApiResponse,
   Organization,
   PageFilters,
+  SelectValue,
   SessionApiResponse,
   SessionField,
   SessionsMeta,
@@ -18,7 +19,7 @@ import {defined} from 'sentry/utils';
 import {TableData} from 'sentry/utils/discover/discoverQuery';
 import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
 import {FieldValueOption} from 'sentry/views/eventsV2/table/queryField';
-import {FieldValueKind} from 'sentry/views/eventsV2/table/types';
+import {FieldValue, FieldValueKind} from 'sentry/views/eventsV2/table/types';
 
 import {DisplayType, Widget, WidgetQuery} from '../types';
 import {getWidgetInterval} from '../utils';
@@ -31,6 +32,7 @@ import {
   generateReleaseWidgetFieldOptions,
   SESSIONS_FIELDS,
   SESSIONS_TAGS,
+  TAG_SORT_DENY_LIST,
 } from '../widgetBuilder/releaseWidget/fields';
 import {
   derivedMetricsToField,
@@ -61,6 +63,7 @@ export const ReleasesConfig: DatasetConfig<
   SessionApiResponse | MetricsApiResponse
 > = {
   defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
+  disableSortOptions,
   getTableRequest: (
     api: Client,
     query: WidgetQuery,
@@ -81,8 +84,10 @@ export const ReleasesConfig: DatasetConfig<
       cursor
     ),
   getSeriesRequest: getReleasesSeriesRequest,
+  getTableSortOptions,
+  getTimeseriesSortOptions,
   filterTableOptions: filterPrimaryReleaseTableOptions,
-  filterTableAggregateParams: filterAggregateParams,
+  filterAggregateParams,
   getCustomFieldRenderer: (field, meta) => getFieldRenderer(field, meta, false),
   SearchBar: ReleaseSearchBar,
   getTableFieldOptions: getReleasesTableFieldOptions,
@@ -90,6 +95,7 @@ export const ReleasesConfig: DatasetConfig<
     generateReleaseWidgetFieldOptions([] as SessionsMeta[], SESSIONS_TAGS),
   handleColumnFieldChangeOverride,
   handleOrderByReset: handleReleasesTableOrderByReset,
+  filterSeriesSortOptions,
   supportedDisplayTypes: [
     DisplayType.AREA,
     DisplayType.BAR,
@@ -102,6 +108,71 @@ export const ReleasesConfig: DatasetConfig<
   transformTable: transformSessionsResponseToTable,
 };
 
+function disableSortOptions(widgetQuery: WidgetQuery) {
+  const {columns} = widgetQuery;
+  if (columns.includes('session.status')) {
+    return {
+      disableSort: true,
+      disableSortDirection: true,
+      disableSortReason: t('Sorting currently not supported with session.status'),
+    };
+  }
+  return {
+    disableSort: false,
+    disableSortDirection: false,
+  };
+}
+
+function getTableSortOptions(_organization: Organization, widgetQuery: WidgetQuery) {
+  const {columns, aggregates} = widgetQuery;
+  const options: SelectValue<string>[] = [];
+  [...aggregates, ...columns]
+    .filter(field => !!field)
+    .filter(field => !DISABLED_SORT.includes(field))
+    .filter(field => !TAG_SORT_DENY_LIST.includes(field))
+    .forEach(field => {
+      options.push({label: field, value: field});
+    });
+
+  return options;
+}
+
+function getTimeseriesSortOptions(_organization: Organization, widgetQuery: WidgetQuery) {
+  const columnSet = new Set(widgetQuery.columns);
+  const releaseFieldOptions = generateReleaseWidgetFieldOptions(
+    Object.values(SESSIONS_FIELDS),
+    SESSIONS_TAGS
+  );
+  const options: Record<string, SelectValue<FieldValue>> = {};
+  Object.entries(releaseFieldOptions).forEach(([key, option]) => {
+    if (['count_healthy', 'count_errored'].includes(option.value.meta.name)) {
+      return;
+    }
+    if (option.value.kind === FieldValueKind.FIELD) {
+      // Only allow sorting by release tag
+      if (option.value.meta.name === 'release' && columnSet.has(option.value.meta.name)) {
+        options[key] = option;
+      }
+      return;
+    }
+    options[key] = option;
+  });
+  return options;
+}
+
+function filterSeriesSortOptions(columns: Set<string>) {
+  return (option: FieldValueOption) => {
+    if (['count_healthy', 'count_errored'].includes(option.value.meta.name)) {
+      return false;
+    }
+    if (option.value.kind === FieldValueKind.FIELD) {
+      // Only allow sorting by release tag
+      return columns.has(option.value.meta.name) && option.value.meta.name === 'release';
+    }
+    return filterPrimaryReleaseTableOptions(option);
+  };
+}
+
 function getReleasesSeriesRequest(
   api: Client,
   widget: Widget,

+ 1 - 1
static/app/views/dashboardsV2/widgetBuilder/buildSteps/columnsStep/index.tsx

@@ -79,7 +79,7 @@ export function ColumnsStep({
         fields={explodedFields}
         errors={queryErrors}
         fieldOptions={datasetConfig.getTableFieldOptions(organization, tags)}
-        filterAggregateParameters={datasetConfig.filterTableAggregateParams}
+        filterAggregateParameters={datasetConfig.filterAggregateParams}
         filterPrimaryOptions={datasetConfig.filterTableOptions}
         onChange={handleColumnFieldChange}
       />

+ 52 - 150
static/app/views/dashboardsV2/widgetBuilder/buildSteps/sortByStep/index.tsx

@@ -2,22 +2,18 @@ import {useEffect} from 'react';
 import styled from '@emotion/styled';
 import trimStart from 'lodash/trimStart';
 
-import {generateOrderOptions} from 'sentry/components/dashboards/widgetQueriesForm';
 import Field from 'sentry/components/forms/field';
 import SelectControl from 'sentry/components/forms/selectControl';
 import {t, tn} from 'sentry/locale';
 import space from 'sentry/styles/space';
 import {Organization, SelectValue, TagCollection} from 'sentry/types';
+import {getDatasetConfig} from 'sentry/views/dashboardsV2/datasetConfig/base';
 import {DisplayType, WidgetQuery, WidgetType} from 'sentry/views/dashboardsV2/types';
-import {generateIssueWidgetOrderOptions} from 'sentry/views/dashboardsV2/widgetBuilder/issueWidget/utils';
 import {
   DataSet,
-  filterPrimaryOptions as filterReleaseSortOptions,
   getResultsLimit,
   SortDirection,
 } from 'sentry/views/dashboardsV2/widgetBuilder/utils';
-import {FieldValueKind} from 'sentry/views/eventsV2/table/types';
-import {IssueSortOptions} from 'sentry/views/issueList/utils';
 
 import {BuildStep} from '../buildStep';
 
@@ -43,30 +39,21 @@ export function SortByStep({
   displayType,
   onSortByChange,
   queries,
-  dataSet,
-  widgetBuilderNewDesign,
   widgetType,
-  organization,
   error,
   limit,
   onLimitChange,
   tags,
 }: Props) {
-  const fields = queries[0].columns;
+  const datasetConfig = getDatasetConfig(widgetType);
 
-  let disabledSort = false;
-  let disabledSortDirection = false;
-  let disabledReason: string | undefined = undefined;
+  let disableSort = false;
+  let disableSortDirection = false;
+  let disableSortReason: string | undefined = undefined;
 
-  if (widgetType === WidgetType.RELEASE && fields.includes('session.status')) {
-    disabledSort = true;
-    disabledSortDirection = true;
-    disabledReason = t('Sorting currently not supported with session.status');
-  }
-
-  if (widgetType === WidgetType.ISSUE) {
-    disabledSortDirection = true;
-    disabledReason = t('Issues dataset does not yet support descending order');
+  if (datasetConfig.disableSortOptions) {
+    ({disableSort, disableSortDirection, disableSortReason} =
+      datasetConfig.disableSortOptions(queries[0]));
   }
 
   const orderBy = queries[0].orderby;
@@ -88,140 +75,55 @@ export function SortByStep({
     }
   }, [limit, maxLimit, onLimitChange]);
 
-  const columnSet = new Set(queries[0].columns);
-  const filterDiscoverOptions = option => {
-    if (
-      option.value.kind === FieldValueKind.FUNCTION ||
-      option.value.kind === FieldValueKind.EQUATION
-    ) {
-      return true;
-    }
-
-    return (
-      columnSet.has(option.value.meta.name) ||
-      option.value.meta.name === CUSTOM_EQUATION_VALUE
-    );
-  };
-
-  const filterReleaseOptions = option => {
-    if (['count_healthy', 'count_errored'].includes(option.value.meta.name)) {
-      return false;
-    }
-    if (option.value.kind === FieldValueKind.FIELD) {
-      // Only allow sorting by release tag
-      return (
-        columnSet.has(option.value.meta.name) && option.value.meta.name === 'release'
-      );
-    }
-    return filterReleaseSortOptions({
-      option,
-      widgetType,
-      displayType: DisplayType.TABLE,
-    });
-  };
-
-  if (widgetBuilderNewDesign) {
-    return (
-      <BuildStep
-        title={
-          displayType === DisplayType.TABLE
-            ? t('Sort by a column')
-            : t('Sort by a y-axis')
-        }
-        description={
-          displayType === DisplayType.TABLE
-            ? t("Choose one of the columns you've created to sort by.")
-            : t("Choose one of the y-axis you've created to sort by.")
-        }
-      >
-        <Field inline={false} error={error} flexibleControlStateSize stacked>
-          {[DisplayType.AREA, DisplayType.BAR, DisplayType.LINE].includes(displayType) &&
-            limit && (
-              <ResultsLimitSelector
-                name="resultsLimit"
-                menuPlacement="auto"
-                options={[...Array(maxLimit).keys()].map(resultLimit => {
-                  const value = resultLimit + 1;
-                  return {
-                    label: tn('Limit to %s result', 'Limit to %s results', value),
-                    value,
-                  };
-                })}
-                value={limit}
-                onChange={(option: SelectValue<number>) => {
-                  onLimitChange(option.value);
-                }}
-              />
-            )}
-          <SortBySelectors
-            displayType={displayType}
-            widgetType={widgetType}
-            hasGroupBy={isTimeseriesChart && !!queries[0].columns.length}
-            disabledReason={disabledReason}
-            disabledSort={disabledSort}
-            disabledSortDirection={disabledSortDirection}
-            sortByOptions={
-              dataSet === DataSet.ISSUES
-                ? generateIssueWidgetOrderOptions(
-                    organization.features.includes('issue-list-trend-sort')
-                  )
-                : generateOrderOptions({
-                    widgetType,
-                    widgetBuilderNewDesign: true,
-                    columns: queries[0].columns,
-                    aggregates: queries[0].aggregates,
-                  })
-            }
-            values={{
-              sortDirection:
-                orderBy[0] === '-'
-                  ? SortDirection.HIGH_TO_LOW
-                  : SortDirection.LOW_TO_HIGH,
-              sortBy: strippedOrderBy,
-            }}
-            onChange={({sortDirection, sortBy}) => {
-              const newOrderBy =
-                sortDirection === SortDirection.HIGH_TO_LOW ? `-${sortBy}` : sortBy;
-              onSortByChange(newOrderBy);
-            }}
-            tags={tags}
-            filterPrimaryOptions={
-              dataSet === DataSet.RELEASES ? filterReleaseOptions : filterDiscoverOptions
-            }
-          />
-        </Field>
-      </BuildStep>
-    );
-  }
-
   return (
     <BuildStep
-      title={t('Sort by a column')}
-      description={t("Choose one of the columns you've created to sort by.")}
+      title={
+        displayType === DisplayType.TABLE ? t('Sort by a column') : t('Sort by a y-axis')
+      }
+      description={
+        displayType === DisplayType.TABLE
+          ? t("Choose one of the columns you've created to sort by.")
+          : t("Choose one of the y-axis you've created to sort by.")
+      }
     >
       <Field inline={false} error={error} flexibleControlStateSize stacked>
-        <SelectControl
-          menuPlacement="auto"
-          value={
-            dataSet === DataSet.EVENTS
-              ? queries[0].orderby
-              : queries[0].orderby || IssueSortOptions.DATE
-          }
-          name="orderby"
-          options={
-            dataSet === DataSet.EVENTS
-              ? generateOrderOptions({
-                  widgetType,
-                  columns: queries[0].columns,
-                  aggregates: queries[0].aggregates,
-                })
-              : generateIssueWidgetOrderOptions(
-                  organization.features.includes('issue-list-trend-sort')
-                )
-          }
-          onChange={(option: SelectValue<string>) => {
-            onSortByChange(option.value);
+        {[DisplayType.AREA, DisplayType.BAR, DisplayType.LINE].includes(displayType) &&
+          limit && (
+            <ResultsLimitSelector
+              name="resultsLimit"
+              menuPlacement="auto"
+              options={[...Array(maxLimit).keys()].map(resultLimit => {
+                const value = resultLimit + 1;
+                return {
+                  label: tn('Limit to %s result', 'Limit to %s results', value),
+                  value,
+                };
+              })}
+              value={limit}
+              onChange={(option: SelectValue<number>) => {
+                onLimitChange(option.value);
+              }}
+            />
+          )}
+        <SortBySelectors
+          displayType={displayType}
+          widgetType={widgetType}
+          hasGroupBy={isTimeseriesChart && !!queries[0].columns.length}
+          disableSortReason={disableSortReason}
+          disableSort={disableSort}
+          disableSortDirection={disableSortDirection}
+          widgetQuery={queries[0]}
+          values={{
+            sortDirection:
+              orderBy[0] === '-' ? SortDirection.HIGH_TO_LOW : SortDirection.LOW_TO_HIGH,
+            sortBy: strippedOrderBy,
+          }}
+          onChange={({sortDirection, sortBy}) => {
+            const newOrderBy =
+              sortDirection === SortDirection.HIGH_TO_LOW ? `-${sortBy}` : sortBy;
+            onSortByChange(newOrderBy);
           }}
+          tags={tags}
         />
       </Field>
     </BuildStep>

+ 72 - 143
static/app/views/dashboardsV2/widgetBuilder/buildSteps/sortByStep/sortBySelectors.tsx

@@ -16,23 +16,15 @@ import {
   isEquation,
   isEquationAlias,
 } from 'sentry/utils/discover/fields';
-import Measurements from 'sentry/utils/measurements/measurements';
 import useOrganization from 'sentry/utils/useOrganization';
-import {DisplayType, WidgetType} from 'sentry/views/dashboardsV2/types';
+import {getDatasetConfig} from 'sentry/views/dashboardsV2/datasetConfig/base';
+import {DisplayType, WidgetQuery, WidgetType} from 'sentry/views/dashboardsV2/types';
 import {
-  getAmendedFieldOptions,
   SortDirection,
   sortDirections,
 } from 'sentry/views/dashboardsV2/widgetBuilder/utils';
 import ArithmeticInput from 'sentry/views/eventsV2/table/arithmeticInput';
 import {QueryField} from 'sentry/views/eventsV2/table/queryField';
-import {FieldValueKind} from 'sentry/views/eventsV2/table/types';
-
-import {
-  generateReleaseWidgetFieldOptions,
-  SESSIONS_FIELDS,
-  SESSIONS_TAGS,
-} from '../../releaseWidget/fields';
 
 import {CUSTOM_EQUATION_VALUE} from '.';
 
@@ -43,32 +35,30 @@ interface Values {
 
 interface Props {
   displayType: DisplayType;
-  filterPrimaryOptions: React.ComponentProps<typeof QueryField>['filterPrimaryOptions'];
   onChange: (values: Values) => void;
-  sortByOptions: SelectValue<string>[];
   tags: TagCollection;
   values: Values;
+  widgetQuery: WidgetQuery;
   widgetType: WidgetType;
-  disabledReason?: string;
-  disabledSort?: boolean;
-  disabledSortDirection?: boolean;
+  disableSort?: boolean;
+  disableSortDirection?: boolean;
+  disableSortReason?: string;
   hasGroupBy?: boolean;
 }
 
 export function SortBySelectors({
   values,
-  sortByOptions,
   widgetType,
   onChange,
-  disabledReason,
-  disabledSort,
-  disabledSortDirection,
-  hasGroupBy,
-  tags,
-  filterPrimaryOptions,
+  disableSortReason,
+  disableSort,
+  disableSortDirection,
+  widgetQuery,
   displayType,
 }: Props) {
+  const datasetConfig = getDatasetConfig(widgetType);
   const organization = useOrganization();
+  const columnSet = new Set(widgetQuery.columns);
   const [showCustomEquation, setShowCustomEquation] = useState(false);
   const [customEquation, setCustomEquation] = useState<Values>({
     sortBy: `${EQUATION_PREFIX}`,
@@ -85,52 +75,72 @@ export function SortBySelectors({
     setShowCustomEquation(isSortingByEquation);
   }, [values.sortBy, values.sortDirection]);
 
-  function generateEquationOptions(options: Props['sortByOptions']) {
-    return options.reduce((acc, option) => {
-      if (option.value.startsWith('equation')) {
-        acc[`equation:${option.value}`] = {
-          label: option.label,
-          value: {
-            kind: FieldValueKind.EQUATION,
-            meta: {
-              name: option.value,
-            },
-          },
-        };
-      }
-      return acc;
-    }, {});
-  }
-
-  function getSortByField() {
-    if (
-      widgetType === WidgetType.DISCOVER &&
-      ![DisplayType.TABLE, DisplayType.TOP_N].includes(displayType)
-    ) {
-      return (
-        <Measurements>
-          {({measurements}) => (
+  return (
+    <Tooltip title={disableSortReason} disabled={!(disableSortDirection && disableSort)}>
+      <Wrapper>
+        <Tooltip
+          title={disableSortReason}
+          disabled={!disableSortDirection || (disableSortDirection && disableSort)}
+        >
+          <SelectControl
+            name="sortDirection"
+            aria-label="Sort direction"
+            menuPlacement="auto"
+            disabled={disableSortDirection}
+            options={Object.keys(sortDirections).map(value => ({
+              label: sortDirections[value],
+              value,
+            }))}
+            value={values.sortDirection}
+            onChange={(option: SelectValue<SortDirection>) => {
+              onChange({
+                sortBy: values.sortBy,
+                sortDirection: option.value,
+              });
+            }}
+          />
+        </Tooltip>
+        <Tooltip
+          title={disableSortReason}
+          disabled={!disableSort || (disableSortDirection && disableSort)}
+        >
+          {displayType === DisplayType.TABLE ? (
+            <SelectControl
+              name="sortBy"
+              aria-label="Sort by"
+              menuPlacement="auto"
+              disabled={disableSort}
+              placeholder={`${t('Select a column')}\u{2026}`}
+              value={values.sortBy}
+              options={uniqBy(
+                datasetConfig.getTableSortOptions!(organization, widgetQuery),
+                ({value}) => value
+              )}
+              onChange={(option: SelectValue<string>) => {
+                onChange({
+                  sortBy: option.value,
+                  sortDirection: values.sortDirection,
+                });
+              }}
+            />
+          ) : (
             <QueryField
+              disabled={disableSort}
               fieldValue={
                 showCustomEquation
                   ? explodeField({field: CUSTOM_EQUATION_VALUE})
                   : explodeField({field: values.sortBy})
               }
-              fieldOptions={{
-                ...(hasGroupBy
-                  ? {
-                      [`field:${CUSTOM_EQUATION_VALUE}`]: {
-                        label: 'Custom Equation',
-                        value: {
-                          kind: FieldValueKind.EQUATION,
-                          meta: {name: CUSTOM_EQUATION_VALUE},
-                        },
-                      },
-                    }
-                  : {}),
-                ...generateEquationOptions(sortByOptions),
-                ...getAmendedFieldOptions({measurements, organization, tags}),
-              }}
+              fieldOptions={datasetConfig.getTimeseriesSortOptions!(
+                organization,
+                widgetQuery
+              )}
+              filterPrimaryOptions={
+                datasetConfig.filterSeriesSortOptions
+                  ? datasetConfig.filterSeriesSortOptions(columnSet)
+                  : undefined
+              }
+              filterAggregateParameters={datasetConfig.filterAggregateParams}
               onChange={value => {
                 if (value.alias && isEquationAlias(value.alias)) {
                   onChange({
@@ -153,90 +163,9 @@ export function SortBySelectors({
                   sortDirection: values.sortDirection,
                 });
               }}
-              filterPrimaryOptions={filterPrimaryOptions}
             />
           )}
-        </Measurements>
-      );
-    }
-    if (
-      widgetType === WidgetType.RELEASE &&
-      ![DisplayType.TABLE, DisplayType.TOP_N].includes(displayType)
-    ) {
-      return (
-        <Tooltip
-          title={disabledReason}
-          disabled={!disabledSort || (disabledSortDirection && disabledSort)}
-        >
-          <QueryField
-            disabled={disabledSort}
-            fieldValue={explodeField({field: values.sortBy})}
-            fieldOptions={generateReleaseWidgetFieldOptions(
-              Object.values(SESSIONS_FIELDS),
-              SESSIONS_TAGS
-            )}
-            onChange={value => {
-              const parsedValue = generateFieldAsString(value);
-              onChange({
-                sortBy: parsedValue,
-                sortDirection: values.sortDirection,
-              });
-            }}
-            filterPrimaryOptions={filterPrimaryOptions}
-          />
-        </Tooltip>
-      );
-    }
-    return (
-      <Tooltip
-        title={disabledReason}
-        disabled={!disabledSort || (disabledSortDirection && disabledSort)}
-      >
-        <SelectControl
-          name="sortBy"
-          aria-label="Sort by"
-          menuPlacement="auto"
-          disabled={disabledSort}
-          placeholder={`${t('Select a column')}\u{2026}`}
-          value={values.sortBy}
-          options={uniqBy(sortByOptions, ({value}) => value)}
-          onChange={(option: SelectValue<string>) => {
-            onChange({
-              sortBy: option.value,
-              sortDirection: values.sortDirection,
-            });
-          }}
-        />
-      </Tooltip>
-    );
-  }
-
-  return (
-    <Tooltip title={disabledReason} disabled={!(disabledSortDirection && disabledSort)}>
-      <Wrapper>
-        <Tooltip
-          title={disabledReason}
-          disabled={!disabledSortDirection || (disabledSortDirection && disabledSort)}
-        >
-          <SelectControl
-            name="sortDirection"
-            aria-label="Sort direction"
-            menuPlacement="auto"
-            disabled={disabledSortDirection}
-            options={Object.keys(sortDirections).map(value => ({
-              label: sortDirections[value],
-              value,
-            }))}
-            value={values.sortDirection}
-            onChange={(option: SelectValue<SortDirection>) => {
-              onChange({
-                sortBy: values.sortBy,
-                sortDirection: option.value,
-              });
-            }}
-          />
         </Tooltip>
-        {getSortByField()}
         {showCustomEquation && (
           <ArithmeticInputWrapper>
             <ArithmeticInput

+ 1 - 1
static/app/views/dashboardsV2/widgetBuilder/issueWidget/utils.tsx

@@ -26,7 +26,7 @@ export function generateIssueWidgetFieldOptions(
   return fieldOptions;
 }
 
-const ISSUE_WIDGET_SORT_OPTIONS = [
+export const ISSUE_WIDGET_SORT_OPTIONS = [
   IssueSortOptions.DATE,
   IssueSortOptions.NEW,
   IssueSortOptions.FREQ,