From 2b74e3f952c5a412b9b55c781fd388955257138c Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Wed, 17 May 2023 03:14:36 +0300 Subject: [PATCH 1/7] Adding associate existing Signed-off-by: Amit Galitzky --- .../AnywhereParentFlyout.tsx | 12 +- .../AnywhereParentFlyout/index.ts | 8 + .../containers/AssociatedDetectors.tsx | 7 +- .../containers/AssociateExisting.tsx | 282 ++++++++++++++++++ .../AssociateExisting/index.ts | 6 + .../CreateAnomalyDetector/utils/helpers.tsx | 4 + .../DocumentationTitle/index.ts | 8 + .../EnhancedAccordion/EnhancedAccordion.js | 83 ++++++ .../EnhancedAccordion.test.js | 15 + .../EnhancedAccordion/index.js | 8 + .../MinimalAccordion/MinimalAccordion.js | 60 ++++ .../MinimalAccordion/MinimalAccordion.test.js | 15 + .../MinimalAccordion/index.js | 8 + public/models/interfaces.ts | 1 + public/plugin.ts | 2 +- public/utils/contextMenu/getActions.tsx | 2 +- 16 files changed, 509 insertions(+), 12 deletions(-) create mode 100644 public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.ts create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/containers/AssociateExisting.tsx create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/index.ts create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/utils/helpers.tsx create mode 100644 public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.ts create mode 100644 public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js create mode 100644 public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js create mode 100644 public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js create mode 100644 public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js create mode 100644 public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js create mode 100644 public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx index d2e624ca..967814ef 100644 --- a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx +++ b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx @@ -7,6 +7,7 @@ import { get } from 'lodash'; import AssociatedDetectors from '../AssociatedDetectors/containers/AssociatedDetectors'; import { getEmbeddable } from '../../../../public/services'; import AddAnomalyDetector from '../CreateAnomalyDetector/AddAnomalyDetector'; +import { DetectorListItem } from '../../../../public/models/interfaces'; const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { const embeddable = getEmbeddable().getEmbeddableFactory; @@ -15,12 +16,15 @@ const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { ]; const [mode, setMode] = useState(startingFlyout); - const [selectedDetectorId, setSelectedDetectorId] = useState(); + const [selectedDetector, setSelectedDetector] = useState( + {} as DetectorListItem + ); const AnywhereFlyout = { create: AddAnomalyDetector, associated: AssociatedDetectors, - }[mode]; + existing: AddAnomalyDetector, + }[mode]; return ( { setMode, mode, indices, - selectedDetectorId, - setSelectedDetectorId, + selectedDetector, + setSelectedDetector, }} /> ); diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.ts b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.ts new file mode 100644 index 00000000..591d4b6d --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import AnywhereParentFlyout from './AnywhereParentFlyout'; + +export default AnywhereParentFlyout; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx index c0b4f64f..d5f755da 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -239,11 +239,6 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { dispatch(getDetectorList(GET_ALL_DETECTORS_QUERY_PARAMS)); }; - // TODO: this part is incomplete because it is pending on a different PR that will have all the associate existing changes - const openAssociateDetectorFlyout = async () => { - console.log('inside create anomaly detector'); - }; - const handleUnlinkDetectorAction = (detector: DetectorListItem) => { setDetectorToUnlink(detector); setConfirmModalState({ @@ -326,7 +321,7 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { fill iconType="link" onClick={() => { - openAssociateDetectorFlyout(); + setMode('existing'); }} > Associate a detector diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/containers/AssociateExisting.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/containers/AssociateExisting.tsx new file mode 100644 index 00000000..d1aa5e59 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/containers/AssociateExisting.tsx @@ -0,0 +1,282 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect, useMemo, useState } from 'react'; +import { + EuiTitle, + EuiSpacer, + EuiIcon, + EuiText, + EuiComboBox, + EuiLoadingSpinner, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiHorizontalRule, +} from '@elastic/eui'; +import { useDispatch, useSelector } from 'react-redux'; +import { get } from 'lodash'; +import { CoreServicesContext } from '../../../../../components/CoreServices/CoreServices'; +import { CoreStart } from '../../../../../../../../src/core/public'; +import { AppState } from '../../../../../redux/reducers'; +import { DetectorListItem } from '../../../../../models/interfaces'; +import { + GET_ALL_DETECTORS_QUERY_PARAMS, + SINGLE_DETECTOR_NOT_FOUND_MSG, +} from '../../../../../pages/utils/constants'; +import { + NO_PERMISSIONS_KEY_WORD, + prettifyErrorMessage, +} from '../../../../../../server/utils/helpers'; +import { SavedObjectLoader } from '../../../../../../../../src/plugins/saved_objects/public'; +import { getDetectorList } from '../../../../../redux/reducers/ad'; +import { getSavedFeatureAnywhereLoader } from '../../../../../services'; +import { ISavedAugmentVis } from '../../../../../../../../src/plugins/vis_augmenter/public'; +import { stateToColorMap } from '../../../../../pages/utils/constants'; +import { + BASE_DOCS_LINK, + PLUGIN_NAME, +} from '../../../../../../public/utils/constants'; +import { renderTime } from '../../../../../../public/pages/DetectorsList/utils/tableUtils'; + +interface AssociateExistingProps { + embeddableVisId: string; + selectedDetector: DetectorListItem | undefined; + setSelectedDetector(detector: DetectorListItem | undefined): void; +} + +export function AssociateExisting( + associateExistingProps: AssociateExistingProps +) { + const core = React.useContext(CoreServicesContext) as CoreStart; + const dispatch = useDispatch(); + const allDetectors = useSelector((state: AppState) => state.ad.detectorList); + const isRequestingFromES = useSelector( + (state: AppState) => state.ad.requesting + ); + const [isLoadingFinalDetectors, setIsLoadingFinalDetectors] = + useState(true); + const isLoading = isRequestingFromES || isLoadingFinalDetectors; + const errorGettingDetectors = useSelector( + (state: AppState) => state.ad.errorMessage + ); + const [ + existingDetectorsAvailableToAssociate, + setExistingDetectorsAvailableToAssociate, + ] = useState([] as DetectorListItem[]); + + // Establish savedObjectLoader for all operations on vis augmented saved objects + const savedObjectLoader: SavedObjectLoader = getSavedFeatureAnywhereLoader(); + + useEffect(() => { + if ( + errorGettingDetectors && + !errorGettingDetectors.includes(SINGLE_DETECTOR_NOT_FOUND_MSG) + ) { + console.error(errorGettingDetectors); + core.notifications.toasts.addDanger( + typeof errorGettingDetectors === 'string' && + errorGettingDetectors.includes(NO_PERMISSIONS_KEY_WORD) + ? prettifyErrorMessage(errorGettingDetectors) + : 'Unable to get all detectors' + ); + setIsLoadingFinalDetectors(false); + } + }, [errorGettingDetectors]); + + // Handle all changes in the assoicated detectors such as unlinking or new detectors associated + useEffect(() => { + // Gets all augmented saved objects + savedObjectLoader.findAll().then((resp: any) => { + if (resp != undefined) { + const savedAugmentObjectsArr: ISavedAugmentVis[] = get( + resp, + 'hits', + [] + ); + const curDetectorsToDisplayOnList = + getExistingDetectorsAvalableToAssociate( + Object.values(allDetectors), + savedAugmentObjectsArr + ); + setExistingDetectorsAvailableToAssociate(curDetectorsToDisplayOnList); + setIsLoadingFinalDetectors(false); + } + }); + }, [allDetectors]); + + // cross checks all the detectors that exist with all the savedAugment Objects to only display ones + // that are associated to the current visualization + const getExistingDetectorsAvalableToAssociate = ( + detectors: DetectorListItem[], + savedAugmentObjects: ISavedAugmentVis[] + ) => { + // Filter all savedAugmentObjects that aren't linked to the specific visualization + const savedAugmentForThisVisualization: ISavedAugmentVis[] = + savedAugmentObjects.filter( + (savedObj) => + get(savedObj, 'visId', '') === associateExistingProps.embeddableVisId + ); + + // Map all detector IDs for all the found augmented vis objects + const savedAugmentDetectorsSet = new Set( + savedAugmentForThisVisualization.map((savedObject) => + get(savedObject, 'pluginResourceId', '') + ) + ); + + // detectors here is all detectors + // for each detector in all detectors return that detector if that detector ID isnt in the set + // filter out any detectors that aren't on the set of detectors IDs from the augmented vis objects. + const detectorsToDisplay = detectors.filter((detector) => { + if ( + !savedAugmentDetectorsSet.has(detector.id) && + detector.detectorType === 'SINGLE_ENTITY' + ) { + return detector; + } + }); + return detectorsToDisplay; + }; + + useEffect(() => { + getDetectors(); + }, []); + + const getDetectors = async () => { + dispatch(getDetectorList(GET_ALL_DETECTORS_QUERY_PARAMS)); + }; + + const selectedOptions = useMemo(() => { + if ( + !existingDetectorsAvailableToAssociate || + !associateExistingProps.selectedDetector + ) { + return []; + } + + const detector = (existingDetectorsAvailableToAssociate || []).find( + (detector) => + detector.id === get(associateExistingProps.selectedDetector, 'id', '') + ); + return detector ? [{ label: detector.name }] : []; + }, [ + associateExistingProps.selectedDetector, + existingDetectorsAvailableToAssociate, + ]); + + const detector = useMemo( + () => + existingDetectorsAvailableToAssociate && + associateExistingProps.selectedDetector && + existingDetectorsAvailableToAssociate.find( + (detector) => + detector.id === get(associateExistingProps.selectedDetector, 'id', '') + ), + [ + associateExistingProps.selectedDetector, + existingDetectorsAvailableToAssociate, + ] + ); + const options = useMemo(() => { + if (!existingDetectorsAvailableToAssociate) { + return []; + } + + return existingDetectorsAvailableToAssociate.map((detector) => ({ + label: detector.name, + })); + }, [existingDetectorsAvailableToAssociate]); + + return ( +
+ +

