diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts
index 804abdcf..47420b44 100644
--- a/public/models/interfaces.ts
+++ b/public/models/interfaces.ts
@@ -231,9 +231,11 @@ export type EntityOptionsMap = {
export type ValidationModelResponse = {
message: String;
sub_issues?: { [key: string]: string };
+ validationType: string;
};
export interface ValidationSettingResponse {
issueType: string;
message: string;
+ validationType: string;
}
diff --git a/public/pages/DefineDetector/utils/constants.tsx b/public/pages/DefineDetector/utils/constants.tsx
index 8cf9ef16..09384236 100644
--- a/public/pages/DefineDetector/utils/constants.tsx
+++ b/public/pages/DefineDetector/utils/constants.tsx
@@ -12,7 +12,6 @@
import { FILTER_TYPES, UIFilter } from '../../../models/interfaces';
import { OPERATORS_MAP } from '../../DefineDetector/components/DataFilterList/utils/constant';
import { DetectorDefinitionFormikValues } from '../../DefineDetector/models/interfaces';
-
export const EMPTY_UI_FILTER: UIFilter = {
filterType: FILTER_TYPES.SIMPLE,
fieldInfo: [],
diff --git a/public/pages/ReviewAndCreate/components/DetectorDefinitionFields/DetectorDefinitionFields.tsx b/public/pages/ReviewAndCreate/components/DetectorDefinitionFields/DetectorDefinitionFields.tsx
index f7b3c630..710b6c51 100644
--- a/public/pages/ReviewAndCreate/components/DetectorDefinitionFields/DetectorDefinitionFields.tsx
+++ b/public/pages/ReviewAndCreate/components/DetectorDefinitionFields/DetectorDefinitionFields.tsx
@@ -20,7 +20,7 @@ import {
EuiText,
} from '@elastic/eui';
import React from 'react';
-import { get } from 'lodash';
+import { get, isEqual } from 'lodash';
import {
Detector,
ValidationSettingResponse,
@@ -28,7 +28,6 @@ import {
import { FilterDisplayList } from '../FilterDisplayList';
import { ConfigCell, FixedWidthRow } from '../../../../components/ConfigCell';
import { toStringConfigCell } from '../../utils/helpers';
-
interface DetectorDefinitionFieldsProps {
detector: Detector;
onEditDetectorDefinition(): void;
@@ -94,19 +93,38 @@ export const DetectorDefinitionFields = (
!props.validDetectorSettings &&
props.validationResponse.hasOwnProperty('message')
) {
- return (
-
-
- - {props.validationResponse.message}
-
-
- );
+ if (
+ isEqual(get(props, 'validationResponse.validationType', ''), 'model')
+ ) {
+ return (
+
+ {JSON.stringify(props.validationResponse.message).replace(
+ /\"/g,
+ ''
+ )}
+
+ );
+ } else {
+ return (
+
+
+ - {props.validationResponse.message}
+
+
+ );
+ }
} else {
return null;
}
diff --git a/public/pages/ReviewAndCreate/components/FilterDisplayList/FilterDisplayList.tsx b/public/pages/ReviewAndCreate/components/FilterDisplayList/FilterDisplayList.tsx
index 8e4c82fb..7d0865a8 100644
--- a/public/pages/ReviewAndCreate/components/FilterDisplayList/FilterDisplayList.tsx
+++ b/public/pages/ReviewAndCreate/components/FilterDisplayList/FilterDisplayList.tsx
@@ -27,7 +27,7 @@ export const FilterDisplayList = (props: FilterDisplayListProps) => {
let filters = get(props, 'uiMetadata.filters', []);
const oldFilterType = get(props, 'uiMetadata.filterType', undefined);
- // We want to show the custom filter if filters is empty and
+ // We want to show the custom filter if filters is empty and
// props.filterQuery isn't empty.
// Two possible situations for the if branch:
// First, old detectors with custom filters will have no filter list, but
diff --git a/public/pages/ReviewAndCreate/components/ModelConfigurationFields/ModelConfigurationFields.tsx b/public/pages/ReviewAndCreate/components/ModelConfigurationFields/ModelConfigurationFields.tsx
index f2299ef2..fe41faa9 100644
--- a/public/pages/ReviewAndCreate/components/ModelConfigurationFields/ModelConfigurationFields.tsx
+++ b/public/pages/ReviewAndCreate/components/ModelConfigurationFields/ModelConfigurationFields.tsx
@@ -26,7 +26,7 @@ import {
FeatureAttributes,
ValidationModelResponse,
} from '../../../../models/interfaces';
-import { get, sortBy } from 'lodash';
+import { get, sortBy, isEqual } from 'lodash';
import ContentPanel from '../../../../components/ContentPanel/ContentPanel';
import { CodeModal } from '../../../../components/CodeModal/CodeModal';
import { AdditionalSettings } from '../AdditionalSettings/AdditionalSettings';
@@ -253,26 +253,50 @@ export const ModelConfigurationFields = (
!props.validModel &&
props.validationFeatureResponse.hasOwnProperty('message')
) {
- return (
-
- {/* Callout can either display feature subissue which related to a specific
- feature issue or display a feature_attribute issue that is general like
- more then x anomaly feature or dulicate feature names */}
- {props.validationFeatureResponse.hasOwnProperty('sub_issues') ? (
- getFeatureValidationCallout(props.validationFeatureResponse)
- ) : (
-
- - {JSON.stringify(props.validationFeatureResponse.message)}
-
- )}
-
- );
+ if (
+ isEqual(
+ get(props, 'validationFeatureResponse.validationType', ''),
+ 'model'
+ )
+ ) {
+ return (
+
+ {JSON.stringify(props.validationFeatureResponse.message).replace(
+ /\"/g,
+ ''
+ )}
+
+ );
+ } else {
+ return (
+
+ {/* Callout can either display feature subissue which related to a specific
+ feature issue or display a feature_attribute issue that is general like
+ more then x anomaly feature or dulicate feature names */}
+ {props.validationFeatureResponse.hasOwnProperty('sub_issues') ? (
+ getFeatureValidationCallout(props.validationFeatureResponse)
+ ) : (
+
+ -
+ {JSON.stringify(props.validationFeatureResponse.message)}
+
+
+ )}
+
+ );
+ }
} else {
return null;
}
diff --git a/public/pages/ReviewAndCreate/containers/ReviewAndCreate.tsx b/public/pages/ReviewAndCreate/containers/ReviewAndCreate.tsx
index 002d2744..1055c2d4 100644
--- a/public/pages/ReviewAndCreate/containers/ReviewAndCreate.tsx
+++ b/public/pages/ReviewAndCreate/containers/ReviewAndCreate.tsx
@@ -20,7 +20,6 @@ import {
EuiTitle,
EuiButtonEmpty,
EuiSpacer,
- EuiCallOut,
} from '@elastic/eui';
import {
createDetector,
@@ -30,7 +29,7 @@ import {
validateDetector,
} from '../../../redux/reducers/ad';
import { Formik, FormikHelpers } from 'formik';
-import { get } from 'lodash';
+import { get, isEmpty } from 'lodash';
import React, { Fragment, useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router';
import { useDispatch, useSelector } from 'react-redux';
@@ -54,7 +53,6 @@ import {
ValidationSettingResponse,
VALIDATION_ISSUE_TYPES,
} from '../../../models/interfaces';
-import { isEmpty } from 'lodash';
interface ReviewAndCreateProps extends RouteComponentProps {
setStep(stepNumber: number): void;
@@ -92,28 +90,49 @@ export function ReviewAndCreate(props: ReviewAndCreateProps) {
// meaning validation has passed and succesful callout will display or validation has failed
// and callouts displaying what the issue is will be displayed instead.
useEffect(() => {
- dispatch(validateDetector(formikToDetector(props.values)))
+ dispatch(validateDetector(formikToDetector(props.values), 'model'))
.then((resp: any) => {
if (isEmpty(Object.keys(resp.response))) {
setValidDetectorSettings(true);
setValidModelConfigurations(true);
} else {
- if (resp.response.hasOwnProperty('detector')) {
- const issueType = Object.keys(resp.response.detector)[0];
- if (resp.response.detector[issueType].hasOwnProperty('message')) {
+ if (
+ resp.response.hasOwnProperty('detector') ||
+ resp.response.hasOwnProperty('model')
+ ) {
+ const validationType = Object.keys(resp.response)[0];
+ const issueType = Object.keys(resp.response[validationType])[0];
+ if (
+ resp.response[validationType][issueType].hasOwnProperty('message')
+ ) {
const validationMessage =
- resp.response.detector[issueType].message;
+ resp.response[validationType][issueType].message;
const detectorSettingIssue: ValidationSettingResponse = {
issueType: issueType,
message: validationMessage,
+ validationType: validationType,
};
+
+ // These issue types only come up during non-blocker validation after blocker validation has passed
+ // This means that the configurations don't have any blocking issues but request either timed out during
+ // non blocking validation or due to an issue in core. This means we aren't able to provide any recommendation
+ // and user has no way of re-trying except re-rendering page which isn't straightforward. At the moment we will
+ // hide these failures instead of explaining both levels of validation being done in the backend.
+ if (issueType == 'aggregation' || issueType == 'timeout') {
+ setValidDetectorSettings(true);
+ setValidModelConfigurations(true);
+ return;
+ }
+
switch (issueType) {
+ // need to handle model validation issue case seperatly
case VALIDATION_ISSUE_TYPES.FEATURE_ATTRIBUTES:
case VALIDATION_ISSUE_TYPES.CATEGORY:
case VALIDATION_ISSUE_TYPES.SHINGLE_SIZE_FIELD:
- const modelResp = resp.response.detector[
+ const modelResp = resp.response[validationType][
issueType
] as ValidationModelResponse;
+ modelResp.validationType = validationType;
setFeatureResponse(modelResp);
setValidDetectorSettings(true);
setValidModelConfigurations(false);
@@ -313,7 +332,6 @@ export function ReviewAndCreate(props: ReviewAndCreateProps) {
iconType="arrowLeft"
fill={false}
data-test-subj="reviewAndCreatePreviousButton"
- //@ts-ignore
onClick={() => {
props.setStep(3);
}}
diff --git a/public/redux/reducers/ad.ts b/public/redux/reducers/ad.ts
index 16ab70fa..d4d12ae6 100644
--- a/public/redux/reducers/ad.ts
+++ b/public/redux/reducers/ad.ts
@@ -377,10 +377,13 @@ export const createDetector = (requestBody: Detector): APIAction => ({
}),
});
-export const validateDetector = (requestBody: Detector): APIAction => ({
+export const validateDetector = (
+ requestBody: Detector,
+ validationType: string
+): APIAction => ({
type: VALIDATE_DETECTOR,
request: (client: HttpSetup) =>
- client.post(`..${AD_NODE_API.DETECTOR}/_validate`, {
+ client.post(`..${AD_NODE_API.DETECTOR}/_validate/${validationType}`, {
body: JSON.stringify(requestBody),
}),
});
diff --git a/server/cluster/ad/adPlugin.ts b/server/cluster/ad/adPlugin.ts
index cbe2cf9f..2020983c 100644
--- a/server/cluster/ad/adPlugin.ts
+++ b/server/cluster/ad/adPlugin.ts
@@ -45,7 +45,13 @@ export default function adPlugin(Client: any, config: any, components: any) {
});
ad.validateDetector = ca({
url: {
- fmt: `${API.DETECTOR_BASE}/_validate`,
+ fmt: `${API.DETECTOR_BASE}/_validate/<%=validationType%>`,
+ req: {
+ validationType: {
+ type: 'string',
+ required: true,
+ },
+ },
},
needBody: true,
method: 'POST',
diff --git a/server/routes/ad.ts b/server/routes/ad.ts
index 80a9a395..bfdf2476 100644
--- a/server/routes/ad.ts
+++ b/server/routes/ad.ts
@@ -95,7 +95,10 @@ export function registerADRoutes(apiRouter: Router, adService: AdService) {
'/detectors/{detectorId}/_topAnomalies/{isHistorical}',
adService.getTopAnomalyResults
);
- apiRouter.post('/detectors/_validate', adService.validateDetector);
+ apiRouter.post(
+ '/detectors/_validate/{validationType}',
+ adService.validateDetector
+ );
}
export default class AdService {
@@ -230,6 +233,9 @@ export default class AdService {
opensearchDashboardsResponse: OpenSearchDashboardsResponseFactory
): Promise> => {
try {
+ let { validationType } = request.params as {
+ validationType: string;
+ };
const requestBody = JSON.stringify(
convertPreviewInputKeysToSnakeCase(request.body)
);
@@ -237,6 +243,7 @@ export default class AdService {
.asScoped(request)
.callAsCurrentUser('ad.validateDetector', {
body: requestBody,
+ validationType: validationType,
});
return opensearchDashboardsResponse.ok({
body: {