From c9d70c5859a680a286c8362d1b2bd76a2f56360f Mon Sep 17 00:00:00 2001 From: Joyce Liu Date: Wed, 26 Aug 2020 19:14:05 -0700 Subject: [PATCH] Adds window size as advanced setting in model configuration. --- public/models/interfaces.ts | 1 + .../pages/DetectorConfig/DetectorConfig.scss | 11 +++ .../DetectorConfig/containers/Features.tsx | 64 +++++++++---- .../DetectorConfig/containers/MetaData.tsx | 3 - .../__tests__/DetectorConfig.test.tsx | 17 ++-- .../DetectorControls/DetectorControls.tsx | 2 +- public/pages/DetectorResults/utils/utils.ts | 3 +- .../EditFeatures/containers/EditFeatures.tsx | 89 +++++++++++++++++-- .../containers/SampleAnomalies.tsx | 2 + .../utils/__tests__/formikToFeatures.test.ts | 3 +- .../containers/utils/formikToFeatures.ts | 8 +- .../containers/models/interfaces.ts | 1 + .../utils/__tests__/detectorToFormik.test.ts | 3 + .../containers/utils/detectorToFormik.ts | 2 + public/redux/configureStore.ts | 3 +- public/utils/constants.ts | 2 +- 16 files changed, 167 insertions(+), 47 deletions(-) diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts index 6f295143..f826fee0 100644 --- a/public/models/interfaces.ts +++ b/public/models/interfaces.ts @@ -103,6 +103,7 @@ export type Detector = { featureAttributes: FeatureAttributes[]; windowDelay: { period: Schedule }; detectionInterval: { period: Schedule }; + shingleSize: number; uiMetadata: UiMetaData; lastUpdateTime: number; enabled?: boolean; diff --git a/public/pages/DetectorConfig/DetectorConfig.scss b/public/pages/DetectorConfig/DetectorConfig.scss index 5af6c250..c2b5d1e4 100644 --- a/public/pages/DetectorConfig/DetectorConfig.scss +++ b/public/pages/DetectorConfig/DetectorConfig.scss @@ -82,3 +82,14 @@ line-height: 24px; text-align: center; } + +.header-single-value-euiBasicTable { + .euiTableHeaderCell, .euiTableRowCell { + border: 0; + .euiTableCellContent { + padding-top: 0; + padding-bottom: 0; + } + } + +} diff --git a/public/pages/DetectorConfig/containers/Features.tsx b/public/pages/DetectorConfig/containers/Features.tsx index 7b90b8d9..5e809e67 100644 --- a/public/pages/DetectorConfig/containers/Features.tsx +++ b/public/pages/DetectorConfig/containers/Features.tsx @@ -20,6 +20,7 @@ import { EuiIcon, EuiButton, EuiEmptyPrompt, + EuiSpacer, } from '@elastic/eui'; import { Detector, @@ -27,7 +28,7 @@ import { FeatureAttributes, } from '../../../models/interfaces'; import { get, sortBy } from 'lodash'; -import { PLUGIN_NAME } from '../../../utils/constants'; +import { PLUGIN_NAME, SHINGLE_SIZE } from '../../../utils/constants'; import ContentPanel from '../../../components/ContentPanel/ContentPanel'; import { CodeModal } from '../components/CodeModal/CodeModal'; import { getTitleWithCount } from '../../../utils/utils'; @@ -93,6 +94,7 @@ export class Features extends Component { public render() { const featureAttributes = get(this.props.detector, 'featureAttributes', []); + const shingleSize = get(this.props.detector, 'shingleSize', SHINGLE_SIZE); const sorting = { sort: { @@ -170,7 +172,7 @@ export class Features extends Component { }, { field: 'state', - name: 'State', + name: 'Feature state', }, ]; @@ -182,17 +184,21 @@ export class Features extends Component { const featureNum = Object.keys(featureAttributes).length; + const setParamsText = `Set the index fields that you want to find anomalies for by defining + the model features. You can also set other model parameters such as + window size.` + + const previewText = `After you set the model features and other optional parameters, you can + preview your anomalies from a sample feature output.` + return (

- Specify index fields that you want to find anomalies for by - defining features. A detector can discover anomalies for up to 5 - features. Once you define the features, you can preview your - anomalies from a sample feature output.{' '} + {`${setParamsText} ${previewText} `} { - Features are required to run a detector + Model parameters are required to run a detector } body={ - Specify index fields that you want to find anomalies for by - defining features. Once you define the features, you can preview - your anomalies from a sample feature output. + {setParamsText} +
+
+ {previewText}
} actions={[ @@ -227,18 +234,37 @@ export class Features extends Component { href={`${PLUGIN_NAME}#/detectors/${this.props.detectorId}/features`} fill > - Add feature + Configure model , ]} /> ) : ( - +

+ + + + + + + +
)}
); diff --git a/public/pages/DetectorConfig/containers/MetaData.tsx b/public/pages/DetectorConfig/containers/MetaData.tsx index 9f2bdf80..0e72e3e0 100644 --- a/public/pages/DetectorConfig/containers/MetaData.tsx +++ b/public/pages/DetectorConfig/containers/MetaData.tsx @@ -24,11 +24,8 @@ import { EuiButton, EuiFormRowProps, } from '@elastic/eui'; -import { PLUGIN_NAME } from '../../../utils/constants'; import { Detector, - Schedule, - UiMetaData, FILTER_TYPES, UIFilter, } from '../../../models/interfaces'; diff --git a/public/pages/DetectorConfig/containers/__tests__/DetectorConfig.test.tsx b/public/pages/DetectorConfig/containers/__tests__/DetectorConfig.test.tsx index 5d09ec55..991f6940 100644 --- a/public/pages/DetectorConfig/containers/__tests__/DetectorConfig.test.tsx +++ b/public/pages/DetectorConfig/containers/__tests__/DetectorConfig.test.tsx @@ -25,7 +25,6 @@ import { render, fireEvent, wait, - waitForElement, } from '@testing-library/react'; // @ts-ignore import { toastNotifications } from 'ui/notify'; @@ -41,7 +40,6 @@ import { } from '../../../../models/interfaces'; import { getRandomDetector, - getRandomFeature, } from '../../../../redux/reducers/__tests__/utils'; import configureStore from '../../../../redux/configureStore'; import { httpClientMock } from '../../../../../test/mocks'; @@ -161,14 +159,15 @@ describe(' spec', () => { ...getRandomDetector(true), uiMetadata: {} as UiMetaData, featureAttributes: [], + shingleSize: 8, }; - const { getByText } = renderWithRouter(randomDetector); + const { getByText, queryByText } = renderWithRouter(randomDetector); await wait(() => { - getByText('Features are required to run a detector'); - getByText( - 'Specify index fields that you want to find anomalies for by defining features. Once you define the features, you can preview your anomalies from a sample feature output.' + getByText('Model parameters are required to run a detector'); + queryByText( + 'Set the index fields' ); - getByText('Features'); + getByText('Model configuration'); getByText(randomDetector.name); getByText(randomDetector.indices[0]); getByText(toString(randomDetector.detectionInterval)); @@ -178,8 +177,8 @@ describe(' spec', () => { getByText(randomDetector.description); // filter should be - getByText('-'); - getByText( - 'Specify index fields that you want to find anomalies for by defining features. A detector can discover anomalies for up to 5 features. Once you define the features, you can preview your anomalies from a sample feature output.' + queryByText( + 'Set the index fields' ); }); }); diff --git a/public/pages/DetectorDetail/components/DetectorControls/DetectorControls.tsx b/public/pages/DetectorDetail/components/DetectorControls/DetectorControls.tsx index 952708aa..55982a92 100644 --- a/public/pages/DetectorDetail/components/DetectorControls/DetectorControls.tsx +++ b/public/pages/DetectorDetail/components/DetectorControls/DetectorControls.tsx @@ -68,7 +68,7 @@ export const DetectorControls = (props: DetectorControls) => { data-test-subj="editFeature" onClick={props.onEditFeatures} > - Edit features + Edit model configuration { //@ts-ignore currentTime .subtract( - SHINGLE_SIZE * detector.detectionInterval.period.interval, + get(detector, 'shingleSize', SHINGLE_SIZE) * detector.detectionInterval.period.interval, detector.detectionInterval.period.unit.toLowerCase() ) //@ts-ignore diff --git a/public/pages/EditFeatures/containers/EditFeatures.tsx b/public/pages/EditFeatures/containers/EditFeatures.tsx index 321134c4..00305f79 100644 --- a/public/pages/EditFeatures/containers/EditFeatures.tsx +++ b/public/pages/EditFeatures/containers/EditFeatures.tsx @@ -29,9 +29,11 @@ import { EuiIcon, EuiCallOut, EuiSpacer, + EuiFieldNumber, + EuiFormRow, } from '@elastic/eui'; -import { FieldArray, FieldArrayRenderProps, Form, Formik } from 'formik'; -import { get, isEmpty, forOwn } from 'lodash'; +import { FieldArray, FieldArrayRenderProps, Form, Formik, Field, FieldProps } from 'formik'; +import { get, isEmpty } from 'lodash'; import React, { Fragment, useState, useEffect, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { RouteComponentProps } from 'react-router-dom'; @@ -39,7 +41,7 @@ import ContentPanel from '../../../components/ContentPanel/ContentPanel'; // @ts-ignore import { toastNotifications } from 'ui/notify'; import { updateDetector, startDetector } from '../../../redux/reducers/ad'; -import { getErrorMessage } from '../../../utils/utils'; +import { getErrorMessage, validatePositiveInteger, isInvalid, getError } from '../../../utils/utils'; import { prepareDetector } from './utils/formikToFeatures'; import { useFetchDetectorInfo } from '../../createDetector/hooks/useFetchDetectorInfo'; //@ts-ignore @@ -49,6 +51,7 @@ import { useHideSideNavBar } from '../../main/hooks/useHideSideNavBar'; import { FeatureAccordion } from '../components/FeatureAccordion/FeatureAccordion'; import { SaveFeaturesConfirmModal } from '../components/ConfirmModal/SaveFeaturesConfirmModal'; import { SAVE_FEATURE_OPTIONS } from '../utils/constants'; +import { SHINGLE_SIZE } from '../../../utils/constants'; import { initialFeatureValue, generateInitialFeatures, @@ -76,6 +79,7 @@ export function EditFeatures(props: EditFeaturesProps) { >(SAVE_FEATURE_OPTIONS.START_AD_JOB); const [firstLoad, setFirstLoad] = useState(true); const [readyToStartAdJob, setReadyToStartAdJob] = useState(true); + const [showAdvancedSettings, setShowAdvancedSettings] = useState(false); useEffect(() => { chrome.breadcrumbs.set([ @@ -85,7 +89,7 @@ export function EditFeatures(props: EditFeaturesProps) { text: detector && detector.name ? detector.name : '', href: `#/detectors/${detectorId}`, }, - BREADCRUMBS.EDIT_FEATURES, + BREADCRUMBS.EDIT_MODEL_CONFIGURATION, ]); }, [detector]); @@ -98,14 +102,13 @@ export function EditFeatures(props: EditFeaturesProps) { const featureDescription = ( Specify an index field that you want to find anomalies for by defining - features{' '} + features. You can add up to 5 features.{' '} Learn more - . You can add up to 5 features. ); @@ -197,6 +200,7 @@ export function EditFeatures(props: EditFeaturesProps) { try { const requestBody = prepareDetector( get(values, 'featureList', []), + get(values, 'shingleSize', SHINGLE_SIZE), detector ); await dispatch(updateDetector(detector.id, requestBody)); @@ -243,11 +247,72 @@ export function EditFeatures(props: EditFeaturesProps) { } }; + const renderAdvancedSettingsToggle = () => ( + {setShowAdvancedSettings(!showAdvancedSettings);}} + > + + {showAdvancedSettings ? 'Hide' : 'Show'} + + + ); + + const renderAdvancedSettings = () => ( + + {({ field, form }: FieldProps) => ( + + Set the number of intervals to consider in a detection + window. We recommend you choose this value based on your actual + data. If you expect missing values in your data or if you want + the anomalies based on the current interval, choose 1. If + your data is continuously ingested and you want the anomalies + based on multiple intervals, choose a larger window size.{' '} + + Learn more + + + } + isInvalid={isInvalid(field.name, form)} + error={getError(field.name, form)} + > + + + + + + +

intervals

+
+
+
+
+ )} +
+ ); + return ( handleSubmit(values, actions.setSubmitting) } @@ -269,7 +334,7 @@ export function EditFeatures(props: EditFeaturesProps) { -

Edit features

+

Model configuration

@@ -278,12 +343,20 @@ export function EditFeatures(props: EditFeaturesProps) { + + + + {!isEmpty(detector) && showAdvancedSettings ? renderAdvancedSettings() : null} + + + {!isEmpty(detector) ? ( diff --git a/public/pages/EditFeatures/containers/SampleAnomalies.tsx b/public/pages/EditFeatures/containers/SampleAnomalies.tsx index 9e1b15fd..be0d462d 100644 --- a/public/pages/EditFeatures/containers/SampleAnomalies.tsx +++ b/public/pages/EditFeatures/containers/SampleAnomalies.tsx @@ -49,6 +49,7 @@ import { focusOnFirstWrongFeature } from '../utils/helpers'; interface SampleAnomaliesProps { detector: Detector; featureList: FeaturesFormikValues[]; + shingleSize: number; errors: any; setFieldTouched: any; } @@ -137,6 +138,7 @@ export function SampleAnomalies(props: SampleAnomaliesProps) { try { const updatedDetector = prepareDetector( props.featureList, + props.shingleSize, newDetector, true ); diff --git a/public/pages/EditFeatures/containers/utils/__tests__/formikToFeatures.test.ts b/public/pages/EditFeatures/containers/utils/__tests__/formikToFeatures.test.ts index 01453ecb..c299c208 100644 --- a/public/pages/EditFeatures/containers/utils/__tests__/formikToFeatures.test.ts +++ b/public/pages/EditFeatures/containers/utils/__tests__/formikToFeatures.test.ts @@ -30,7 +30,8 @@ describe('featuresToFormik', () => { featureType: FEATURE_TYPE.SIMPLE, aggregationQuery: '', }; - const apiRequest = prepareDetector([newFeature], randomDetector, false); + const randomPositiveInt = Math.ceil(Math.random()*100); + const apiRequest = prepareDetector([newFeature], randomPositiveInt, randomDetector, false); // expect(apiRequest.featureAttributes).toEqual([ // { // featureId: newFeature.featureId, diff --git a/public/pages/EditFeatures/containers/utils/formikToFeatures.ts b/public/pages/EditFeatures/containers/utils/formikToFeatures.ts index 324c20d7..0232c1d3 100644 --- a/public/pages/EditFeatures/containers/utils/formikToFeatures.ts +++ b/public/pages/EditFeatures/containers/utils/formikToFeatures.ts @@ -35,19 +35,21 @@ export interface FeaturesFormikValues { } export function prepareDetector( - values: FeaturesFormikValues[], + featureValues: FeaturesFormikValues[], + shingleSizeValue: number, ad: Detector, forPreview: boolean = false ): Detector { const detector = cloneDeep(ad); - const featureAttributes = formikToFeatures(values, forPreview); + const featureAttributes = formikToFeatures(featureValues, forPreview); return { ...detector, featureAttributes: [...featureAttributes], + shingleSize: shingleSizeValue, uiMetadata: { ...detector.uiMetadata, - features: { ...formikToUIMetadata(values) }, + features: { ...formikToUIMetadata(featureValues) }, }, }; } diff --git a/public/pages/createDetector/containers/models/interfaces.ts b/public/pages/createDetector/containers/models/interfaces.ts index 9cd642b6..96c2b3b3 100644 --- a/public/pages/createDetector/containers/models/interfaces.ts +++ b/public/pages/createDetector/containers/models/interfaces.ts @@ -25,4 +25,5 @@ export interface ADFormikValues { filterQuery: string; detectionInterval: number; windowDelay: number; + shingleSize: number; } diff --git a/public/pages/createDetector/containers/utils/__tests__/detectorToFormik.test.ts b/public/pages/createDetector/containers/utils/__tests__/detectorToFormik.test.ts index 2464f93e..4fc21495 100644 --- a/public/pages/createDetector/containers/utils/__tests__/detectorToFormik.test.ts +++ b/public/pages/createDetector/containers/utils/__tests__/detectorToFormik.test.ts @@ -14,6 +14,7 @@ */ import { INITIAL_VALUES } from '../constant'; +import { SHINGLE_SIZE } from '../../../../../utils/constants'; import { getRandomDetector } from '../../../../../redux/reducers/__tests__/utils'; import { detectorToFormik } from '../detectorToFormik'; import { Detector, FILTER_TYPES } from '../../../../../models/interfaces'; @@ -34,6 +35,7 @@ describe('adToFormik', () => { filterType: FILTER_TYPES.SIMPLE, filterQuery: JSON.stringify(randomDetector.filterQuery || {}, null, 4), index: [{ label: randomDetector.indices[0] }], // Currently we support only one index + shingleSize: SHINGLE_SIZE, timeField: randomDetector.timeField, detectionInterval: randomDetector.detectionInterval.period.interval, windowDelay: randomDetector.windowDelay.period.interval, @@ -51,6 +53,7 @@ describe('adToFormik', () => { filterType: FILTER_TYPES.CUSTOM, filterQuery: JSON.stringify(randomDetector.filterQuery || {}, null, 4), index: [{ label: randomDetector.indices[0] }], // Currently we support only one index + shingleSize: SHINGLE_SIZE, timeField: randomDetector.timeField, detectionInterval: randomDetector.detectionInterval.period.interval, windowDelay: randomDetector.windowDelay.period.interval, diff --git a/public/pages/createDetector/containers/utils/detectorToFormik.ts b/public/pages/createDetector/containers/utils/detectorToFormik.ts index 833c5df6..aa4c7d99 100644 --- a/public/pages/createDetector/containers/utils/detectorToFormik.ts +++ b/public/pages/createDetector/containers/utils/detectorToFormik.ts @@ -17,6 +17,7 @@ import { cloneDeep, isEmpty, get } from 'lodash'; import { Detector, FILTER_TYPES } from '../../../../models/interfaces'; import { ADFormikValues } from '../models/interfaces'; import { INITIAL_VALUES } from './constant'; +import { SHINGLE_SIZE } from '../../../../utils/constants' export function detectorToFormik(ad: Detector): ADFormikValues { const initialValues = cloneDeep(INITIAL_VALUES); @@ -40,5 +41,6 @@ export function detectorToFormik(ad: Detector): ADFormikValues { timeField: ad.timeField, detectionInterval: get(ad, 'detectionInterval.period.interval', 10), windowDelay: get(ad, 'windowDelay.period.interval', 0), + shingleSize: get(ad, 'shingleSize', SHINGLE_SIZE), }; } diff --git a/public/redux/configureStore.ts b/public/redux/configureStore.ts index f94b2903..319bbbaf 100644 --- a/public/redux/configureStore.ts +++ b/public/redux/configureStore.ts @@ -20,7 +20,8 @@ import reducers, { AppState } from './reducers'; function configureStore(httpClient: IHttpService) { const middleWares = [clientMiddleware(httpClient)]; - const store = compose(applyMiddleware(...middleWares))(createStore); + const composeWithReduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; + const store = composeWithReduxDevTools(applyMiddleware(...middleWares))(createStore); //@ts-ignore return store(reducers); } diff --git a/public/utils/constants.ts b/public/utils/constants.ts index 3cfbdb2e..64ba72e8 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -28,7 +28,7 @@ export const BREADCRUMBS = Object.freeze({ CREATE_DETECTOR: { text: 'Create detector' }, EDIT_DETECTOR: { text: 'Edit detector' }, DASHBOARD: { text: 'Dashboard', href: '#/' }, - EDIT_FEATURES: { text: 'Edit features' }, + EDIT_MODEL_CONFIGURATION: { text: 'Edit model configuration' }, }); export const APP_PATH = {