+ View existing anomaly detectors across your system and add the + detector(s) to a dashboard and visualization.{' '} + + Learn more + +

+
+ + +

Select detector to associate

+
+ + + Eligible detectors don't include high-cardinality detectors. + + {!existingDetectorsAvailableToAssociate && } + {existingDetectorsAvailableToAssociate && ( + { + let detector = {} as DetectorListItem | undefined; + + if (selectedOptions && selectedOptions.length) { + const match = existingDetectorsAvailableToAssociate.find( + (detector) => detector.name === selectedOptions[0].label + ); + detector = match; + } + associateExistingProps.setSelectedDetector(detector); + }} + aria-label="Select an anomaly detector to associate" + isClearable + singleSelection + placeholder="Search for an anomaly detector" + /> + )} + + {detector && ( + <> + + + +

{detector.name}

+
+ + + {renderTime(detector.enabledTime)} + +
+ + + View detector page + + +
+ +
    + {[ + ['Indexes', (detector) => detector.indices], + [ + 'Anomalies last 24 hours', + (detector) => detector.totalAnomalies, + ], + [ + 'Last real-time occurence', + (detector) => renderTime(detector.lastActiveAnomaly), + ], + ].map(([label, getValue]) => ( +
  • + + {label}: {getValue(detector)} + +
  • + ))} +
