Skip to content

Commit 9ae8917

Browse files
authored
Signed-off-by: Amit Galitzky <amgalitz@amazon.com>
1 parent 53a0f4f commit 9ae8917

File tree

9 files changed

+129
-52
lines changed

9 files changed

+129
-52
lines changed

public/models/interfaces.ts

+2
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,11 @@ export type EntityOptionsMap = {
231231
export type ValidationModelResponse = {
232232
message: String;
233233
sub_issues?: { [key: string]: string };
234+
validationType: string;
234235
};
235236

236237
export interface ValidationSettingResponse {
237238
issueType: string;
238239
message: string;
240+
validationType: string;
239241
}

public/pages/DefineDetector/utils/constants.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import { FILTER_TYPES, UIFilter } from '../../../models/interfaces';
1313
import { OPERATORS_MAP } from '../../DefineDetector/components/DataFilterList/utils/constant';
1414
import { DetectorDefinitionFormikValues } from '../../DefineDetector/models/interfaces';
15-
1615
export const EMPTY_UI_FILTER: UIFilter = {
1716
filterType: FILTER_TYPES.SIMPLE,
1817
fieldInfo: [],

public/pages/ReviewAndCreate/components/DetectorDefinitionFields/DetectorDefinitionFields.tsx

+33-15
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@ import {
2020
EuiText,
2121
} from '@elastic/eui';
2222
import React from 'react';
23-
import { get } from 'lodash';
23+
import { get, isEqual } from 'lodash';
2424
import {
2525
Detector,
2626
ValidationSettingResponse,
2727
} from '../../../../models/interfaces';
2828
import { FilterDisplayList } from '../FilterDisplayList';
2929
import { ConfigCell, FixedWidthRow } from '../../../../components/ConfigCell';
3030
import { toStringConfigCell } from '../../utils/helpers';
31-
3231
interface DetectorDefinitionFieldsProps {
3332
detector: Detector;
3433
onEditDetectorDefinition(): void;
@@ -94,19 +93,38 @@ export const DetectorDefinitionFields = (
9493
!props.validDetectorSettings &&
9594
props.validationResponse.hasOwnProperty('message')
9695
) {
97-
return (
98-
<EuiCallOut
99-
title="Issues found in the detector settings"
100-
color="danger"
101-
iconType="alert"
102-
size="s"
103-
style={{ marginBottom: '10px' }}
104-
>
105-
<ul>
106-
<li>{props.validationResponse.message}</li>
107-
</ul>
108-
</EuiCallOut>
109-
);
96+
if (
97+
isEqual(get(props, 'validationResponse.validationType', ''), 'model')
98+
) {
99+
return (
100+
<EuiCallOut
101+
title="We identified some areas that might improve your model"
102+
color="warning"
103+
iconType="iInCircle"
104+
size="s"
105+
style={{ marginBottom: '10px' }}
106+
>
107+
{JSON.stringify(props.validationResponse.message).replace(
108+
/\"/g,
109+
''
110+
)}
111+
</EuiCallOut>
112+
);
113+
} else {
114+
return (
115+
<EuiCallOut
116+
title="Issues found in the detector settings"
117+
color="danger"
118+
iconType="alert"
119+
size="s"
120+
style={{ marginBottom: '10px' }}
121+
>
122+
<ul>
123+
<li>{props.validationResponse.message}</li>
124+
</ul>
125+
</EuiCallOut>
126+
);
127+
}
110128
} else {
111129
return null;
112130
}

public/pages/ReviewAndCreate/components/FilterDisplayList/FilterDisplayList.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const FilterDisplayList = (props: FilterDisplayListProps) => {
2727
let filters = get(props, 'uiMetadata.filters', []);
2828
const oldFilterType = get(props, 'uiMetadata.filterType', undefined);
2929

30-
// We want to show the custom filter if filters is empty and
30+
// We want to show the custom filter if filters is empty and
3131
// props.filterQuery isn't empty.
3232
// Two possible situations for the if branch:
3333
// First, old detectors with custom filters will have no filter list, but

public/pages/ReviewAndCreate/components/ModelConfigurationFields/ModelConfigurationFields.tsx

+45-21
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
FeatureAttributes,
2727
ValidationModelResponse,
2828
} from '../../../../models/interfaces';
29-
import { get, sortBy } from 'lodash';
29+
import { get, sortBy, isEqual } from 'lodash';
3030
import ContentPanel from '../../../../components/ContentPanel/ContentPanel';
3131
import { CodeModal } from '../../../../components/CodeModal/CodeModal';
3232
import { AdditionalSettings } from '../AdditionalSettings/AdditionalSettings';
@@ -253,26 +253,50 @@ export const ModelConfigurationFields = (
253253
!props.validModel &&
254254
props.validationFeatureResponse.hasOwnProperty('message')
255255
) {
256-
return (
257-
<EuiCallOut
258-
title="issues found in the model configuration"
259-
color="danger"
260-
iconType="alert"
261-
size="s"
262-
style={{ marginBottom: '10px' }}
263-
>
264-
{/* Callout can either display feature subissue which related to a specific
265-
feature issue or display a feature_attribute issue that is general like
266-
more then x anomaly feature or dulicate feature names */}
267-
{props.validationFeatureResponse.hasOwnProperty('sub_issues') ? (
268-
getFeatureValidationCallout(props.validationFeatureResponse)
269-
) : (
270-
<ul>
271-
<li>{JSON.stringify(props.validationFeatureResponse.message)}</li>
272-
</ul>
273-
)}
274-
</EuiCallOut>
275-
);
256+
if (
257+
isEqual(
258+
get(props, 'validationFeatureResponse.validationType', ''),
259+
'model'
260+
)
261+
) {
262+
return (
263+
<EuiCallOut
264+
title="We identified some areas that might improve your model"
265+
color="warning"
266+
iconType="iInCircle"
267+
size="s"
268+
style={{ marginBottom: '10px' }}
269+
>
270+
{JSON.stringify(props.validationFeatureResponse.message).replace(
271+
/\"/g,
272+
''
273+
)}
274+
</EuiCallOut>
275+
);
276+
} else {
277+
return (
278+
<EuiCallOut
279+
title="Issues found in the model configuration"
280+
color="danger"
281+
iconType="alert"
282+
size="s"
283+
style={{ marginBottom: '10px' }}
284+
>
285+
{/* Callout can either display feature subissue which related to a specific
286+
feature issue or display a feature_attribute issue that is general like
287+
more then x anomaly feature or dulicate feature names */}
288+
{props.validationFeatureResponse.hasOwnProperty('sub_issues') ? (
289+
getFeatureValidationCallout(props.validationFeatureResponse)
290+
) : (
291+
<ul>
292+
<li>
293+
{JSON.stringify(props.validationFeatureResponse.message)}
294+
</li>
295+
</ul>
296+
)}
297+
</EuiCallOut>
298+
);
299+
}
276300
} else {
277301
return null;
278302
}

public/pages/ReviewAndCreate/containers/ReviewAndCreate.tsx

+28-10
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
EuiTitle,
2121
EuiButtonEmpty,
2222
EuiSpacer,
23-
EuiCallOut,
2423
} from '@elastic/eui';
2524
import {
2625
createDetector,
@@ -30,7 +29,7 @@ import {
3029
validateDetector,
3130
} from '../../../redux/reducers/ad';
3231
import { Formik, FormikHelpers } from 'formik';
33-
import { get } from 'lodash';
32+
import { get, isEmpty } from 'lodash';
3433
import React, { Fragment, useEffect, useState } from 'react';
3534
import { RouteComponentProps } from 'react-router';
3635
import { useDispatch, useSelector } from 'react-redux';
@@ -54,7 +53,6 @@ import {
5453
ValidationSettingResponse,
5554
VALIDATION_ISSUE_TYPES,
5655
} from '../../../models/interfaces';
57-
import { isEmpty } from 'lodash';
5856

5957
interface ReviewAndCreateProps extends RouteComponentProps {
6058
setStep(stepNumber: number): void;
@@ -92,28 +90,49 @@ export function ReviewAndCreate(props: ReviewAndCreateProps) {
9290
// meaning validation has passed and succesful callout will display or validation has failed
9391
// and callouts displaying what the issue is will be displayed instead.
9492
useEffect(() => {
95-
dispatch(validateDetector(formikToDetector(props.values)))
93+
dispatch(validateDetector(formikToDetector(props.values), 'model'))
9694
.then((resp: any) => {
9795
if (isEmpty(Object.keys(resp.response))) {
9896
setValidDetectorSettings(true);
9997
setValidModelConfigurations(true);
10098
} else {
101-
if (resp.response.hasOwnProperty('detector')) {
102-
const issueType = Object.keys(resp.response.detector)[0];
103-
if (resp.response.detector[issueType].hasOwnProperty('message')) {
99+
if (
100+
resp.response.hasOwnProperty('detector') ||
101+
resp.response.hasOwnProperty('model')
102+
) {
103+
const validationType = Object.keys(resp.response)[0];
104+
const issueType = Object.keys(resp.response[validationType])[0];
105+
if (
106+
resp.response[validationType][issueType].hasOwnProperty('message')
107+
) {
104108
const validationMessage =
105-
resp.response.detector[issueType].message;
109+
resp.response[validationType][issueType].message;
106110
const detectorSettingIssue: ValidationSettingResponse = {
107111
issueType: issueType,
108112
message: validationMessage,
113+
validationType: validationType,
109114
};
115+
116+
// These issue types only come up during non-blocker validation after blocker validation has passed
117+
// This means that the configurations don't have any blocking issues but request either timed out during
118+
// non blocking validation or due to an issue in core. This means we aren't able to provide any recommendation
119+
// and user has no way of re-trying except re-rendering page which isn't straightforward. At the moment we will
120+
// hide these failures instead of explaining both levels of validation being done in the backend.
121+
if (issueType == 'aggregation' || issueType == 'timeout') {
122+
setValidDetectorSettings(true);
123+
setValidModelConfigurations(true);
124+
return;
125+
}
126+
110127
switch (issueType) {
128+
// need to handle model validation issue case seperatly
111129
case VALIDATION_ISSUE_TYPES.FEATURE_ATTRIBUTES:
112130
case VALIDATION_ISSUE_TYPES.CATEGORY:
113131
case VALIDATION_ISSUE_TYPES.SHINGLE_SIZE_FIELD:
114-
const modelResp = resp.response.detector[
132+
const modelResp = resp.response[validationType][
115133
issueType
116134
] as ValidationModelResponse;
135+
modelResp.validationType = validationType;
117136
setFeatureResponse(modelResp);
118137
setValidDetectorSettings(true);
119138
setValidModelConfigurations(false);
@@ -313,7 +332,6 @@ export function ReviewAndCreate(props: ReviewAndCreateProps) {
313332
iconType="arrowLeft"
314333
fill={false}
315334
data-test-subj="reviewAndCreatePreviousButton"
316-
//@ts-ignore
317335
onClick={() => {
318336
props.setStep(3);
319337
}}

public/redux/reducers/ad.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -377,10 +377,13 @@ export const createDetector = (requestBody: Detector): APIAction => ({
377377
}),
378378
});
379379

380-
export const validateDetector = (requestBody: Detector): APIAction => ({
380+
export const validateDetector = (
381+
requestBody: Detector,
382+
validationType: string
383+
): APIAction => ({
381384
type: VALIDATE_DETECTOR,
382385
request: (client: HttpSetup) =>
383-
client.post(`..${AD_NODE_API.DETECTOR}/_validate`, {
386+
client.post(`..${AD_NODE_API.DETECTOR}/_validate/${validationType}`, {
384387
body: JSON.stringify(requestBody),
385388
}),
386389
});

server/cluster/ad/adPlugin.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@ export default function adPlugin(Client: any, config: any, components: any) {
4545
});
4646
ad.validateDetector = ca({
4747
url: {
48-
fmt: `${API.DETECTOR_BASE}/_validate`,
48+
fmt: `${API.DETECTOR_BASE}/_validate/<%=validationType%>`,
49+
req: {
50+
validationType: {
51+
type: 'string',
52+
required: true,
53+
},
54+
},
4955
},
5056
needBody: true,
5157
method: 'POST',

server/routes/ad.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ export function registerADRoutes(apiRouter: Router, adService: AdService) {
9595
'/detectors/{detectorId}/_topAnomalies/{isHistorical}',
9696
adService.getTopAnomalyResults
9797
);
98-
apiRouter.post('/detectors/_validate', adService.validateDetector);
98+
apiRouter.post(
99+
'/detectors/_validate/{validationType}',
100+
adService.validateDetector
101+
);
99102
}
100103

101104
export default class AdService {
@@ -230,13 +233,17 @@ export default class AdService {
230233
opensearchDashboardsResponse: OpenSearchDashboardsResponseFactory
231234
): Promise<IOpenSearchDashboardsResponse<any>> => {
232235
try {
236+
let { validationType } = request.params as {
237+
validationType: string;
238+
};
233239
const requestBody = JSON.stringify(
234240
convertPreviewInputKeysToSnakeCase(request.body)
235241
);
236242
const response = await this.client
237243
.asScoped(request)
238244
.callAsCurrentUser('ad.validateDetector', {
239245
body: requestBody,
246+
validationType: validationType,
240247
});
241248
return opensearchDashboardsResponse.ok({
242249
body: {

0 commit comments

Comments
 (0)