export function validateMetricThreshold()

in x-pack/solutions/observability/plugins/infra/public/alerting/metric_threshold/components/validation.tsx [27:214]


export function validateMetricThreshold({
  criteria,
  filterQuery,
}: {
  criteria: MetricExpressionParams[];
  filterQuery?: FilterQuery;
}): ValidationResult {
  const validationResult = { errors: {} };
  const errors: {
    [id: string]: {
      aggField: string[];
      timeSizeUnit: string[];
      timeWindowSize: string[];
      critical: {
        threshold0: string[];
        threshold1: string[];
      };
      warning: {
        threshold0: string[];
        threshold1: string[];
      };
      metric: string[];
      customMetricsError?: string;
      customMetrics: Record<string, { aggType?: string; field?: string; filter?: string }>;
      equation?: string;
    };
  } & { filterQuery?: string[] } = {};
  validationResult.errors = errors;

  if (filterQuery === QUERY_INVALID) {
    errors.filterQuery = [
      i18n.translate('xpack.infra.metrics.alertFlyout.error.invalidFilterQuery', {
        defaultMessage: 'Filter query is invalid.',
      }),
    ];
  }

  if (!criteria || !criteria.length) {
    return validationResult;
  }

  criteria.forEach((c, idx) => {
    // Create an id for each criteria, so we can map errors to specific criteria.
    const id = idx.toString();

    errors[id] = errors[id] || {
      aggField: [],
      timeSizeUnit: [],
      timeWindowSize: [],
      critical: {
        threshold0: [],
        threshold1: [],
      },
      warning: {
        threshold0: [],
        threshold1: [],
      },
      metric: [],
      filterQuery: [],
      customMetrics: {},
    };
    if (!c.aggType) {
      errors[id].aggField.push(
        i18n.translate('xpack.infra.metrics.alertFlyout.error.aggregationRequired', {
          defaultMessage: 'Aggregation is required.',
        })
      );
    }

    if (!c.threshold || !c.threshold.length) {
      errors[id].critical.threshold0.push(
        i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', {
          defaultMessage: 'Threshold is required.',
        })
      );
    }

    if (c.warningThreshold && !c.warningThreshold.length) {
      errors[id].warning.threshold0.push(
        i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', {
          defaultMessage: 'Threshold is required.',
        })
      );
    }

    for (const props of [
      { comparator: c.comparator, threshold: c.threshold, type: 'critical' },
      { comparator: c.warningComparator, threshold: c.warningThreshold, type: 'warning' },
    ]) {
      // The Threshold component returns an empty array with a length ([empty]) because it's using delete newThreshold[i].
      // We need to use [...c.threshold] to convert it to an array with an undefined value ([undefined]) so we can test each element.
      const { comparator, threshold, type } = props as {
        comparator?: COMPARATORS;
        threshold?: number[];
        type: 'critical' | 'warning';
      };
      if (threshold && threshold.length && ![...threshold].every(isNumber)) {
        [...threshold].forEach((v, i) => {
          if (!isNumber(v)) {
            const key = i === 0 ? 'threshold0' : 'threshold1';
            errors[id][type][key].push(
              i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdTypeRequired', {
                defaultMessage: 'Thresholds must contain a valid number.',
              })
            );
          }
        });
      }

      if (comparator === COMPARATORS.BETWEEN && (!threshold || threshold.length < 2)) {
        errors[id][type].threshold1.push(
          i18n.translate('xpack.infra.metrics.alertFlyout.error.thresholdRequired', {
            defaultMessage: 'Threshold is required.',
          })
        );
      }
    }

    if (!c.timeSize) {
      errors[id].timeWindowSize.push(
        i18n.translate('xpack.infra.metrics.alertFlyout.error.timeRequred', {
          defaultMessage: 'Time size is Required.',
        })
      );
    }

    if (c.aggType !== 'count' && c.aggType !== 'custom' && !c.metric) {
      errors[id].metric.push(
        i18n.translate('xpack.infra.metrics.alertFlyout.error.metricRequired', {
          defaultMessage: 'Metric is required.',
        })
      );
    }

    if (isCustomMetricExpressionParams(c)) {
      if (!c.customMetrics || (c.customMetrics && c.customMetrics.length < 1)) {
        errors[id].customMetricsError = i18n.translate(
          'xpack.infra.metrics.alertFlyout.error.customMetricsError',
          {
            defaultMessage: 'You must define at least 1 custom metric',
          }
        );
      } else {
        c.customMetrics.forEach((metric) => {
          const customMetricErrors: { aggType?: string; field?: string; filter?: string } = {};
          if (!metric.aggType) {
            customMetricErrors.aggType = i18n.translate(
              'xpack.infra.metrics.alertFlyout.error.customMetrics.aggTypeRequired',
              {
                defaultMessage: 'Aggregation is required',
              }
            );
          }
          if (metric.aggType !== 'count' && !metric.field) {
            customMetricErrors.field = i18n.translate(
              'xpack.infra.metrics.alertFlyout.error.customMetrics.fieldRequired',
              {
                defaultMessage: 'Field is required',
              }
            );
          }
          if (metric.aggType === 'count' && metric.filter) {
            try {
              fromKueryExpression(metric.filter);
            } catch (e) {
              customMetricErrors.filter = e.message;
            }
          }
          if (!isEmpty(customMetricErrors)) {
            errors[id].customMetrics[metric.name] = customMetricErrors;
          }
        });
      }

      if (c.equation && c.equation.match(EQUATION_REGEX)) {
        errors[id].equation = i18n.translate(
          'xpack.infra.metrics.alertFlyout.error.equation.invalidCharacters',
          {
            defaultMessage:
              'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =',
          }
        );
      }
    }
  });

  return validationResult;
}