diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx index 70c27e684..d2e624caa 100644 --- a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx +++ b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx @@ -6,6 +6,7 @@ import React, { useState } from 'react'; import { get } from 'lodash'; import AssociatedDetectors from '../AssociatedDetectors/containers/AssociatedDetectors'; import { getEmbeddable } from '../../../../public/services'; +import AddAnomalyDetector from '../CreateAnomalyDetector/AddAnomalyDetector'; const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { const embeddable = getEmbeddable().getEmbeddableFactory; @@ -17,6 +18,7 @@ const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { const [selectedDetectorId, setSelectedDetectorId] = useState(); const AnywhereFlyout = { + create: AddAnomalyDetector, associated: AssociatedDetectors, }[mode]; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx new file mode 100644 index 000000000..ead4014b9 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx @@ -0,0 +1,718 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, useEffect, Fragment } from 'react'; +import { + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiTitle, + EuiButton, + EuiFormFieldset, + EuiCheckableCard, + EuiSpacer, + EuiIcon, + EuiText, + EuiSwitch, + EuiFormRow, + EuiFieldText, + EuiCheckbox, + EuiFlexItem, + EuiFlexGroup, + EuiFieldNumber, + EuiCallOut, + EuiButtonEmpty, + EuiPanel, +} from '@elastic/eui'; +import './styles.scss'; +import { + createAugmentVisSavedObject, + ISavedAugmentVis, + ISavedPluginResource, + VisLayerExpressionFn, + VisLayerTypes, +} from '../../../../../../src/plugins/vis_augmenter/public'; +import { useDispatch } from 'react-redux'; +import { isEmpty, get } from 'lodash'; +import { + Field, + FieldArray, + FieldArrayRenderProps, + FieldProps, + Formik, +} from 'formik'; +import { + createDetector, + getDetectorCount, + matchDetector, + startDetector, +} from '../../../../public/redux/reducers/ad'; +import { EmbeddableRenderer } from '../../../../../../src/plugins/embeddable/public'; +import './styles.scss'; +import EnhancedAccordion from '../EnhancedAccordion'; +import MinimalAccordion from '../MinimalAccordion'; +import { DataFilterList } from '../../../../public/pages/DefineDetector/components/DataFilterList/DataFilterList'; +import { + getError, + getErrorMessage, + isInvalid, + validateDetectorName, + validateNonNegativeInteger, + validatePositiveInteger, +} from '../../../../public/utils/utils'; +import { + CUSTOM_AD_RESULT_INDEX_PREFIX, + MAX_DETECTORS, +} from '../../../../server/utils/constants'; +import { + focusOnFirstWrongFeature, + initialFeatureValue, +} from '../../../../public/pages/ConfigureModel/utils/helpers'; +import { + getIndices, + getMappings, +} from '../../../../public/redux/reducers/opensearch'; +import { formikToDetector } from '../../../../public/pages/ReviewAndCreate/utils/helpers'; +import { FormattedFormRow } from '../../../../public/components/FormattedFormRow/FormattedFormRow'; +import { FeatureAccordion } from '../../../../public/pages/ConfigureModel/components/FeatureAccordion'; +import { + AD_DOCS_LINK, + AD_HIGH_CARDINALITY_LINK, + MAX_FEATURE_NUM, +} from '../../../../public/utils/constants'; +import { getNotifications } from '../../../../public/services'; +import { prettifyErrorMessage } from '../../../../server/utils/helpers'; +import { + ORIGIN_PLUGIN_VIS_LAYER, + OVERLAY_ANOMALIES, + VIS_LAYER_PLUGIN_TYPE, +} from '../../../../public/expressions/constants'; +import { formikToDetectorName, visFeatureListToFormik } from './helpers'; + +function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) { + const dispatch = useDispatch(); + const [queryText, setQueryText] = useState(''); + useEffect(() => { + const getInitialIndices = async () => { + await dispatch(getIndices(queryText)); + }; + getInitialIndices(); + dispatch(getMappings(embeddable.vis.data.aggs.indexPattern.title)); + }, []); + + const [isShowVis, setIsShowVis] = useState(false); + const [accordionsOpen, setAccordionsOpen] = useState({ modelFeatures: true }); + const [detectorNameFromVis, setDetectorNameFromVis] = useState( + formikToDetectorName(embeddable.vis.title) + ); + const [intervalValue, setIntervalalue] = useState(10); + const [delayValue, setDelayValue] = useState(1); + const [enabled, setEnabled] = useState(false); + + const title = embeddable.getTitle(); + const onAccordionToggle = (key) => { + const newAccordionsOpen = { ...accordionsOpen }; + newAccordionsOpen[key] = !accordionsOpen[key]; + setAccordionsOpen(newAccordionsOpen); + }; + const onDetectorNameChange = (e, field) => { + field.onChange(e); + setDetectorNameFromVis(e.target.value); + }; + const onIntervalChange = (e, field) => { + field.onChange(e); + setIntervalalue(e.target.value); + }; + const onDelayChange = (e, field) => { + field.onChange(e); + setDelayValue(e.target.value); + }; + const aggList = embeddable.vis.data.aggs.aggs.filter( + (feature) => feature.schema == 'metric' + ); + const featureList = aggList.filter( + (feature, index) => + index < + (aggList.length < MAX_FEATURE_NUM ? aggList.length : MAX_FEATURE_NUM) + ); + + const notifications = getNotifications(); + const handleValidationAndSubmit = (formikProps) => { + if (!isEmpty(formikProps.errors)) { + focusOnFirstWrongFeature(formikProps.errors, formikProps.setFieldTouched); + notifications.toasts.addDanger('One or more input fields is invalid'); + } else { + handleSubmit(formikProps); + } + }; + + const handleSubmit = (formikProps) => { + formikProps.setSubmitting(true); + try { + const detectorToCreate = formikToDetector(formikProps.values); + dispatch(createDetector(detectorToCreate)) + .then(async (response) => { + notifications.toasts.addSuccess( + `Detector created: ${formikProps.values.name}` + ); + dispatch(startDetector(response.response.id)) + .then((startDetectorResponse) => { + notifications.toasts.addSuccess( + `Successfully started the real-time detector` + ); + }) + .catch((err: any) => { + notifications.toasts.addDanger( + prettifyErrorMessage( + getErrorMessage( + err, + 'There was a problem starting the real-time detector' + ) + ) + ); + }); + + const fn = { + type: VisLayerTypes.PointInTimeEvents, + name: OVERLAY_ANOMALIES, + args: { + detectorId: response.response.id, + }, + } as VisLayerExpressionFn; + + const pluginResource = { + type: VIS_LAYER_PLUGIN_TYPE, + id: response.response.id, + } as ISavedPluginResource; + + const savedObjectToCreate = { + title: embeddable.vis.title, + originPlugin: ORIGIN_PLUGIN_VIS_LAYER, + pluginResource: pluginResource, + visId: embeddable.vis.id, + savedObjectType: 'visualization', + visLayerExpressionFn: fn, + } as ISavedAugmentVis; + + // TODO: catch saved object failure + const savedObject = await createAugmentVisSavedObject( + savedObjectToCreate + ); + + const saveObjectResponse = await savedObject.save({}); + }) + .catch((err: any) => { + dispatch(getDetectorCount()).then((response: any) => { + const totalDetectors = get(response, 'response.count', 0); + if (totalDetectors === MAX_DETECTORS) { + notifications.toasts.addDanger( + 'Cannot create detector - limit of ' + + MAX_DETECTORS + + ' detectors reached' + ); + } else { + notifications.toasts.addDanger( + prettifyErrorMessage( + getErrorMessage( + err, + 'There was a problem creating the detector' + ) + ) + ); + } + }); + }); + closeFlyout(); + } catch (e) { + } finally { + formikProps.setSubmitting(false); + } + }; + + const validateVisDetectorName = async (detectorName: string) => { + if (isEmpty(detectorName)) { + return 'Detector name cannot be empty'; + } else { + const error = validateDetectorName(detectorName); + if (error) { + return error; + } + const resp = await dispatch(matchDetector(detectorName)); + const match = get(resp, 'response.match', false); + if (!match) { + return undefined; + } + //If more than one detectors found, duplicate exists. + if (match) { + return 'Duplicate detector name'; + } + } + }; + + const initialDetectorValue = { + name: detectorNameFromVis, + index: [{ label: embeddable.vis.data.aggs.indexPattern.title }], + timeField: embeddable.vis.data.indexPattern.timeFieldName, + interval: intervalValue, + windowDelay: delayValue, + shingleSize: 8, + filterQuery: { match_all: {} }, + description: '', + resultIndex: undefined, + filters: [], + featureList: visFeatureListToFormik( + featureList, + embeddable.vis.params.seriesParams + ), + categoryFieldEnabled: false, + realTime: true, + historical: false, + }; + + return ( +
+ + {(formikProps) => ( + <> + + +

Add anomaly detector

+
+
+ +
+ + + Options to create a new detector or associate an + existing detector + + + ), + }} + className="add-anomaly-detector__modes" + > + {[ + { + id: 'add-anomaly-detector__create', + label: 'Create new detector', + value: 'create', + }, + { + id: 'add-anomaly-detector__existing', + label: 'Associate existing detector', + value: 'existing', + }, + ].map((option) => ( + setMode(option.value), + }} + /> + ))} + + + {mode === 'create' && ( +
+ +

+ Create and configure an anomaly detector to + automatically detect anomalies in your data and to view + real-time results on the visualization.{' '} + + Learn more + +

+
+ +
+ +

+ + {title} +

+
+ setIsShowVis(!isShowVis)} + /> +
+
+ + +
+ + +

Detector details

+
+ + + onAccordionToggle('detectorDetails')} + subTitle={ + +

+ Detector interval: {intervalValue} minutes; Window + delay: {delayValue} minutes +

+
+ } + > + + {({ field, form }: FieldProps) => ( + + onDetectorNameChange(e, field)} + /> + + )} + + + + + {({ field, form }: FieldProps) => ( + + + + + + + onIntervalChange(e, field) + } + /> + + + +

minutes

+
+
+
+
+
+
+ )} +
+ + + + {({ field, form }: FieldProps) => ( + + + + onDelayChange(e, field)} + /> + + + +

minutes

+
+
+
+
+ )} +
+
+ + + + + onAccordionToggle('advancedConfiguration') + } + initialIsOpen={false} + > + + + + +

