From 334e163e24b4b75eeeb8930abd99fae26506f414 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Thu, 6 Jul 2023 22:16:37 -0700 Subject: [PATCH] Adding a callout on flyout when association limit has been reached (#524) * adding a callout when association limit has been reached Signed-off-by: Amit Galitzky * fixed limit check and ran prettier Signed-off-by: Amit Galitzky --------- Signed-off-by: Amit Galitzky --- .../containers/AssociatedDetectors.tsx | 29 + .../AddAnomalyDetector.tsx | 825 ++++++++++-------- public/plugin.ts | 2 +- public/services.ts | 4 +- 4 files changed, 477 insertions(+), 383 deletions(-) diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx index ac6ea8a8..1cbdbc82 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -14,6 +14,7 @@ import { EuiFlyout, EuiFlexItem, EuiFlexGroup, + EuiCallOut, } from '@elastic/eui'; import { get, isEmpty } from 'lodash'; import '../styles.scss'; @@ -45,6 +46,7 @@ import { getAugmentVisSavedObjs, } from '../../../../../../../src/plugins/vis_augmenter/public'; import { ASSOCIATED_DETECTOR_ACTION } from '../utils/constants'; +import { PLUGIN_AUGMENTATION_MAX_OBJECTS_SETTING } from '../../../../../public/expressions/constants'; interface ConfirmModalState { isOpen: boolean; @@ -74,6 +76,8 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { const [detectorToUnlink, setDetectorToUnlink] = useState( {} as DetectorListItem ); + const [associationLimitReached, setAssociationLimitReached] = + useState(false); const [confirmModalState, setConfirmModalState] = useState( { isOpen: false, @@ -91,6 +95,9 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { const uiSettings = getUISettings(); const notifications = getNotifications(); + let maxAssociatedCount = uiSettings.get( + PLUGIN_AUGMENTATION_MAX_OBJECTS_SETTING + ); useEffect(() => { if ( @@ -137,11 +144,19 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { getAugmentVisSavedObjs(embeddable.vis.id, savedObjectLoader, uiSettings) .then((savedAugmentObjectsArr: any) => { if (savedAugmentObjectsArr != undefined) { + if (maxAssociatedCount <= savedAugmentObjectsArr.length) { + setAssociationLimitReached(true); + } else { + setAssociationLimitReached(false); + } const curSelectedDetectors = getAssociatedDetectors( Object.values(allDetectors), savedAugmentObjectsArr ); setSelectedDetectors(curSelectedDetectors); + maxAssociatedCount = uiSettings.get( + PLUGIN_AUGMENTATION_MAX_OBJECTS_SETTING + ); setIsLoadingFinalDetectors(false); } }) @@ -295,6 +310,19 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { + {associationLimitReached ? ( + + Adding more objects may affect cluster performance and prevent + dashboards from rendering properly. Remove associations before + adding new ones. + + ) : null} {confirmModalState.isOpen ? ( { setMode('existing'); diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx index 6e22edab..106d0dff 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx @@ -51,7 +51,10 @@ import { matchDetector, startDetector, } from '../../../../public/redux/reducers/ad'; -import { EmbeddableRenderer, ErrorEmbeddable } from '../../../../../../src/plugins/embeddable/public'; +import { + EmbeddableRenderer, + ErrorEmbeddable, +} from '../../../../../../src/plugins/embeddable/public'; import './styles.scss'; import EnhancedAccordion from '../EnhancedAccordion'; import MinimalAccordion from '../MinimalAccordion'; @@ -92,7 +95,7 @@ import { getSavedFeatureAnywhereLoader, getUISettings, getUiActions, - getQueryService + getQueryService, } from '../../../../public/services'; import { prettifyErrorMessage } from '../../../../server/utils/helpers'; import { @@ -120,8 +123,8 @@ function AddAnomalyDetector({ const dispatch = useDispatch(); const [queryText, setQueryText] = useState(''); const [generatedEmbeddable, setGeneratedEmbeddable] = useState< - VisualizeEmbeddable | ErrorEmbeddable ->(); + VisualizeEmbeddable | ErrorEmbeddable + >(); useEffect(() => { const getInitialIndices = async () => { @@ -131,7 +134,11 @@ function AddAnomalyDetector({ dispatch(getMappings(embeddable.vis.data.aggs.indexPattern.title)); const createEmbeddable = async () => { - const visEmbeddable = await fetchVisEmbeddable(embeddable.vis.id, getEmbeddable(), getQueryService()); + const visEmbeddable = await fetchVisEmbeddable( + embeddable.vis.id, + getEmbeddable(), + getQueryService() + ); setGeneratedEmbeddable(visEmbeddable); }; @@ -145,6 +152,8 @@ function AddAnomalyDetector({ const [intervalValue, setIntervalalue] = useState(10); const [delayValue, setDelayValue] = useState(1); const [enabled, setEnabled] = useState(false); + const [associationLimitReached, setAssociationLimitReached] = + useState(false); const title = embeddable.getTitle(); const onAccordionToggle = (key) => { @@ -229,6 +238,67 @@ function AddAnomalyDetector({ const savedObjectLoader: SavedAugmentVisLoader = getSavedFeatureAnywhereLoader(); + let maxAssociatedCount = uiSettings.get( + PLUGIN_AUGMENTATION_MAX_OBJECTS_SETTING + ); + + useEffect(async () => { + // Gets all augmented saved objects + await savedObjectLoader.findAll().then(async (resp) => { + if (resp !== undefined) { + const savedAugmentObjects = get(resp, 'hits', []); + // gets all the saved object for this visualization + const savedObjectsForThisVisualization = savedAugmentObjects.filter( + (savedObj) => get(savedObj, 'visId', '') === embeddable.vis.id + ); + if (maxAssociatedCount <= savedObjectsForThisVisualization.length) { + setAssociationLimitReached(true); + } else { + setAssociationLimitReached(false); + } + } + }); + }, []); + + const getEmbeddableSection = () => { + return ( + <> + +

+ 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)} + /> +
+
+ + +
+ + ); + }; + const getAugmentVisSavedObject = (detectorId: string) => { const fn = { type: VisLayerTypes.PointInTimeEvents, @@ -490,416 +560,410 @@ function AddAnomalyDetector({ -
- - - 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 === FLYOUT_MODES.existing && ( - - )} - {mode === FLYOUT_MODES.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)} + {associationLimitReached ? ( +
+ + Adding more objects may affect cluster performance and + prevent dashboards from rendering properly. Remove + associations before adding new ones. + + {getEmbeddableSection()} +
+ ) : ( +
+ + + 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), + }} /> -
-
- - -
- - -

Detector details

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

- Detector interval: {intervalValue} minute(s); Window - delay: {delayValue} minute(s) -

-
- } - > - - {({ field, form }: FieldProps) => ( - - + + {mode === FLYOUT_MODES.existing && ( + + )} + {mode === FLYOUT_MODES.create && ( +
+ {getEmbeddableSection()} + + +

Detector details

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

+ Detector interval: {intervalValue} minute(s); + Window delay: {delayValue} minute(s) +

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

minute(s)

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

minute(s)

-
+ + + + onIntervalChange(e, field) + } + /> + + + +

minute(s)

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

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

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

intervals

+

minute(s)

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

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

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

intervals

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

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

+
+
+
+ + + +

Model Features

+
+ + + onAccordionToggle('modelFeatures')} > - -

- 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); + + {({ + 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()); }} - 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. -

-
-
- ); - }} -
-
- -
- )} -
+ > + Add another feature + + + + +

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

+
+ + ); + }} + + + +
+ )} +
+ )}
@@ -919,6 +983,7 @@ function AddAnomalyDetector({ ) : ( { diff --git a/public/plugin.ts b/public/plugin.ts index 8d8f6db2..25ea0ebf 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -34,7 +34,7 @@ import { setSavedFeatureAnywhereLoader, setUiActions, setUISettings, - setQueryService + setQueryService, } from './services'; import { AnomalyDetectionOpenSearchDashboardsPluginStart } from 'public'; import { diff --git a/public/services.ts b/public/services.ts index 0ee47f2b..ef899307 100644 --- a/public/services.ts +++ b/public/services.ts @@ -36,8 +36,8 @@ export const [getUiActions, setUiActions] = export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); -export const [getQueryService, setQueryService] = -createGetterSetter('Query'); +export const [getQueryService, setQueryService] = + createGetterSetter('Query'); // This is primarily used for mocking this module and each of its fns in tests. export default {