+ + )} +
+ ); +} + +export default AssociateExisting; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/index.ts b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/index.ts new file mode 100644 index 00000000..90aa3ae3 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { AssociateExisting } from './containers/AssociateExisting'; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/utils/helpers.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/utils/helpers.tsx new file mode 100644 index 00000000..a850c169 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/utils/helpers.tsx @@ -0,0 +1,4 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ diff --git a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.ts b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.ts new file mode 100644 index 00000000..e9f1bd89 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import DocumentationTitle from './containers/DocumentationTitle'; + +export default DocumentationTitle; diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js new file mode 100644 index 00000000..61752b3c --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js @@ -0,0 +1,83 @@ +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/EnhancedAccordion.test.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js new file mode 100644 index 00000000..07cce2cb --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import EnhancedAccordion from './EnhancedAccordion'; + +describe('EnhancedAccordion', () => { + test('renders', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js new file mode 100644 index 00000000..0b994f5f --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js @@ -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/MinimalAccordion/MinimalAccordion.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js new file mode 100644 index 00000000..530065ca --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js @@ -0,0 +1,60 @@ +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/MinimalAccordion.test.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js new file mode 100644 index 00000000..3da83605 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import MinimalAccordion from './MinimalAccordion'; + +describe('MinimalAccordion', () => { + test('renders', () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js new file mode 100644 index 00000000..7f222f69 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import MinimalAccordion from './MinimalAccordion'; + +export default MinimalAccordion; diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts index f8fbc248..eff5ead5 100644 --- a/public/models/interfaces.ts +++ b/public/models/interfaces.ts @@ -217,6 +217,7 @@ export type DetectorListItem = { lastActiveAnomaly: number; lastUpdateTime: number; enabledTime?: number; + detectorType?: string; }; export type EntityData = { diff --git a/public/plugin.ts b/public/plugin.ts index 0c45e15e..c048eb0e 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -99,4 +99,4 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin setNotifications(core.notifications); return {}; } -} +} \ No newline at end of file diff --git a/public/utils/contextMenu/getActions.tsx b/public/utils/contextMenu/getActions.tsx index 0c1302e4..5f977d14 100644 --- a/public/utils/contextMenu/getActions.tsx +++ b/public/utils/contextMenu/getActions.tsx @@ -83,4 +83,4 @@ export const getActions = () => { }, }, ].map((options) => createADAction({ ...options, grouping })); -}; +}; \ No newline at end of file From bfb9c697716d8d4b56fb0ffd53585702768060c9 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Mon, 22 May 2023 16:52:17 -0700 Subject: [PATCH 2/7] removed usememo, addressed other comments Signed-off-by: Amit Galitzky --- .../AnywhereParentFlyout.tsx | 9 ++-- .../AddAnomalyDetector.tsx | 53 +++++++++++++++++++ .../containers/AssociateExisting.tsx | 28 ++++------ 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx index 967814ef..8e77299d 100644 --- a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx +++ b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx @@ -6,8 +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'; -import { DetectorListItem } from '../../../../public/models/interfaces'; +import AddAnomalyDetector from '../CreateAnomalyDetector'; const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { const embeddable = getEmbeddable().getEmbeddableFactory; @@ -16,15 +15,13 @@ const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { ]; const [mode, setMode] = useState(startingFlyout); - const [selectedDetector, setSelectedDetector] = useState( - {} as DetectorListItem - ); + const [selectedDetector, setSelectedDetector] = useState(undefined); const AnywhereFlyout = { create: AddAnomalyDetector, associated: AssociatedDetectors, existing: AddAnomalyDetector, - }[mode]; + }[mode]; return ( { + enum VisLayerTypes { + PointInTimeEvents = 'PointInTimeEvents', + } + + const fn = { + type: VisLayerTypes.PointInTimeEvents, + name: 'overlay_anomalies', + args: { + detectorId: detector.id, + }, + } as VisLayerExpressionFn; + + const pluginResource = { + type: 'anomay-detection', + id: detector.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; + + createAugmentVisSavedObject(savedObjectToCreate) + .then((savedObject: any) => { + savedObject + .save({}) + .then((response: any) => { + notifications.toasts.addSuccess({ + title: `The ${detector.name} is associated with the ${title} visualization`, + text: "The detector's anomalies do not appear on the visualization. Refresh your dashboard to update the visualization", + }); + closeFlyout(); + }) + .catch((error) => { + notifications.toasts.addDanger( + prettifyErrorMessage( + `Error associating selected detector: ${error}` + ) + ); + }); + }) + .catch((error) => { + notifications.toasts.addDanger( + prettifyErrorMessage(`Error associating selected detector: ${error}`) + ); + }); + }; + return (
{ @@ -124,7 +124,7 @@ export function AssociateExisting( // Map all detector IDs for all the found augmented vis objects const savedAugmentDetectorsSet = new Set( savedAugmentForThisVisualization.map((savedObject) => - get(savedObject, 'pluginResourceId', '') + get(savedObject, 'pluginResource.id', '') ) ); @@ -168,19 +168,8 @@ export function AssociateExisting( existingDetectorsAvailableToAssociate, ]); - const detector = useMemo( - () => - existingDetectorsAvailableToAssociate && - associateExistingProps.selectedDetector && - existingDetectorsAvailableToAssociate.find( - (detector) => - detector.id === get(associateExistingProps.selectedDetector, 'id', '') - ), - [ - associateExistingProps.selectedDetector, - existingDetectorsAvailableToAssociate, - ] - ); + const detector = associateExistingProps.selectedDetector; + const options = useMemo(() => { if (!existingDetectorsAvailableToAssociate) { return []; @@ -210,8 +199,7 @@ export function AssociateExisting( Eligible detectors don't include high-cardinality detectors. - {!existingDetectorsAvailableToAssociate && } - {existingDetectorsAvailableToAssociate && ( + {existingDetectorsAvailableToAssociate ? ( + ) : ( + )} {detector && ( @@ -256,7 +246,7 @@ export function AssociateExisting(
    {[ - ['Indexes', (detector) => detector.indices], + ['Indices', (detector) => detector.indices], [ 'Anomalies last 24 hours', (detector) => detector.totalAnomalies, From 7e24cd978c64464c18cf6437155e2e26cd15bd3b Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Wed, 24 May 2023 15:20:29 -0700 Subject: [PATCH 3/7] merge cleanup Signed-off-by: Amit Galitzky --- .../AnywhereParentFlyout/index.ts | 8 -- .../CreateAnomalyDetector/utils/helpers.tsx | 4 - .../DocumentationTitle/index.ts | 8 -- .../EnhancedAccordion/EnhancedAccordion.js | 83 ------------------- .../EnhancedAccordion.test.js | 15 ---- .../EnhancedAccordion/index.js | 8 -- .../MinimalAccordion/MinimalAccordion.js | 60 -------------- .../MinimalAccordion/MinimalAccordion.test.js | 15 ---- .../MinimalAccordion/index.js | 8 -- 9 files changed, 209 deletions(-) delete mode 100644 public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.ts delete mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/utils/helpers.tsx delete mode 100644 public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.ts delete mode 100644 public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js delete mode 100644 public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js delete mode 100644 public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js delete mode 100644 public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js delete mode 100644 public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js delete mode 100644 public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.ts b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.ts deleted file mode 100644 index 591d4b6d..00000000 --- a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import AnywhereParentFlyout from './AnywhereParentFlyout'; - -export default AnywhereParentFlyout; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/utils/helpers.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/utils/helpers.tsx deleted file mode 100644 index a850c169..00000000 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/utils/helpers.tsx +++ /dev/null @@ -1,4 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ diff --git a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.ts b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.ts deleted file mode 100644 index e9f1bd89..00000000 --- a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import DocumentationTitle from './containers/DocumentationTitle'; - -export default DocumentationTitle; diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js deleted file mode 100644 index 61752b3c..00000000 --- a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js +++ /dev/null @@ -1,83 +0,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/EnhancedAccordion.test.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js deleted file mode 100644 index 07cce2cb..00000000 --- a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import EnhancedAccordion from './EnhancedAccordion'; - -describe('EnhancedAccordion', () => { - test('renders', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js deleted file mode 100644 index 0b994f5f..00000000 --- a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import EnhancedAccordion from './EnhancedAccordion'; - -export default EnhancedAccordion; diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js deleted file mode 100644 index 530065ca..00000000 --- a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js +++ /dev/null @@ -1,60 +0,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/MinimalAccordion.test.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js deleted file mode 100644 index 3da83605..00000000 --- a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import MinimalAccordion from './MinimalAccordion'; - -describe('MinimalAccordion', () => { - test('renders', () => { - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js deleted file mode 100644 index 7f222f69..00000000 --- a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import MinimalAccordion from './MinimalAccordion'; - -export default MinimalAccordion; From bcaa084cd4ad4594033417aec00718cea91e8ec5 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Wed, 24 May 2023 18:31:18 -0700 Subject: [PATCH 4/7] added integration to call on alerting Signed-off-by: Amit Galitzky --- .../AddAnomalyDetector.tsx | 296 ++++++++++++------ .../containers/AssociateExisting.tsx | 2 +- public/expressions/constants.ts | 2 + public/expressions/overlay_anomalies.ts | 2 + public/plugin.ts | 30 +- public/services.ts | 4 + public/utils/contextMenu/getActions.tsx | 2 +- public/utils/utils.tsx | 3 + 8 files changed, 231 insertions(+), 110 deletions(-) diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx index c3c0c1f8..03828895 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx @@ -80,9 +80,11 @@ import { FeatureAccordion } from '../../../../public/pages/ConfigureModel/compon import { AD_DOCS_LINK, AD_HIGH_CARDINALITY_LINK, + ALERTING_PLUGIN_NAME, + DEFAULT_SHINGLE_SIZE, MAX_FEATURE_NUM, } from '../../../../public/utils/constants'; -import { getNotifications } from '../../../../public/services'; +import { getNotifications, getUiActions } from '../../../../public/services'; import { prettifyErrorMessage } from '../../../../server/utils/helpers'; import { ORIGIN_PLUGIN_VIS_LAYER, @@ -90,9 +92,22 @@ import { VIS_LAYER_PLUGIN_TYPE, } from '../../../../public/expressions/constants'; import { formikToDetectorName, visFeatureListToFormik } from './helpers'; - -function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) { +import { AssociateExisting } from './AssociateExisting'; +import { mountReactNode } from '../../../../../../src/core/public/utils'; +import { CoreServicesContext } from '../../../../public/components/CoreServices/CoreServices'; +import { CoreStart } from '../../../../../../src/core/public'; + +function AddAnomalyDetector({ + embeddable, + closeFlyout, + mode, + setMode, + selectedDetector, + setSelectedDetector, +}) { const dispatch = useDispatch(); + const core = React.useContext(CoreServicesContext) as CoreStart; + const [queryText, setQueryText] = useState(''); useEffect(() => { const getInitialIndices = async () => { @@ -148,21 +163,43 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) { } }; - const handleSubmit = (formikProps) => { + const getAugmentVisSavedObject = (detectorId: string) => { + const fn = { + type: VisLayerTypes.PointInTimeEvents, + name: OVERLAY_ANOMALIES, + args: { + detectorId: detectorId, + }, + } as VisLayerExpressionFn; + + const pluginResource = { + type: VIS_LAYER_PLUGIN_TYPE, + id: detectorId, + } as ISavedPluginResource; + + return { + title: embeddable.vis.title, + originPlugin: ORIGIN_PLUGIN_VIS_LAYER, + pluginResource: pluginResource, + visId: embeddable.vis.id, + visLayerExpressionFn: fn, + } as ISavedAugmentVis; + }; + + //DONE 1. if detector is created succesfully, started succesfully and associated succesfully and alerting exists -> show end message with alerting button + //DONE 2. If detector is created succesfully, started succesfully and associated succesfully and alerting doesn't exist -> show end message with OUT alerting button + //DONE 3. If detector is created succesfully, started succesfully and fails association -> show one toast with detector created and started succesfully and one toast with failed association + //DONE 4. If detector is created succesfully, fails starting and fails association -> show one toast with detector created succesfully, one toast with failed association + //DONE 5. If detector is created successfully, fails starting and fails associating -> show one toast with detector created succesfully, one toast with fail starting, one toast with failed association + //DONE 6. If detector fails creating -> show one toast with detector failed creating + const handleSubmit = async (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` - ); - }) + .then((startDetectorResponse) => {}) .catch((err: any) => { notifications.toasts.addDanger( prettifyErrorMessage( @@ -174,34 +211,54 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) { ); }); - 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({}); + const detectorId = response.response.id; + + const augmentVisSavedObjectToCreate: ISavedAugmentVis = + getAugmentVisSavedObject(detectorId); + + createAugmentVisSavedObject(augmentVisSavedObjectToCreate) + .then((savedObject: any) => { + savedObject + .save({}) + .then((response: any) => { + const shingleSize = get( + formikProps.values, + 'shingleSize', + DEFAULT_SHINGLE_SIZE + ); + const detectorId = get(savedObject, 'pluginResource.id', ''); + notifications.toasts.addSuccess({ + title: `The ${formikProps.values.name} is associated with the ${title} visualization`, + text: mountReactNode( + getEverythingSuccesfulButton(detectorId, shingleSize) + ), + }); + closeFlyout(); + }) + .catch((error) => { + console.error( + `Error associating selected detector4: ${error}` + ); + notifications.toasts.addDanger( + prettifyErrorMessage( + `Error associating selected detector in save process: ${error}` + ) + ); + notifications.toasts.addSuccess( + `Detector created: ${formikProps.values.name}` + ); + }); + }) + .catch((error) => { + notifications.toasts.addDanger( + prettifyErrorMessage( + `Error associating selected detector3 in crate process: ${error}` + ) + ); + notifications.toasts.addSuccess( + `Detector created: ${formikProps.values.name}` + ); + }); }) .catch((err: any) => { dispatch(getDetectorCount()).then((response: any) => { @@ -231,6 +288,74 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) { } }; + const getEverythingSuccesfulButton = (detectorId, shingleSize) => { + return ( + +

+ `Attempting to initialize the detector with historical data. This + initializing process takes approximately 1 minute if you have data in + each of the last ${32 + shingleSize} consecutive intervals.` +

+ {alertingExists(detectorId) ? ( + + openAlerting(detectorId)}> + Set Up Alerts + {' '} +

set up alerts

+
+ ) : null} +
+ ); + }; + + const alertingExists = (detectorId) => { + try { + const uiActionService = getUiActions(); + uiActionService.getTrigger('ALERTING_TRIGGER_AD_ID'); + return true; + } catch (e) { + console.error('No allerting trigger exists', e); + return false; + } + }; + + const openAlerting = (detectorId) => { + const uiActionService = getUiActions(); + uiActionService + .getTrigger('ALERTING_TRIGGER_AD_ID') + .exec({ embeddable, detectorId }); + }; + + const handleAssociate = async (detector: DetectorListItem) => { + const augmentVisSavedObjectToCreate: ISavedAugmentVis = + getAugmentVisSavedObject(detector.id); + + createAugmentVisSavedObject(augmentVisSavedObjectToCreate) + .then((savedObject: any) => { + savedObject + .save({}) + .then((response: any) => { + notifications.toasts.addSuccess({ + title: `The ${detector.name} is associated with the ${title} visualization`, + text: "The detector's anomalies do not appear on the visualization. Refresh your dashboard to update the visualization", + }); + closeFlyout(); + }) + .catch((error) => { + notifications.toasts.addDanger( + prettifyErrorMessage( + `Error associating selected detector: ${error}` + ) + ); + }); + }) + .catch((error) => { + notifications.toasts.addDanger( + prettifyErrorMessage(`Error associating selected detector: ${error}`) + ); + }); + }; + const validateVisDetectorName = async (detectorName: string) => { if (isEmpty(detectorName)) { return 'Detector name cannot be empty'; @@ -271,59 +396,6 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) { historical: false, }; - const handleAssociate = async (detector: DetectorListItem) => { - enum VisLayerTypes { - PointInTimeEvents = 'PointInTimeEvents', - } - - const fn = { - type: VisLayerTypes.PointInTimeEvents, - name: 'overlay_anomalies', - args: { - detectorId: detector.id, - }, - } as VisLayerExpressionFn; - - const pluginResource = { - type: 'anomay-detection', - id: detector.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; - - createAugmentVisSavedObject(savedObjectToCreate) - .then((savedObject: any) => { - savedObject - .save({}) - .then((response: any) => { - notifications.toasts.addSuccess({ - title: `The ${detector.name} is associated with the ${title} visualization`, - text: "The detector's anomalies do not appear on the visualization. Refresh your dashboard to update the visualization", - }); - closeFlyout(); - }) - .catch((error) => { - notifications.toasts.addDanger( - prettifyErrorMessage( - `Error associating selected detector: ${error}` - ) - ); - }); - }) - .catch((error) => { - notifications.toasts.addDanger( - prettifyErrorMessage(`Error associating selected detector: ${error}`) - ); - }); - }; - return (
+ {mode === 'existing' && ( + + )} {mode === 'create' && (
@@ -748,16 +827,27 @@ function AddAnomalyDetector({ embeddable, closeFlyout, mode, setMode }) { Cancel - { - handleValidationAndSubmit(formikProps); - }} - > - Create Detector - + {mode === 'existing' ? ( + handleAssociate(selectedDetector)} + > + associate detector + + ) : ( + { + handleValidationAndSubmit(formikProps); + }} + > + create detector + + )} diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/containers/AssociateExisting.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/containers/AssociateExisting.tsx index a4a2542a..c511b50f 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/containers/AssociateExisting.tsx +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AssociateExisting/containers/AssociateExisting.tsx @@ -252,7 +252,7 @@ export function AssociateExisting( (detector) => detector.totalAnomalies, ], [ - 'Last real-time occurence', + 'Last real-time occurrence', (detector) => renderTime(detector.lastActiveAnomaly), ], ].map(([label, getValue]) => ( diff --git a/public/expressions/constants.ts b/public/expressions/constants.ts index 2082e349..9fb05ed4 100644 --- a/public/expressions/constants.ts +++ b/public/expressions/constants.ts @@ -11,3 +11,5 @@ export const VIS_LAYER_PLUGIN_TYPE = 'Anomaly Detectors'; export const TYPE_OF_EXPR_VIS_LAYERS = 'vis_layers'; export const OVERLAY_ANOMALIES = 'overlay_anomalies'; + +export const PLUGIN_EVENT_TYPE = 'anomalies'; diff --git a/public/expressions/overlay_anomalies.ts b/public/expressions/overlay_anomalies.ts index 5d639297..aa364a48 100644 --- a/public/expressions/overlay_anomalies.ts +++ b/public/expressions/overlay_anomalies.ts @@ -33,6 +33,7 @@ import { NO_PERMISSIONS_KEY_WORD } from '../../server/utils/helpers'; import { ORIGIN_PLUGIN_VIS_LAYER, OVERLAY_ANOMALIES, + PLUGIN_EVENT_TYPE, TYPE_OF_EXPR_VIS_LAYERS, VIS_LAYER_PLUGIN_TYPE, } from './constants'; @@ -204,6 +205,7 @@ export const overlayAnomaliesFunction = pluginResource: ADPluginResource, events: [], error: visLayerError, + pluginEventType: PLUGIN_EVENT_TYPE, } as PointInTimeEventsVisLayer; return { type: TYPE_OF_EXPR_VIS_LAYERS, diff --git a/public/plugin.ts b/public/plugin.ts index c048eb0e..29c2b859 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -13,6 +13,7 @@ import { AppMountParameters, CoreSetup, CoreStart, + NotificationsSetup, NotificationsStart, Plugin, } from '../../../src/core/public'; @@ -30,11 +31,18 @@ import { setEmbeddable, setNotifications, setOverlays, - setSavedFeatureAnywhereLoader + setSavedFeatureAnywhereLoader, + setUiActions, } from './services'; import { AnomalyDetectionOpenSearchDashboardsPluginStart } from 'public'; -import { VisAugmenterStart } from '../../../src/plugins/vis_augmenter/public'; - +import { + VisAugmenterSetup, + VisAugmenterStart, +} from '../../../src/plugins/vis_augmenter/public'; +import { + UiActionsSetup, + UiActionsStart, +} from '../../../src/plugins/ui_actions/public'; declare module '../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -42,14 +50,19 @@ declare module '../../../src/plugins/ui_actions/public' { } } +//TODO: there is currently no savedAugmentVisLoader in VisAugmentSetup interface, this needs to be fixed export interface AnomalyDetectionSetupDeps { embeddable: EmbeddableSetup; + notifications: NotificationsSetup; + visAugmenter: VisAugmenterSetup; + //uiActions: UiActionsSetup; } export interface AnomalyDetectionStartDeps { embeddable: EmbeddableStart; notifications: NotificationsStart; visAugmenter: VisAugmenterStart; + uiActions: UiActionsStart; } export class AnomalyDetectionOpenSearchDashboardsPlugin @@ -72,6 +85,12 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin }, }); + // // set embeddable plugin for feature anywhere create flyout + // setEmbeddable(embeddable); + + // // set vis argumenter loader for feature anywhere associated flyout + // setSavedFeatureAnywhereLoader(visAugmenter.savedAugmentVisLoader); + // Set the HTTP client so it can be pulled into expression fns to make // direct server-side calls setClient(core.http); @@ -91,12 +110,13 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin public start( core: CoreStart, - { embeddable, visAugmenter }: AnomalyDetectionStartDeps + { embeddable, visAugmenter, uiActions }: AnomalyDetectionStartDeps ): AnomalyDetectionOpenSearchDashboardsPluginStart { setEmbeddable(embeddable); setOverlays(core.overlays); setSavedFeatureAnywhereLoader(visAugmenter.savedAugmentVisLoader); setNotifications(core.notifications); + setUiActions(uiActions); return {}; } -} \ No newline at end of file +} diff --git a/public/services.ts b/public/services.ts index 1908f443..cfdf7be8 100644 --- a/public/services.ts +++ b/public/services.ts @@ -11,6 +11,7 @@ import { import { EmbeddableStart } from '../../../src/plugins/embeddable/public'; import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/public'; import { SavedObjectLoader } from '../../../src/plugins/saved_objects/public'; +import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; export const [getSavedFeatureAnywhereLoader, setSavedFeatureAnywhereLoader] = createGetterSetter('savedFeatureAnywhereLoader'); @@ -26,3 +27,6 @@ export const [getOverlays, setOverlays] = export const [getNotifications, setNotifications] = createGetterSetter('Notifications'); + +export const [getUiActions, setUiActions] = + createGetterSetter('UIActions'); diff --git a/public/utils/contextMenu/getActions.tsx b/public/utils/contextMenu/getActions.tsx index 5f977d14..0c1302e4 100644 --- a/public/utils/contextMenu/getActions.tsx +++ b/public/utils/contextMenu/getActions.tsx @@ -83,4 +83,4 @@ export const getActions = () => { }, }, ].map((options) => createADAction({ ...options, grouping })); -}; \ No newline at end of file +}; diff --git a/public/utils/utils.tsx b/public/utils/utils.tsx index 95c42354..75b4859a 100644 --- a/public/utils/utils.tsx +++ b/public/utils/utils.tsx @@ -137,6 +137,9 @@ export const getAlertingCreateMonitorLink = ( const navLinks = get(core, 'chrome.navLinks', undefined); const url = `${navLinks.get(ALERTING_PLUGIN_NAME)?.url}`; const alertingRootUrl = getPluginRootPath(url, ALERTING_PLUGIN_NAME); + console.log('navLinks: ' + JSON.stringify(navLinks)); + console.log('url: ' + JSON.stringify(url)); + console.log('alertingRootUrl: ' + JSON.stringify(alertingRootUrl)); return !resultIndex ? `${alertingRootUrl}#/create-monitor?searchType=ad&adId=${detectorId}&name=${detectorName}&interval=${ 2 * detectorInterval From 1a28212d0f691c9ad9a395077610ca47c70d018b Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Wed, 24 May 2023 23:49:48 -0700 Subject: [PATCH 5/7] cleaned up files and added changes to check if detector is deleted in expr fn Signed-off-by: Amit Galitzky --- .../AddAnomalyDetector.tsx | 56 ++++++++++--------- public/expressions/constants.ts | 2 +- public/expressions/overlay_anomalies.ts | 39 ++++++++++--- public/utils/utils.tsx | 3 - server/utils/helpers.ts | 4 ++ 5 files changed, 67 insertions(+), 37 deletions(-) diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx index 03828895..7b8cb8a8 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx @@ -80,7 +80,6 @@ import { FeatureAccordion } from '../../../../public/pages/ConfigureModel/compon import { AD_DOCS_LINK, AD_HIGH_CARDINALITY_LINK, - ALERTING_PLUGIN_NAME, DEFAULT_SHINGLE_SIZE, MAX_FEATURE_NUM, } from '../../../../public/utils/constants'; @@ -106,8 +105,6 @@ function AddAnomalyDetector({ setSelectedDetector, }) { const dispatch = useDispatch(); - const core = React.useContext(CoreServicesContext) as CoreStart; - const [queryText, setQueryText] = useState(''); useEffect(() => { const getInitialIndices = async () => { @@ -186,12 +183,13 @@ function AddAnomalyDetector({ } as ISavedAugmentVis; }; - //DONE 1. if detector is created succesfully, started succesfully and associated succesfully and alerting exists -> show end message with alerting button - //DONE 2. If detector is created succesfully, started succesfully and associated succesfully and alerting doesn't exist -> show end message with OUT alerting button - //DONE 3. If detector is created succesfully, started succesfully and fails association -> show one toast with detector created and started succesfully and one toast with failed association - //DONE 4. If detector is created succesfully, fails starting and fails association -> show one toast with detector created succesfully, one toast with failed association - //DONE 5. If detector is created successfully, fails starting and fails associating -> show one toast with detector created succesfully, one toast with fail starting, one toast with failed association - //DONE 6. If detector fails creating -> show one toast with detector failed creating + // Error handeling/notification cases listed here as many things are being done sequentially + //1. if detector is created succesfully, started succesfully and associated succesfully and alerting exists -> show end message with alerting button + //2. If detector is created succesfully, started succesfully and associated succesfully and alerting doesn't exist -> show end message with OUT alerting button + //3. If detector is created succesfully, started succesfully and fails association -> show one toast with detector created, and one toast with failed association + //4. If detector is created succesfully, fails starting and fails association -> show one toast with detector created succesfully, one toast with failed association + //5. If detector is created successfully, fails starting and fails associating -> show one toast with detector created succesfully, one toast with fail starting, one toast with failed association + //6. If detector fails creating -> show one toast with detector failed creating const handleSubmit = async (formikProps) => { formikProps.setSubmitting(true); try { @@ -230,14 +228,14 @@ function AddAnomalyDetector({ notifications.toasts.addSuccess({ title: `The ${formikProps.values.name} is associated with the ${title} visualization`, text: mountReactNode( - getEverythingSuccesfulButton(detectorId, shingleSize) + getEverythingSuccessfulButton(detectorId, shingleSize) ), }); closeFlyout(); }) .catch((error) => { console.error( - `Error associating selected detector4: ${error}` + `Error associating selected detector: ${error}` ); notifications.toasts.addDanger( prettifyErrorMessage( @@ -252,7 +250,7 @@ function AddAnomalyDetector({ .catch((error) => { notifications.toasts.addDanger( prettifyErrorMessage( - `Error associating selected detector3 in crate process: ${error}` + `Error associating selected detector in create process: ${error}` ) ); notifications.toasts.addSuccess( @@ -288,38 +286,44 @@ function AddAnomalyDetector({ } }; - const getEverythingSuccesfulButton = (detectorId, shingleSize) => { + const getEverythingSuccessfulButton = (detectorId, shingleSize) => { return (

- `Attempting to initialize the detector with historical data. This + Attempting to initialize the detector with historical data. This initializing process takes approximately 1 minute if you have data in - each of the last ${32 + shingleSize} consecutive intervals.` + each of the last {32 + shingleSize} consecutive intervals.

{alertingExists(detectorId) ? ( - - openAlerting(detectorId)}> - Set Up Alerts - {' '} -

set up alerts

-
+ + +

Set up alerts to be notifified of any anomalies.

+
+ +
+ openAlerting(detectorId)}> + Set up alerts + {' '} +
+
+
) : null}
); }; - const alertingExists = (detectorId) => { + const alertingExists = () => { try { const uiActionService = getUiActions(); uiActionService.getTrigger('ALERTING_TRIGGER_AD_ID'); return true; } catch (e) { - console.error('No allerting trigger exists', e); + console.error('No alerting trigger exists', e); return false; } }; - const openAlerting = (detectorId) => { + const openAlerting = (detectorId: string) => { const uiActionService = getUiActions(); uiActionService .getTrigger('ALERTING_TRIGGER_AD_ID') @@ -834,7 +838,7 @@ function AddAnomalyDetector({ isLoading={formikProps.isSubmitting} onClick={() => handleAssociate(selectedDetector)} > - associate detector + Associate detector ) : ( - create detector + Create detector )} diff --git a/public/expressions/constants.ts b/public/expressions/constants.ts index 9fb05ed4..41a79276 100644 --- a/public/expressions/constants.ts +++ b/public/expressions/constants.ts @@ -12,4 +12,4 @@ export const TYPE_OF_EXPR_VIS_LAYERS = 'vis_layers'; export const OVERLAY_ANOMALIES = 'overlay_anomalies'; -export const PLUGIN_EVENT_TYPE = 'anomalies'; +export const PLUGIN_EVENT_TYPE = 'Anomalies'; diff --git a/public/expressions/overlay_anomalies.ts b/public/expressions/overlay_anomalies.ts index aa364a48..3ff8c128 100644 --- a/public/expressions/overlay_anomalies.ts +++ b/public/expressions/overlay_anomalies.ts @@ -29,7 +29,11 @@ import { VisLayerErrorTypes, } from '../../../../src/plugins/vis_augmenter/public'; import { PLUGIN_NAME } from '../utils/constants'; -import { NO_PERMISSIONS_KEY_WORD } from '../../server/utils/helpers'; +import { + CANT_FIND_KEY_WORD, + DOES_NOT_HAVE_PERMISSIONS_KEY_WORD, + NO_PERMISSIONS_KEY_WORD, +} from '../../server/utils/helpers'; import { ORIGIN_PLUGIN_VIS_LAYER, OVERLAY_ANOMALIES, @@ -42,6 +46,8 @@ type Input = ExprVisLayers; type Output = Promise; type Name = typeof OVERLAY_ANOMALIES; +const DETECTOR_HAS_BEEN_DELETED = 'detector has been deleted'; + interface Arguments { detectorId: string; } @@ -73,9 +79,9 @@ const getAnomalies = async ( return parsePureAnomalies(anomalySummaryResponse); }; -const getDetectorName = async (detectorId: string) => { +const getDetectorResponse = async (detectorId: string) => { const resp = await getClient().get(`..${AD_NODE_API.DETECTOR}/${detectorId}`); - return get(resp.response, 'name', ''); + return resp; }; // This takes anomalies and returns them as vis layer of type PointInTimeEvents @@ -153,7 +159,17 @@ export const overlayAnomaliesFunction = urlPath: `${PLUGIN_NAME}#/detectors/${detectorId}/results`, //details page for detector in AD plugin }; try { - const detectorName = await getDetectorName(detectorId); + const detectorResponse = await getDetectorResponse(detectorId); + if (get(detectorResponse, 'error', '').includes(CANT_FIND_KEY_WORD)) { + throw new Error('Anomaly Detector - ' + DETECTOR_HAS_BEEN_DELETED); + } else if ( + get(detectorResponse, 'error', '').includes( + DOES_NOT_HAVE_PERMISSIONS_KEY_WORD + ) + ) { + throw new Error(get(detectorResponse, 'error', '')); + } + const detectorName = get(detectorResponse.response, 'name', ''); if (detectorName === '') { throw new Error('Anomaly Detector - Unable to get detector'); } @@ -178,15 +194,24 @@ export const overlayAnomaliesFunction = : ([anomalyLayer] as VisLayers), }; } catch (error) { - console.log('Anomaly Detector - Unable to get anomalies: ', error); + console.error('Anomaly Detector - Unable to get anomalies: ', error); let visLayerError: VisLayerError = {} as VisLayerError; if ( typeof error === 'string' && - error.includes(NO_PERMISSIONS_KEY_WORD) + (error.includes(NO_PERMISSIONS_KEY_WORD) || + error.includes(DOES_NOT_HAVE_PERMISSIONS_KEY_WORD)) ) { visLayerError = { type: VisLayerErrorTypes.PERMISSIONS_FAILURE, - message: error, //TODO: might just change this to a generic message like rest of AD plugin + message: error, + }; + } else if ( + typeof error === 'string' && + error.includes(DETECTOR_HAS_BEEN_DELETED) + ) { + visLayerError = { + type: VisLayerErrorTypes.RESOURCE_DELETED, + message: error, }; } else { visLayerError = { diff --git a/public/utils/utils.tsx b/public/utils/utils.tsx index 75b4859a..95c42354 100644 --- a/public/utils/utils.tsx +++ b/public/utils/utils.tsx @@ -137,9 +137,6 @@ export const getAlertingCreateMonitorLink = ( const navLinks = get(core, 'chrome.navLinks', undefined); const url = `${navLinks.get(ALERTING_PLUGIN_NAME)?.url}`; const alertingRootUrl = getPluginRootPath(url, ALERTING_PLUGIN_NAME); - console.log('navLinks: ' + JSON.stringify(navLinks)); - console.log('url: ' + JSON.stringify(url)); - console.log('alertingRootUrl: ' + JSON.stringify(alertingRootUrl)); return !resultIndex ? `${alertingRootUrl}#/create-monitor?searchType=ad&adId=${detectorId}&name=${detectorName}&interval=${ 2 * detectorInterval diff --git a/server/utils/helpers.ts b/server/utils/helpers.ts index 035d2c74..15c80b3e 100644 --- a/server/utils/helpers.ts +++ b/server/utils/helpers.ts @@ -66,6 +66,10 @@ const PERMISSIONS_ERROR_PATTERN = export const NO_PERMISSIONS_KEY_WORD = 'no permissions'; +export const DOES_NOT_HAVE_PERMISSIONS_KEY_WORD = 'does not have permissions'; + +export const CANT_FIND_KEY_WORD = "Can't find"; + export const prettifyErrorMessage = (rawErrorMessage: string) => { if (isEmpty(rawErrorMessage) || rawErrorMessage === 'undefined') { return 'Unknown error is returned.'; From fa45fd8149e6860553c52ff6e76c05f03998e687 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Thu, 25 May 2023 17:22:59 -0700 Subject: [PATCH 6/7] fixing dependency and notifcations issues Signed-off-by: Amit Galitzky --- .../AnywhereParentFlyout.tsx | 7 +-- .../AnywhereParentFlyout/constants.ts | 13 +++++ .../containers/AssociatedDetectors.tsx | 53 ++++++++----------- .../AddAnomalyDetector.tsx | 47 +++++++++++----- .../containers/AssociateExisting.tsx | 40 +++++++------- .../CreateAnomalyDetector/styles.scss | 10 ++++ public/plugin.ts | 2 + public/services.ts | 8 ++- public/utils/contextMenu/getActions.tsx | 5 +- 9 files changed, 115 insertions(+), 70 deletions(-) create mode 100644 public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/constants.ts diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx index 8e77299d..5ab72b2d 100644 --- a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx +++ b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx @@ -7,6 +7,7 @@ import { get } from 'lodash'; import AssociatedDetectors from '../AssociatedDetectors/containers/AssociatedDetectors'; import { getEmbeddable } from '../../../../public/services'; import AddAnomalyDetector from '../CreateAnomalyDetector'; +import { FLYOUT_MODES } from './constants'; const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { const embeddable = getEmbeddable().getEmbeddableFactory; @@ -18,9 +19,9 @@ const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { const [selectedDetector, setSelectedDetector] = useState(undefined); const AnywhereFlyout = { - create: AddAnomalyDetector, - associated: AssociatedDetectors, - existing: AddAnomalyDetector, + [FLYOUT_MODES.create]: AddAnomalyDetector, + [FLYOUT_MODES.associated]: AssociatedDetectors, + [FLYOUT_MODES.existing]: AddAnomalyDetector, }[mode]; return ( diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/constants.ts b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/constants.ts new file mode 100644 index 00000000..fa470962 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/constants.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +//created: Flyout for creating a new anomaly detector from a visualization +//associated: Flyout for listing all the associated detectors to the given visualization +//existing: Flyout for associating existing detectors with the current visualizations +export enum FLYOUT_MODES { + create = 'create', + associated = 'associated', + existing = 'existing', +} diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx index d5f755da..69f299cc 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -24,6 +24,7 @@ import { DetectorListItem } from '../../../../models/interfaces'; import { getSavedFeatureAnywhereLoader, getNotifications, + getUISettings, } from '../../../../services'; import { GET_ALL_DETECTORS_QUERY_PARAMS, @@ -39,7 +40,11 @@ import { EmptyAssociatedDetectorMessage, ConfirmUnlinkDetectorModal, } from '../components'; -import { ISavedAugmentVis } from '../../../../../../../src/plugins/vis_augmenter/public'; +import { + ISavedAugmentVis, + SavedAugmentVisLoader, + getAugmentVisSavedObjs, +} from '../../../../../../../src/plugins/vis_augmenter/public'; import { ASSOCIATED_DETECTOR_ACTION } from '../utils/constants'; interface ConfirmModalState { @@ -82,8 +87,10 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { ); // Establish savedObjectLoader for all operations on vis_augment saved objects - const savedObjectLoader: SavedObjectLoader = getSavedFeatureAnywhereLoader(); + const savedObjectLoader: SavedAugmentVisLoader = + getSavedFeatureAnywhereLoader(); + const uiSettings = getUISettings(); const notifications = getNotifications(); useEffect(() => { @@ -127,15 +134,12 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { // Handles all changes in the assoicated detectors such as unlinking or new detectors associated useEffect(() => { - // Gets all augmented saved objects - savedObjectLoader - .findAll() - .then((resp: any) => { - if (resp != undefined) { - const savedAugmentObjectsArr: ISavedAugmentVis[] = get( - resp, - 'hits', - [] + // Gets all augmented saved objects that are associated to the given visualization + getAugmentVisSavedObjs(embeddable.vis.id, savedObjectLoader, uiSettings) + .then((savedAugmentObjectsArr: any) => { + if (savedAugmentObjectsArr != undefined) { + console.log( + 'savedAugmentObjectsArr: ' + JSON.stringify(savedAugmentObjectsArr) ); const curSelectedDetectors = getAssociatedDetectors( Object.values(allDetectors), @@ -156,14 +160,8 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { // that are associated to the current visualization const getAssociatedDetectors = ( detectors: DetectorListItem[], - savedAugmentObjects: ISavedAugmentVis[] + savedAugmentForThisVisualization: ISavedAugmentVis[] ) => { - // Filter all savedAugmentObjects that aren't linked to the specific visualization - const savedAugmentForThisVisualization: ISavedAugmentVis[] = - savedAugmentObjects.filter( - (savedObj) => get(savedObj, 'visId', '') === embeddable.vis.id - ); - // Map all detector IDs for all the found augmented vis objects const savedAugmentDetectorsSet = new Set( savedAugmentForThisVisualization.map((savedObject) => @@ -180,18 +178,13 @@ function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { const onUnlinkDetector = async () => { setIsLoadingFinalDetectors(true); - await savedObjectLoader.findAll().then(async (resp: any) => { - if (resp != undefined) { - // gets all the saved object for this visualization - const savedAugmentForThisVisualization: ISavedAugmentVis[] = get( - resp, - 'hits', - [] as ISavedAugmentVis[] - ).filter( - (savedObj: ISavedAugmentVis[]) => - get(savedObj, 'visId', '') === embeddable.vis.id - ); - + // Gets all augmented saved objects that are associated to the given visualization + await getAugmentVisSavedObjs( + embeddable.vis.id, + savedObjectLoader, + uiSettings + ).then(async (savedAugmentForThisVisualization: any) => { + if (savedAugmentForThisVisualization != undefined) { // find saved augment object matching detector we want to unlink // There should only be one detector and vis pairing const savedAugmentToUnlink = savedAugmentForThisVisualization.find( diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx index 7b8cb8a8..1245518e 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx @@ -31,6 +31,7 @@ import { createAugmentVisSavedObject, ISavedAugmentVis, ISavedPluginResource, + SavedAugmentVisLoader, VisLayerExpressionFn, VisLayerTypes, } from '../../../../../../src/plugins/vis_augmenter/public'; @@ -83,7 +84,12 @@ import { DEFAULT_SHINGLE_SIZE, MAX_FEATURE_NUM, } from '../../../../public/utils/constants'; -import { getNotifications, getUiActions } from '../../../../public/services'; +import { + getNotifications, + getSavedFeatureAnywhereLoader, + getUISettings, + getUiActions, +} from '../../../../public/services'; import { prettifyErrorMessage } from '../../../../server/utils/helpers'; import { ORIGIN_PLUGIN_VIS_LAYER, @@ -93,8 +99,7 @@ import { import { formikToDetectorName, visFeatureListToFormik } from './helpers'; import { AssociateExisting } from './AssociateExisting'; import { mountReactNode } from '../../../../../../src/core/public/utils'; -import { CoreServicesContext } from '../../../../public/components/CoreServices/CoreServices'; -import { CoreStart } from '../../../../../../src/core/public'; +import { FLYOUT_MODES } from '../AnywhereParentFlyout/constants'; function AddAnomalyDetector({ embeddable, @@ -160,6 +165,10 @@ function AddAnomalyDetector({ } }; + const uiSettings = getUISettings(); + const savedObjectLoader: SavedAugmentVisLoader = + getSavedFeatureAnywhereLoader(); + const getAugmentVisSavedObject = (detectorId: string) => { const fn = { type: VisLayerTypes.PointInTimeEvents, @@ -210,11 +219,14 @@ function AddAnomalyDetector({ }); const detectorId = response.response.id; - const augmentVisSavedObjectToCreate: ISavedAugmentVis = getAugmentVisSavedObject(detectorId); - createAugmentVisSavedObject(augmentVisSavedObjectToCreate) + createAugmentVisSavedObject( + augmentVisSavedObjectToCreate, + uiSettings, + savedObjectLoader + ) .then((savedObject: any) => { savedObject .save({}) @@ -230,12 +242,14 @@ function AddAnomalyDetector({ text: mountReactNode( getEverythingSuccessfulButton(detectorId, shingleSize) ), + className: 'createdAndAssociatedSuccessToast', + toastLifeTimeMs: 3000000, }); closeFlyout(); }) .catch((error) => { console.error( - `Error associating selected detector: ${error}` + `Error associating selected detector in save process: ${error}` ); notifications.toasts.addDanger( prettifyErrorMessage( @@ -248,6 +262,9 @@ function AddAnomalyDetector({ }); }) .catch((error) => { + console.error( + `Error associating selected detector in create process: ${error}` + ); notifications.toasts.addDanger( prettifyErrorMessage( `Error associating selected detector in create process: ${error}` @@ -294,16 +311,16 @@ function AddAnomalyDetector({ initializing process takes approximately 1 minute if you have data in each of the last {32 + shingleSize} consecutive intervals.

- {alertingExists(detectorId) ? ( + {alertingExists() ? ( -

Set up alerts to be notifified of any anomalies.

+

Set up alerts to be notified of any anomalies.

openAlerting(detectorId)}> Set up alerts - {' '} +
@@ -334,7 +351,11 @@ function AddAnomalyDetector({ const augmentVisSavedObjectToCreate: ISavedAugmentVis = getAugmentVisSavedObject(detector.id); - createAugmentVisSavedObject(augmentVisSavedObjectToCreate) + createAugmentVisSavedObject( + augmentVisSavedObjectToCreate, + uiSettings, + savedObjectLoader + ) .then((savedObject: any) => { savedObject .save({}) @@ -454,14 +475,14 @@ function AddAnomalyDetector({ ))} - {mode === 'existing' && ( + {mode === FLYOUT_MODES.existing && ( )} - {mode === 'create' && ( + {mode === FLYOUT_MODES.create && (

@@ -831,7 +852,7 @@ function AddAnomalyDetector({ Cancel - {mode === 'existing' ? ( + {mode === FLYOUT_MODES.existing ? ( state.ad.requesting ); + const uiSettings = getUISettings(); const [isLoadingFinalDetectors, setIsLoadingFinalDetectors] = useState(true); const isLoading = isRequestingFromES || isLoadingFinalDetectors; @@ -69,7 +76,8 @@ export function AssociateExisting( ] = useState([] as DetectorListItem[]); // Establish savedObjectLoader for all operations on vis augmented saved objects - const savedObjectLoader: SavedObjectLoader = getSavedFeatureAnywhereLoader(); + const savedObjectLoader: SavedAugmentVisLoader = + getSavedFeatureAnywhereLoader(); useEffect(() => { if ( @@ -89,14 +97,13 @@ export function AssociateExisting( // Handle all changes in the assoicated detectors such as unlinking or new detectors associated useEffect(() => { - // Gets all augmented saved objects - savedObjectLoader.findAll().then((resp: any) => { - if (resp != undefined) { - const savedAugmentObjectsArr: ISavedAugmentVis[] = get( - resp, - 'hits', - [] - ); + // Gets all augmented saved objects for the given visualization + getAugmentVisSavedObjs( + associateExistingProps.embeddableVisId, + savedObjectLoader, + uiSettings + ).then((savedAugmentObjectsArr: any) => { + if (savedAugmentObjectsArr != undefined) { const curDetectorsToDisplayOnList = getExistingDetectorsAvailableToAssociate( Object.values(allDetectors), @@ -112,15 +119,8 @@ export function AssociateExisting( // that are associated to the current visualization const getExistingDetectorsAvailableToAssociate = ( detectors: DetectorListItem[], - savedAugmentObjects: ISavedAugmentVis[] + savedAugmentForThisVisualization: ISavedAugmentVis[] ) => { - // Filter all savedAugmentObjects that aren't linked to the specific visualization - const savedAugmentForThisVisualization: ISavedAugmentVis[] = - savedAugmentObjects.filter( - (savedObj) => - get(savedObj, 'visId', '') === associateExistingProps.embeddableVisId - ); - // Map all detector IDs for all the found augmented vis objects const savedAugmentDetectorsSet = new Set( savedAugmentForThisVisualization.map((savedObject) => diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss index bf457fc5..e16e3895 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss @@ -56,3 +56,13 @@ height: 100%; min-height: 40px; } + +.euiGlobalToastList { + width: 650px; +} + +.createdAndAssociatedSuccessToast { + width: 550px; + position: relative; + right: 15px; +} diff --git a/public/plugin.ts b/public/plugin.ts index 29c2b859..83fd40eb 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -33,6 +33,7 @@ import { setOverlays, setSavedFeatureAnywhereLoader, setUiActions, + setUISettings, } from './services'; import { AnomalyDetectionOpenSearchDashboardsPluginStart } from 'public'; import { @@ -112,6 +113,7 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin core: CoreStart, { embeddable, visAugmenter, uiActions }: AnomalyDetectionStartDeps ): AnomalyDetectionOpenSearchDashboardsPluginStart { + setUISettings(core.uiSettings); setEmbeddable(embeddable); setOverlays(core.overlays); setSavedFeatureAnywhereLoader(visAugmenter.savedAugmentVisLoader); diff --git a/public/services.ts b/public/services.ts index cfdf7be8..7e0d7843 100644 --- a/public/services.ts +++ b/public/services.ts @@ -5,16 +5,17 @@ import { CoreStart, + IUiSettingsClient, NotificationsStart, OverlayStart, } from '../../../src/core/public'; import { EmbeddableStart } from '../../../src/plugins/embeddable/public'; import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/public'; -import { SavedObjectLoader } from '../../../src/plugins/saved_objects/public'; import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; +import { SavedAugmentVisLoader } from '../../../src/plugins/vis_augmenter/public'; export const [getSavedFeatureAnywhereLoader, setSavedFeatureAnywhereLoader] = - createGetterSetter('savedFeatureAnywhereLoader'); + createGetterSetter('savedFeatureAnywhereLoader'); export const [getClient, setClient] = createGetterSetter('http'); @@ -30,3 +31,6 @@ export const [getNotifications, setNotifications] = export const [getUiActions, setUiActions] = createGetterSetter('UIActions'); + +export const [getUISettings, setUISettings] = + createGetterSetter('UISettings'); diff --git a/public/utils/contextMenu/getActions.tsx b/public/utils/contextMenu/getActions.tsx index 0c1302e4..cccfd399 100644 --- a/public/utils/contextMenu/getActions.tsx +++ b/public/utils/contextMenu/getActions.tsx @@ -15,6 +15,7 @@ import configureStore from '../../redux/configureStore'; import DocumentationTitle from '../../components/FeatureAnywhereContextMenu/DocumentationTitle/containers/DocumentationTitle'; import { AD_DOCS_LINK, APM_TRACE } from '../constants'; import { getClient, getOverlays } from '../../../public/services'; +import { FLYOUT_MODES } from '../../../public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/constants'; // This is used to create all actions in the same context menu const grouping: Action['grouping'] = [ @@ -58,7 +59,7 @@ export const getActions = () => { ), icon: 'plusInCircle' as EuiIconType, order: 100, - onClick: getOnClick('create'), + onClick: getOnClick(FLYOUT_MODES.create), }, { grouping, @@ -71,7 +72,7 @@ export const getActions = () => { ), icon: 'gear' as EuiIconType, order: 99, - onClick: getOnClick('associated'), + onClick: getOnClick(FLYOUT_MODES.associated), }, { id: 'documentationAnomalyDetector', From 15dc68df78652a67bbfb36887797222aaf6543a7 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Thu, 25 May 2023 17:48:24 -0700 Subject: [PATCH 7/7] removed long toast life time Signed-off-by: Amit Galitzky --- .../CreateAnomalyDetector/AddAnomalyDetector.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx index 1245518e..307f26de 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx @@ -243,7 +243,6 @@ function AddAnomalyDetector({ getEverythingSuccessfulButton(detectorId, shingleSize) ), className: 'createdAndAssociatedSuccessToast', - toastLifeTimeMs: 3000000, }); closeFlyout(); })