+ Source:{' '} + {embeddable.vis.data.aggs.indexPattern.title} +

+
+ + +
+ + + + + {({ field, form }: FieldProps) => ( + + + + + + + +

intervals

+
+
+
+
+ )} +
+
+ + + + {({ field, form }: FieldProps) => ( + + + { + if (enabled) { + form.setFieldValue('resultIndex', ''); + } + setEnabled(!enabled); + }} + /> + + + {enabled ? ( + + + + ) : null} + + {enabled ? ( + + + + + + ) : null} + + )} + + + + + +

+ The dashboard does not support high-cardinality + detectors. + + Learn more + +

+
+
+
+ + + +

Model Features

+
+ + + onAccordionToggle('modelFeatures')} + > + + + + {({ + push, + remove, + form: { values }, + }: FieldArrayRenderProps) => { + return ( + + {values.featureList.map( + (feature: any, index: number) => ( + { + remove(index); + }} + index={index} + feature={feature} + handleChange={formikProps.handleChange} + displayMode="flyout" + /> + ) + )} + + = MAX_FEATURE_NUM + } + onClick={() => { + push(initialFeatureValue()); + }} + > + Add another feature + + + + +

+ You can add up to{' '} + {Math.max( + MAX_FEATURE_NUM - values.featureList.length, + 0 + )}{' '} + more features. +

+
+
+ ); + }} +
+
+
+ )} +
+
+ + + + Cancel + + + { + handleValidationAndSubmit(formikProps); + }} + > + Create Detector + + + + + + )} +
+
+ ); +} + +export default AddAnomalyDetector; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/helpers.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/helpers.tsx new file mode 100644 index 000000000..5c69015ce --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/helpers.tsx @@ -0,0 +1,61 @@ +import { dispatch } from 'd3'; +import { matchDetector } from 'public/redux/reducers/ad'; +import { validateDetectorName } from 'public/utils/utils'; +import { FEATURE_TYPE } from '../../../../public/models/interfaces'; +import { FeaturesFormikValues } from '../../../../public/pages/ConfigureModel/models/interfaces'; +import { find, get, isEmpty, snakeCase } from 'lodash'; + +export function visFeatureListToFormik( + featureList, + seriesParams +): FeaturesFormikValues[] { + return featureList.map((feature) => { + return { + featureId: feature.id, + featureName: getFeatureNameFromVisParams(feature.id, seriesParams), + featureEnabled: true, + featureType: FEATURE_TYPE.SIMPLE, + importance: 1, + newFeature: false, + aggregationBy: 'sum', + aggregationOf: visAggregationToFormik(feature), + aggregationQuery: JSON.stringify( + visAggregationQueryToFormik(feature, seriesParams) + ), + }; + }); +} + +export function formikToDetectorName(title) { + const detectorName = + title + '_anomaly_detector_' + Math.floor(100000 + Math.random() * 900000); + detectorName.replace(/[^a-zA-Z0-9-_]/g, '_'); + return detectorName; +} + +const getFeatureNameFromVisParams = (id, seriesParams) => { + let name = find(seriesParams, function (param) { + if (param.data.id === id) { + return true; + } + }); + + return name.data.label.replace(/[^a-zA-Z0-9-_]/g, '_'); +}; + +function visAggregationToFormik(value) { + return [ + { + label: value.params.field.name, + type: 'number', + }, + ]; +} + +function visAggregationQueryToFormik(value, seriesParams) { + return { + [snakeCase(getFeatureNameFromVisParams(value.id, seriesParams))]: { + sum: { field: value.params.field.name }, + }, + }; +} diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.tsx new file mode 100644 index 000000000..cacc501e0 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import AddAnomalyDetector from './AddAnomalyDetector'; + +export default AddAnomalyDetector; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss new file mode 100644 index 000000000..bf457fc55 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss @@ -0,0 +1,58 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +.add-anomaly-detector { + height: 100%; + display: flex; + flex-direction: column; + + .euiFlyoutBody__overflowContent { + height: 100%; + padding-bottom: 0; + } + + .euiFlexItem.add-anomaly-detector__scroll { + overflow-y: auto; + } + + &__flex-group { + height: 100%; + } + + &__modes { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + } +} + +.create-new { + &__vis { + height: 400px; + + &--hidden { + display: none; + } + } + + &__title-and-toggle { + display: flex; + justify-content: space-between; + } + + &__title-icon { + margin-right: 10px; + vertical-align: middle; + } + + .visualization { + padding: 0; + } +} + +.featureButton { + width: 100%; + height: 100%; + min-height: 40px; +} diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.tsx b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.tsx new file mode 100644 index 000000000..17075e27a --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.tsx @@ -0,0 +1,87 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import React from 'react'; +import { + EuiTitle, + EuiSpacer, + EuiButtonIcon, + EuiButtonEmpty, + EuiAccordion, + EuiPanel, +} from '@elastic/eui'; +import './styles.scss'; + +const EnhancedAccordion = ({ + id, + title, + subTitle, + isOpen, + onToggle, + children, + isButton, + iconType, + extraAction, + initialIsOpen, +}) => ( +
+
+ +
+
+ {!isButton && ( + {extraAction}
+ } + forceState={isOpen ? 'open' : 'closed'} + onToggle={onToggle} + initialIsOpen={initialIsOpen} + buttonContent={ +
+ +

{title}

+
+ + {subTitle && ( + <> + + {subTitle} + + )} +
+ } + > + + {children} + + + )} + {isButton && ( + + )} +
+ +); + +export default EnhancedAccordion; diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.tsx b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.tsx new file mode 100644 index 000000000..0b994f5f5 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import EnhancedAccordion from './EnhancedAccordion'; + +export default EnhancedAccordion; diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/styles.scss b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/styles.scss new file mode 100644 index 000000000..4615733d8 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/styles.scss @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +.enhanced-accordion { + &__arrow { + transition: rotate 0.3s; + rotate: 0deg; + + &--open { + rotate: 90deg; + } + + &--hidden { + visibility: hidden; + } + } + + &__title { + padding: 12px 16px; + } + + &__extra { + padding-right: 16px; + } + + &__button { + width: 100%; + height: 100%; + min-height: 50px; + } +} diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.tsx b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.tsx new file mode 100644 index 000000000..ec290cd21 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.tsx @@ -0,0 +1,64 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import React from 'react'; +import { + EuiHorizontalRule, + EuiTitle, + EuiAccordion, + EuiSpacer, + EuiPanel, + EuiTextColor, + EuiText, +} from '@elastic/eui'; +import './styles.scss'; + +function MinimalAccordion({ + id, + title, + subTitle, + children, + isUsingDivider, + extraAction, +}) { + return ( +
+ {isUsingDivider && ( + <> + + + + )} + + +
{title}
+
+ {subTitle && ( + + {subTitle} + + )} + + } + extraAction={ +
{extraAction}
+ } + > + + {children} + +
+
+ ); +} + +export default MinimalAccordion; diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.tsx b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.tsx new file mode 100644 index 000000000..7f222f693 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import MinimalAccordion from './MinimalAccordion'; + +export default MinimalAccordion; diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/styles.scss b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/styles.scss new file mode 100644 index 000000000..3b64d5ee0 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/styles.scss @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +.minimal-accordion { + .euiAccordion__button { + align-items: flex-start; + + &:hover, + &:focus { + text-decoration: none; + + .minimal-accordion__title { + text-decoration: underline; + } + } + } + + &__title { + margin-top: -5px; + font-weight: 400; + } + + &__panel { + padding-left: 28px; + padding-bottom: 0; + } +} diff --git a/public/expressions/constants.ts b/public/expressions/constants.ts index 032d544c5..2082e3491 100644 --- a/public/expressions/constants.ts +++ b/public/expressions/constants.ts @@ -9,3 +9,5 @@ export const ORIGIN_PLUGIN_VIS_LAYER = 'anomalyDetectionDashboards'; export const VIS_LAYER_PLUGIN_TYPE = 'Anomaly Detectors'; export const TYPE_OF_EXPR_VIS_LAYERS = 'vis_layers'; + +export const OVERLAY_ANOMALIES = 'overlay_anomalies'; diff --git a/public/expressions/overlay_anomalies.ts b/public/expressions/overlay_anomalies.ts index ddbc96cfb..5d639297f 100644 --- a/public/expressions/overlay_anomalies.ts +++ b/public/expressions/overlay_anomalies.ts @@ -32,21 +32,21 @@ import { PLUGIN_NAME } from '../utils/constants'; import { NO_PERMISSIONS_KEY_WORD } from '../../server/utils/helpers'; import { ORIGIN_PLUGIN_VIS_LAYER, + OVERLAY_ANOMALIES, TYPE_OF_EXPR_VIS_LAYERS, VIS_LAYER_PLUGIN_TYPE, } from './constants'; type Input = ExprVisLayers; type Output = Promise; +type Name = typeof OVERLAY_ANOMALIES; interface Arguments { detectorId: string; } -const name = 'overlay_anomalies'; - export type OverlayAnomaliesExpressionFunctionDefinition = - ExpressionFunctionDefinition<'overlay_anomalies', Input, Arguments, Output>; + ExpressionFunctionDefinition; // This gets all the needed anomalies for the given detector ID and time range const getAnomalies = async ( @@ -112,9 +112,10 @@ const convertAnomaliesToPointInTimeEventsVisLayer = ( * If there are any errors fetching the anomalies the function will return a VisLayerError in the * VisLayer detailing the error type. */ + export const overlayAnomaliesFunction = (): OverlayAnomaliesExpressionFunctionDefinition => ({ - name, + name: OVERLAY_ANOMALIES, type: TYPE_OF_EXPR_VIS_LAYERS, inputTypes: [TYPE_OF_EXPR_VIS_LAYERS], help: i18n.translate('data.functions.overlay_anomalies.help', { diff --git a/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx b/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx index 17efc289c..f18058580 100644 --- a/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx +++ b/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx @@ -20,6 +20,7 @@ import { EuiButton, EuiFieldText, EuiCheckbox, + EuiButtonIcon, } from '@elastic/eui'; import { Field, FieldProps } from 'formik'; import { @@ -40,6 +41,7 @@ interface FeatureAccordionProps { index: number; feature: any; handleChange(event: React.ChangeEvent): void; + displayMode?: string; } export const FeatureAccordion = (props: FeatureAccordionProps) => { @@ -94,11 +96,25 @@ export const FeatureAccordion = (props: FeatureAccordionProps) => { ); }; - const deleteAction = (onClick: any) => ( - - Delete - - ); + const deleteAction = (onClick: any) => { + if (props.displayMode === 'flyout') { + return ( + + ); + } else { + return ( + + Delete + + ); + } + }; return (