Browse Source

ref(dashboards): Move y-axis into config (#36191)

Move y-axis selection into config
Shruthi 2 years ago
parent
commit
a74e14fca2

+ 62 - 0
static/app/utils/discover/fields.tsx

@@ -1335,6 +1335,68 @@ export function aggregateFunctionOutputType(
   return null;
 }
 
+export function errorsAndTransactionsAggregateFunctionOutputType(
+  funcName: string,
+  firstArg: string | undefined
+): AggregationOutputType | null {
+  const aggregate = AGGREGATIONS[ALIASES[funcName] || funcName];
+
+  // Attempt to use the function's outputType.
+  if (aggregate?.outputType) {
+    return aggregate.outputType;
+  }
+
+  // If the first argument is undefined and it is not required,
+  // then we attempt to get the default value.
+  if (!firstArg && aggregate?.parameters?.[0]) {
+    if (aggregate.parameters[0].required === false) {
+      firstArg = aggregate.parameters[0].defaultValue;
+    }
+  }
+
+  // If the function is an inherit type it will have a field as
+  // the first parameter and we can use that to get the type.
+  if (firstArg && FIELDS.hasOwnProperty(firstArg)) {
+    return FIELDS[firstArg];
+  }
+
+  if (firstArg && isMeasurement(firstArg)) {
+    return measurementType(firstArg);
+  }
+
+  if (firstArg && isSpanOperationBreakdownField(firstArg)) {
+    return 'duration';
+  }
+
+  return null;
+}
+
+export function sessionsAggregateFunctionOutputType(
+  funcName: string,
+  firstArg: string | undefined
+): AggregationOutputType | null {
+  const aggregate = SESSIONS_OPERATIONS[funcName];
+
+  // Attempt to use the function's outputType.
+  if (aggregate?.outputType) {
+    return aggregate.outputType;
+  }
+
+  // If the first argument is undefined and it is not required,
+  // then we attempt to get the default value.
+  if (!firstArg && aggregate?.parameters?.[0]) {
+    if (aggregate.parameters[0].required === false) {
+      firstArg = aggregate.parameters[0].defaultValue;
+    }
+  }
+
+  if (firstArg && SESSIONS_FIELDS.hasOwnProperty(firstArg)) {
+    return SESSIONS_FIELDS[firstArg].type as AggregationOutputType;
+  }
+
+  return null;
+}
+
 /**
  * Get the multi-series chart type for an aggregate function.
  */

+ 14 - 1
static/app/views/dashboardsV2/datasetConfig/base.tsx

@@ -7,7 +7,7 @@ import {Series} from 'sentry/types/echarts';
 import {TableData} from 'sentry/utils/discover/discoverQuery';
 import {MetaType} from 'sentry/utils/discover/eventView';
 import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
-import {isEquation} from 'sentry/utils/discover/fields';
+import {isEquation, QueryFieldValue} from 'sentry/utils/discover/fields';
 import {FieldValueOption} from 'sentry/views/eventsV2/table/queryField';
 import {FieldValue} from 'sentry/views/eventsV2/table/types';
 
@@ -37,6 +37,7 @@ export interface DatasetConfig<SeriesResponse, TableResponse> {
    * Widget Builder.
    */
   defaultWidgetQuery: WidgetQuery;
+  enableEquations: boolean;
   /**
    * Field options to display in the Column selectors for
    * Table display type.
@@ -91,6 +92,18 @@ export interface DatasetConfig<SeriesResponse, TableResponse> {
    * columns on the Widget Builder.
    */
   filterTableOptions?: (option: FieldValueOption) => boolean;
+  /**
+   * Filter the options available to the parameters list
+   * of an aggregate function in QueryField component on the
+   * Widget Builder.
+   */
+  filterYAxisAggregateParams?: (
+    fieldValue: QueryFieldValue,
+    displayType: DisplayType
+  ) => (option: FieldValueOption) => boolean;
+  filterYAxisOptions?: (
+    displayType: DisplayType
+  ) => (option: FieldValueOption) => boolean;
   /**
    * Used to select custom renderers for field types.
    */

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

@@ -22,8 +22,11 @@ import {
   RenderFunctionBaggage,
 } from 'sentry/utils/discover/fieldRenderers';
 import {
+  errorsAndTransactionsAggregateFunctionOutputType,
   isEquation,
   isEquationAlias,
+  isLegalYAxisType,
+  QueryFieldValue,
   SPAN_OP_BREAKDOWN_FIELDS,
   stripEquationPrefix,
 } from 'sentry/utils/discover/fields';
@@ -77,9 +80,12 @@ export const ErrorsAndTransactionsConfig: DatasetConfig<
   TableData | EventsTableData
 > = {
   defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
+  enableEquations: true,
   getCustomFieldRenderer: getCustomEventsFieldRenderer,
   SearchBar: EventsSearchBar,
   filterSeriesSortOptions,
+  filterYAxisAggregateParams,
+  filterYAxisOptions,
   getTableFieldOptions: getEventsTableFieldOptions,
   getTimeseriesSortOptions,
   getTableSortOptions,
@@ -254,6 +260,64 @@ function transformEventsResponseToTable(
   return tableData as TableData;
 }
 
+function filterYAxisAggregateParams(
+  fieldValue: QueryFieldValue,
+  displayType: DisplayType
+) {
+  return (option: FieldValueOption) => {
+    // Only validate function parameters for timeseries widgets and
+    // world map widgets.
+    if (displayType === DisplayType.BIG_NUMBER) {
+      return true;
+    }
+
+    if (fieldValue.kind !== FieldValueKind.FUNCTION) {
+      return true;
+    }
+
+    const functionName = fieldValue.function[0];
+    const primaryOutput = errorsAndTransactionsAggregateFunctionOutputType(
+      functionName as string,
+      option.value.meta.name
+    );
+    if (primaryOutput) {
+      return isLegalYAxisType(primaryOutput);
+    }
+
+    if (
+      option.value.kind === FieldValueKind.FUNCTION ||
+      option.value.kind === FieldValueKind.EQUATION
+    ) {
+      // Functions and equations are not legal options as an aggregate/function parameter.
+      return false;
+    }
+
+    return isLegalYAxisType(option.value.meta.dataType);
+  };
+}
+
+function filterYAxisOptions(displayType: DisplayType) {
+  return (option: FieldValueOption) => {
+    // Only validate function names for timeseries widgets and
+    // world map widgets.
+    if (
+      !(displayType === DisplayType.BIG_NUMBER) &&
+      option.value.kind === FieldValueKind.FUNCTION
+    ) {
+      const primaryOutput = errorsAndTransactionsAggregateFunctionOutputType(
+        option.value.meta.name,
+        undefined
+      );
+      if (primaryOutput) {
+        // If a function returns a specific type, then validate it.
+        return isLegalYAxisType(primaryOutput);
+      }
+    }
+
+    return option.value.kind === FieldValueKind.FUNCTION;
+  };
+}
+
 function transformEventsResponseToSeries(
   data: EventsStats | MultiSeriesEventsStats,
   widgetQuery: WidgetQuery,

+ 1 - 0
static/app/views/dashboardsV2/datasetConfig/issues.tsx

@@ -51,6 +51,7 @@ type EndpointParams = Partial<PageFilters['datetime']> & {
 
 export const IssuesConfig: DatasetConfig<never, Group[]> = {
   defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
+  enableEquations: false,
   disableSortOptions,
   getTableRequest,
   getCustomFieldRenderer: getIssueFieldRenderer,

+ 13 - 0
static/app/views/dashboardsV2/datasetConfig/releases.tsx

@@ -18,6 +18,7 @@ import {Series} from 'sentry/types/echarts';
 import {defined} from 'sentry/utils';
 import {TableData} from 'sentry/utils/discover/discoverQuery';
 import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
+import {QueryFieldValue} from 'sentry/utils/discover/fields';
 import {FieldValueOption} from 'sentry/views/eventsV2/table/queryField';
 import {FieldValue, FieldValueKind} from 'sentry/views/eventsV2/table/types';
 
@@ -63,6 +64,7 @@ export const ReleasesConfig: DatasetConfig<
   SessionApiResponse | MetricsApiResponse
 > = {
   defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
+  enableEquations: false,
   disableSortOptions,
   getTableRequest: (
     api: Client,
@@ -88,6 +90,9 @@ export const ReleasesConfig: DatasetConfig<
   getTimeseriesSortOptions,
   filterTableOptions: filterPrimaryReleaseTableOptions,
   filterAggregateParams,
+  filterYAxisAggregateParams: (_fieldValue: QueryFieldValue, _displayType: DisplayType) =>
+    filterAggregateParams,
+  filterYAxisOptions,
   getCustomFieldRenderer: (field, meta) => getFieldRenderer(field, meta, false),
   SearchBar: ReleaseSearchBar,
   getTableFieldOptions: getReleasesTableFieldOptions,
@@ -221,6 +226,14 @@ function filterAggregateParams(option: FieldValueOption) {
   return option.value.kind === FieldValueKind.METRICS;
 }
 
+function filterYAxisOptions(_displayType: DisplayType) {
+  return (option: FieldValueOption) => {
+    return [FieldValueKind.FUNCTION, FieldValueKind.NUMERIC_METRICS].includes(
+      option.value.kind
+    );
+  };
+}
+
 function handleReleasesTableOrderByReset(widgetQuery: WidgetQuery, newFields: string[]) {
   const disableSortBy = widgetQuery.columns.includes('session.status');
   if (disableSortBy) {

+ 9 - 27
static/app/views/dashboardsV2/widgetBuilder/buildSteps/yAxisStep/index.tsx

@@ -1,13 +1,11 @@
 import {t} from 'sentry/locale';
 import {Organization, TagCollection} from 'sentry/types';
 import {QueryFieldValue} from 'sentry/utils/discover/fields';
-import Measurements from 'sentry/utils/measurements/measurements';
 import {DisplayType, WidgetType} from 'sentry/views/dashboardsV2/types';
 
-import {DataSet, getAmendedFieldOptions} from '../../utils';
+import {DataSet} from '../../utils';
 import {BuildStep} from '../buildStep';
 
-import {ReleaseYAxisSelector} from './releaseYAxisSelector';
 import {YAxisSelector} from './yAxisSelector';
 
 interface Props {
@@ -23,11 +21,9 @@ interface Props {
 
 export function YAxisStep({
   displayType,
-  dataSet,
   queryErrors,
   aggregates,
   onYAxisChange,
-  organization,
   tags,
   widgetType,
 }: Props) {
@@ -46,28 +42,14 @@ export function YAxisStep({
           : t("This is the data you'd be visualizing in the display.")
       }
     >
-      {dataSet === DataSet.RELEASES ? (
-        <ReleaseYAxisSelector
-          widgetType={widgetType}
-          displayType={displayType}
-          aggregates={aggregates}
-          onChange={onYAxisChange}
-          errors={queryErrors}
-        />
-      ) : (
-        <Measurements>
-          {({measurements}) => (
-            <YAxisSelector
-              widgetType={widgetType}
-              displayType={displayType}
-              aggregates={aggregates}
-              fieldOptions={getAmendedFieldOptions({measurements, organization, tags})}
-              onChange={onYAxisChange}
-              errors={queryErrors}
-            />
-          )}
-        </Measurements>
-      )}
+      <YAxisSelector
+        widgetType={widgetType}
+        displayType={displayType}
+        aggregates={aggregates}
+        onChange={onYAxisChange}
+        tags={tags}
+        errors={queryErrors}
+      />
     </BuildStep>
   );
 }

+ 0 - 39
static/app/views/dashboardsV2/widgetBuilder/buildSteps/yAxisStep/releaseYAxisSelector.tsx

@@ -1,39 +0,0 @@
-import {QueryFieldValue} from 'sentry/utils/discover/fields';
-import {DisplayType, WidgetType} from 'sentry/views/dashboardsV2/types';
-import {
-  generateReleaseWidgetFieldOptions,
-  SESSIONS_FIELDS,
-  SESSIONS_TAGS,
-} from 'sentry/views/dashboardsV2/widgetBuilder/releaseWidget/fields';
-
-import {YAxisSelector} from './yAxisSelector';
-
-interface Props {
-  aggregates: QueryFieldValue[];
-  displayType: DisplayType;
-  onChange: (newFields: QueryFieldValue[]) => void;
-  widgetType: WidgetType;
-  errors?: Record<string, any>[];
-}
-
-export function ReleaseYAxisSelector({
-  aggregates,
-  displayType,
-  widgetType,
-  onChange,
-  errors,
-}: Props) {
-  return (
-    <YAxisSelector
-      widgetType={widgetType}
-      displayType={displayType}
-      aggregates={aggregates}
-      onChange={onChange}
-      errors={errors}
-      fieldOptions={generateReleaseWidgetFieldOptions(
-        Object.values(SESSIONS_FIELDS),
-        SESSIONS_TAGS
-      )}
-    />
-  );
-}

+ 15 - 94
static/app/views/dashboardsV2/widgetBuilder/buildSteps/yAxisStep/yAxisSelector/index.tsx

@@ -4,20 +4,13 @@ import ButtonBar from 'sentry/components/buttonBar';
 import Field from 'sentry/components/forms/field';
 import {t} from 'sentry/locale';
 import space from 'sentry/styles/space';
-import {
-  aggregateFunctionOutputType,
-  isLegalYAxisType,
-  QueryFieldValue,
-} from 'sentry/utils/discover/fields';
+import {TagCollection} from 'sentry/types';
+import {QueryFieldValue} from 'sentry/utils/discover/fields';
 import useOrganization from 'sentry/utils/useOrganization';
-import {DisplayType, Widget, WidgetType} from 'sentry/views/dashboardsV2/types';
-import {
-  doNotValidateYAxis,
-  filterPrimaryOptions,
-} from 'sentry/views/dashboardsV2/widgetBuilder/utils';
-import {FieldValueOption, QueryField} from 'sentry/views/eventsV2/table/queryField';
+import {getDatasetConfig} from 'sentry/views/dashboardsV2/datasetConfig/base';
+import {DisplayType, Widget} from 'sentry/views/dashboardsV2/types';
+import {QueryField} from 'sentry/views/eventsV2/table/queryField';
 import {FieldValueKind} from 'sentry/views/eventsV2/table/types';
-import {generateFieldOptions} from 'sentry/views/eventsV2/utils';
 
 import {AddButton} from './addButton';
 import {DeleteButton} from './deleteButton';
@@ -25,11 +18,11 @@ import {DeleteButton} from './deleteButton';
 interface Props {
   aggregates: QueryFieldValue[];
   displayType: DisplayType;
-  fieldOptions: ReturnType<typeof generateFieldOptions>;
   /**
    * Fired when aggregates are added/removed/modified/reordered.
    */
   onChange: (aggregates: QueryFieldValue[]) => void;
+  tags: TagCollection;
   widgetType: Widget['widgetType'];
   errors?: Record<string, any>;
   noFieldsMessage?: string;
@@ -39,13 +32,13 @@ export function YAxisSelector({
   displayType,
   widgetType,
   aggregates,
-  fieldOptions,
+  tags,
   onChange,
   errors,
   noFieldsMessage,
 }: Props) {
   const organization = useOrganization();
-  const isReleaseWidget = widgetType === WidgetType.RELEASE;
+  const datasetConfig = getDatasetConfig(widgetType);
 
   function handleAddOverlay(event: React.MouseEvent) {
     event.preventDefault();
@@ -81,76 +74,7 @@ export function YAxisSelector({
     onChange(newAggregates);
   }
 
-  function handleTopNChangeField(value: QueryFieldValue) {
-    // Top N widgets can only ever change a single y-axis
-    onChange([value]);
-  }
-
-  function filterAggregateParameters(fieldValue: QueryFieldValue) {
-    return (option: FieldValueOption) => {
-      if (isReleaseWidget) {
-        if (option.value.kind === FieldValueKind.METRICS) {
-          return true;
-        }
-        return false;
-      }
-
-      // Only validate function parameters for timeseries widgets and
-      // world map widgets.
-      if (doNotValidateYAxis(displayType)) {
-        return true;
-      }
-
-      if (fieldValue.kind !== FieldValueKind.FUNCTION) {
-        return true;
-      }
-
-      const functionName = fieldValue.function[0];
-      const primaryOutput = aggregateFunctionOutputType(
-        functionName as string,
-        option.value.meta.name
-      );
-      if (primaryOutput) {
-        return isLegalYAxisType(primaryOutput);
-      }
-
-      if (
-        option.value.kind === FieldValueKind.FUNCTION ||
-        option.value.kind === FieldValueKind.EQUATION
-      ) {
-        // Functions and equations are not legal options as an aggregate/function parameter.
-        return false;
-      }
-
-      return isLegalYAxisType(option.value.meta.dataType);
-    };
-  }
-
   const fieldError = errors?.find(error => error?.aggregates)?.aggregates;
-
-  if (displayType === DisplayType.TOP_N) {
-    const fieldValue = aggregates[aggregates.length - 1];
-    return (
-      <Field inline={false} flexibleControlStateSize error={fieldError} stacked>
-        <QueryFieldWrapper>
-          <QueryField
-            fieldValue={fieldValue}
-            fieldOptions={generateFieldOptions({organization})}
-            onChange={handleTopNChangeField}
-            filterPrimaryOptions={option =>
-              filterPrimaryOptions({
-                option,
-                widgetType,
-                displayType,
-              })
-            }
-            filterAggregateParameters={filterAggregateParameters(fieldValue)}
-          />
-        </QueryFieldWrapper>
-      </Field>
-    );
-  }
-
   const canDelete = aggregates.length > 1;
 
   const hideAddYAxisButtons =
@@ -165,16 +89,13 @@ export function YAxisSelector({
         <QueryFieldWrapper key={`${fieldValue}:${i}`}>
           <QueryField
             fieldValue={fieldValue}
-            fieldOptions={fieldOptions}
+            fieldOptions={datasetConfig.getTableFieldOptions(organization, tags)}
             onChange={value => handleChangeQueryField(value, i)}
-            filterPrimaryOptions={option =>
-              filterPrimaryOptions({
-                option,
-                widgetType,
-                displayType,
-              })
-            }
-            filterAggregateParameters={filterAggregateParameters(fieldValue)}
+            filterPrimaryOptions={datasetConfig.filterYAxisOptions?.(displayType)}
+            filterAggregateParameters={datasetConfig.filterYAxisAggregateParams?.(
+              fieldValue,
+              displayType
+            )}
             otherColumns={aggregates}
             noFieldsMessage={noFieldsMessage}
           />
@@ -187,7 +108,7 @@ export function YAxisSelector({
       {!hideAddYAxisButtons && (
         <Actions gap={1}>
           <AddButton title={t('Add Overlay')} onAdd={handleAddOverlay} />
-          {!isReleaseWidget && (
+          {datasetConfig.enableEquations && (
             <AddButton title={t('Add an Equation')} onAdd={handleAddEquation} />
           )}
         </Actions>

+ 4 - 6
static/app/views/dashboardsV2/widgetBuilder/widgetBuilder.tsx

@@ -224,12 +224,10 @@ function WidgetBuilder({
             ...defaultWidgetQuery,
             orderby:
               defaultWidgetQuery.orderby ||
-              generateOrderOptions({
-                widgetType: WidgetType.DISCOVER,
-                widgetBuilderNewDesign,
-                columns: defaultWidgetQuery.columns,
-                aggregates: defaultWidgetQuery.aggregates,
-              })[0].value,
+              (datasetConfig.getTableSortOptions
+                ? datasetConfig.getTableSortOptions(organization, defaultWidgetQuery)[0]
+                    .value
+                : ''),
           },
         ];
       } else {