diff --git a/public/action/ad_dashboard_action.tsx b/public/action/ad_dashboard_action.tsx index 0be356ed..a845351a 100644 --- a/public/action/ad_dashboard_action.tsx +++ b/public/action/ad_dashboard_action.tsx @@ -1,6 +1,6 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 */ import { IEmbeddable } from '../../../../src/plugins/dashboard/public/embeddable_plugin'; import { @@ -75,4 +75,4 @@ export const createADAction = ({ onClick({ embeddable }); }, - }); \ No newline at end of file + }); diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx index 2a54a169..70c27e68 100644 --- a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx +++ b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/AnywhereParentFlyout.tsx @@ -1,10 +1,10 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 */ import React, { useState } from 'react'; import { get } from 'lodash'; -import AddAnomalyDetector from '../CreateAnomalyDetector'; +import AssociatedDetectors from '../AssociatedDetectors/containers/AssociatedDetectors'; import { getEmbeddable } from '../../../../public/services'; const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { @@ -17,7 +17,7 @@ const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { const [selectedDetectorId, setSelectedDetectorId] = useState(); const AnywhereFlyout = { - create: AddAnomalyDetector, + associated: AssociatedDetectors, }[mode]; return ( @@ -30,8 +30,8 @@ const AnywhereParentFlyout = ({ startingFlyout, ...props }) => { selectedDetectorId, setSelectedDetectorId, }} - /> + /> ); }; -export default AnywhereParentFlyout; \ No newline at end of file +export default AnywhereParentFlyout; diff --git a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.tsx b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.tsx index cca0078b..591d4b6d 100644 --- a/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.tsx +++ b/public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/index.tsx @@ -5,4 +5,4 @@ import AnywhereParentFlyout from './AnywhereParentFlyout'; -export default AnywhereParentFlyout; \ No newline at end of file +export default AnywhereParentFlyout; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx new file mode 100644 index 00000000..25687ed5 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx @@ -0,0 +1,78 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { + EuiText, + EuiOverlayMask, + EuiButton, + EuiButtonEmpty, + EuiModal, + EuiModalHeader, + EuiModalFooter, + EuiModalBody, + EuiModalHeaderTitle, +} from '@elastic/eui'; +import { DetectorListItem } from '../../../../../models/interfaces'; +import { EuiSpacer } from '@elastic/eui'; + +interface ConfirmUnlinkDetectorModalProps { + detector: DetectorListItem; + onUnlinkDetector(): void; + onHide(): void; + onConfirm(): void; + isListLoading: boolean; +} + +export const ConfirmUnlinkDetectorModal = ( + props: ConfirmUnlinkDetectorModalProps +) => { + const [isModalLoading, setIsModalLoading] = useState(false); + const isLoading = isModalLoading || props.isListLoading; + return ( + + + + {'Remove association?'} + + + + Removing association unlinks {props.detector.name} detector from the + visualization but does not delete it. The detector association can + be restored. + + + + + {isLoading ? null : ( + + Cancel + + )} + { + setIsModalLoading(true); + props.onUnlinkDetector(); + props.onConfirm(); + }} + > + {'Remove association'} + + + + + ); +}; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyAssociatedDetectorMessage/EmptyAssociatedDetectorMessage.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyAssociatedDetectorMessage/EmptyAssociatedDetectorMessage.tsx new file mode 100644 index 00000000..d005e087 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyAssociatedDetectorMessage/EmptyAssociatedDetectorMessage.tsx @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import React from 'react'; + +const FILTER_TEXT = 'There are no detectors matching your search'; + +interface EmptyDetectorProps { + isFilterApplied: boolean; + embeddableTitle: string; +} + +export const EmptyAssociatedDetectorMessage = (props: EmptyDetectorProps) => ( + No anomaly detectors to display} + titleSize="s" + data-test-subj="emptyAssociatedDetectorFlyoutMessage" + style={{ maxWidth: '45em' }} + body={ + +

+ {props.isFilterApplied + ? FILTER_TEXT + : `There are no anomaly detectors associated with ${props.embeddableTitle} visualization.`} +

+
+ } + /> +); diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/index.ts b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/index.ts new file mode 100644 index 00000000..92d619eb --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { ConfirmUnlinkDetectorModal } from './ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal'; +export { EmptyAssociatedDetectorMessage } from './EmptyAssociatedDetectorMessage/EmptyAssociatedDetectorMessage'; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx new file mode 100644 index 00000000..c0b4f64f --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -0,0 +1,344 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useMemo, useEffect, useState } from 'react'; +import { + EuiFlyoutHeader, + EuiTitle, + EuiSpacer, + EuiInMemoryTable, + EuiFlyoutBody, + EuiButton, + EuiFlyout, + EuiFlexItem, + EuiFlexGroup, +} from '@elastic/eui'; +import { get, isEmpty } from 'lodash'; +import '../styles.scss'; +import { getColumns } from '../utils/helpers'; +import { useDispatch, useSelector } from 'react-redux'; +import { AppState } from '../../../../redux/reducers'; +import { DetectorListItem } from '../../../../models/interfaces'; +import { + getSavedFeatureAnywhereLoader, + getNotifications, +} from '../../../../services'; +import { + GET_ALL_DETECTORS_QUERY_PARAMS, + SINGLE_DETECTOR_NOT_FOUND_MSG, +} from '../../../../pages/utils/constants'; +import { getDetectorList } from '../../../../redux/reducers/ad'; +import { + prettifyErrorMessage, + NO_PERMISSIONS_KEY_WORD, +} from '../../../../../server/utils/helpers'; +import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public'; +import { + EmptyAssociatedDetectorMessage, + ConfirmUnlinkDetectorModal, +} from '../components'; +import { ISavedAugmentVis } from '../../../../../../../src/plugins/vis_augmenter/public'; +import { ASSOCIATED_DETECTOR_ACTION } from '../utils/constants'; + +interface ConfirmModalState { + isOpen: boolean; + action: ASSOCIATED_DETECTOR_ACTION; + isListLoading: boolean; + isRequestingToClose: boolean; + affectedDetector: DetectorListItem; +} + +function AssociatedDetectors({ embeddable, closeFlyout, setMode }) { + 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 embeddableTitle = embeddable.getTitle(); + const [selectedDetectors, setSelectedDetectors] = useState( + [] as DetectorListItem[] + ); + + const [detectorToUnlink, setDetectorToUnlink] = useState( + {} as DetectorListItem + ); + const [confirmModalState, setConfirmModalState] = useState( + { + isOpen: false, + //@ts-ignore + action: null, + isListLoading: false, + isRequestingToClose: false, + affectedDetector: {} as DetectorListItem, + } + ); + + // Establish savedObjectLoader for all operations on vis_augment saved objects + const savedObjectLoader: SavedObjectLoader = getSavedFeatureAnywhereLoader(); + + const notifications = getNotifications(); + + useEffect(() => { + if ( + errorGettingDetectors && + !errorGettingDetectors.includes(SINGLE_DETECTOR_NOT_FOUND_MSG) + ) { + console.error(errorGettingDetectors); + notifications.toasts.addDanger( + typeof errorGettingDetectors === 'string' && + errorGettingDetectors.includes(NO_PERMISSIONS_KEY_WORD) + ? prettifyErrorMessage(errorGettingDetectors) + : 'Unable to get all detectors' + ); + setIsLoadingFinalDetectors(false); + } + }, [errorGettingDetectors]); + + // Update modal state if user decides to close modal + useEffect(() => { + if (confirmModalState.isRequestingToClose) { + if (isLoading) { + setConfirmModalState({ + ...confirmModalState, + isListLoading: true, + }); + } else { + setConfirmModalState({ + ...confirmModalState, + isOpen: false, + isListLoading: false, + isRequestingToClose: false, + }); + } + } + }, [confirmModalState.isRequestingToClose, isLoading]); + + useEffect(() => { + getDetectors(); + }, []); + + // 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', + [] + ); + const curSelectedDetectors = getAssociatedDetectors( + Object.values(allDetectors), + savedAugmentObjectsArr + ); + setSelectedDetectors(curSelectedDetectors); + setIsLoadingFinalDetectors(false); + } + }) + .catch((error) => { + notifications.toasts.addDanger( + prettifyErrorMessage(`Unable to fetch associated detectors: ${error}`) + ); + }); + }, [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 getAssociatedDetectors = ( + detectors: DetectorListItem[], + savedAugmentObjects: 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) => + get(savedObject, 'pluginResource.id', '') + ) + ); + + // filter out any detectors that aren't on the set of detectors IDs from the augmented vis objects. + const detectorsToDisplay = detectors.filter((detector) => + savedAugmentDetectorsSet.has(detector.id) + ); + return detectorsToDisplay; + }; + + 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 + ); + + // find saved augment object matching detector we want to unlink + // There should only be one detector and vis pairing + const savedAugmentToUnlink = savedAugmentForThisVisualization.find( + (savedObject) => + get(savedObject, 'pluginResource.id', '') === detectorToUnlink.id + ); + await savedObjectLoader + .delete(get(savedAugmentToUnlink, 'id', '')) + .then(async (resp: any) => { + notifications.toasts.addSuccess({ + title: `Association removed between the ${detectorToUnlink.name} + and the ${embeddableTitle} visualization`, + text: "The detector's anomalies do not appear on the visualization. Refresh your dashboard to update the visualization", + }); + }) + .catch((error) => { + notifications.toasts.addDanger( + prettifyErrorMessage( + `Error unlinking selected detector: ${error}` + ) + ); + }) + .finally(() => { + getDetectors(); + }); + } + }); + }; + + const handleHideModal = () => { + setConfirmModalState({ + ...confirmModalState, + isOpen: false, + }); + }; + + const handleConfirmModal = () => { + setConfirmModalState({ + ...confirmModalState, + isRequestingToClose: true, + }); + }; + + const getDetectors = async () => { + 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({ + isOpen: true, + action: ASSOCIATED_DETECTOR_ACTION.UNLINK, + isListLoading: false, + isRequestingToClose: false, + affectedDetector: detector, + }); + }; + + const columns = useMemo( + () => getColumns({ handleUnlinkDetectorAction }), + [handleUnlinkDetectorAction] + ); + + const renderEmptyMessage = () => { + if (isLoading) { + return 'Loading detectors...'; + } else if (!isEmpty(selectedDetectors)) { + return ( + + ); + } else { + return ( + + ); + } + }; + + const tableProps = { + items: selectedDetectors, + columns, + search: { + box: { + disabled: selectedDetectors.length === 0, + incremental: true, + schema: true, + }, + }, + hasActions: true, + pagination: true, + sorting: true, + message: renderEmptyMessage(), + }; + return ( +
+ + + +

+ Associated anomaly detectors +

+
+
+ + {confirmModalState.isOpen ? ( + + ) : null} + + + +

{embeddableTitle}

+
+
+ + { + openAssociateDetectorFlyout(); + }} + > + Associate a detector + + +
+ + +
+
+
+ ); +} + +export default AssociatedDetectors; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.ts b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.ts new file mode 100644 index 00000000..39483649 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { AssociatedDetectors } from './containers/AssociatedDetectors'; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss new file mode 100644 index 00000000..6598f00e --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +.associated-detectors { + height: 100%; + display: flex; + flex-direction: column; + + .euiFlyoutBody__overflowContent { + height: 100%; + padding-bottom: 0; + } + + &__flex-group { + height: 100%; + } +} diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx new file mode 100644 index 00000000..37236349 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export enum ASSOCIATED_DETECTOR_ACTION { + UNLINK, +} diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx new file mode 100644 index 00000000..c6125537 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx @@ -0,0 +1,75 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiBasicTableColumn, EuiHealth, EuiLink } from '@elastic/eui'; +import { DETECTOR_STATE } from 'server/utils/constants'; +import { stateToColorMap } from '../../../../pages/utils/constants'; +import { PLUGIN_NAME } from '../../../../utils/constants'; +import { Detector } from '../../../../models/interfaces'; + +export const renderState = (state: DETECTOR_STATE) => { + return ( + //@ts-ignore + {state} + ); +}; + +export const getColumns = ({ handleUnlinkDetectorAction }) => + [ + { + field: 'name', + name: 'Detector', + sortable: true, + truncateText: true, + width: '30%', + align: 'left', + render: (name: string, detector: Detector) => ( + + {name} + + ), + }, + { + field: 'curState', + name: 'Real-time state', + sortable: true, + align: 'left', + width: '30%', + truncateText: true, + render: renderState, + }, + { + field: 'totalAnomalies', + name: 'Anomalies/24hr', + sortable: true, + dataType: 'number', + align: 'left', + truncateText: true, + width: '30%', + }, + { + name: 'Actions', + align: 'left', + truncateText: true, + width: '10%', + actions: [ + { + type: 'icon', + name: 'Remove association', + description: 'Remove association', + icon: 'unlink', + onClick: handleUnlinkDetectorAction, + }, + ], + }, + ] as EuiBasicTableColumn[]; + +export const search = { + box: { + incremental: true, + schema: true, + }, +}; diff --git a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/containers/DocumentationTitle.tsx b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/containers/DocumentationTitle.tsx index 22d2ac3c..3ee81e65 100644 --- a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/containers/DocumentationTitle.tsx +++ b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/containers/DocumentationTitle.tsx @@ -25,4 +25,4 @@ const DocumentationTitle = () => ( ); -export default DocumentationTitle; \ No newline at end of file +export default DocumentationTitle; diff --git a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.tsx b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.tsx index 03b2fb80..e9f1bd89 100644 --- a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.tsx +++ b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.tsx @@ -5,4 +5,4 @@ import DocumentationTitle from './containers/DocumentationTitle'; -export default DocumentationTitle; \ No newline at end of file +export default DocumentationTitle; diff --git a/public/plugin.ts b/public/plugin.ts index 36e15cc3..f3f4669e 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -15,14 +15,24 @@ import { CoreStart, Plugin, } from '../../../src/core/public'; -import { CONTEXT_MENU_TRIGGER, EmbeddableSetup, EmbeddableStart } from '../../../src/plugins/embeddable/public'; +import { + CONTEXT_MENU_TRIGGER, + EmbeddableSetup, + EmbeddableStart, +} from '../../../src/plugins/embeddable/public'; import { ACTION_AD } from './action/ad_dashboard_action'; import { PLUGIN_NAME } from './utils/constants'; import { getActions } from './utils/contextMenu/getActions'; import { overlayAnomaliesFunction } from './expressions/overlay_anomalies'; -import { setClient, setEmbeddable, setOverlays } from './services'; +import { + setClient, + setEmbeddable, + setNotifications, + setOverlays, + setSavedFeatureAnywhereLoader, +} from './services'; import { AnomalyDetectionOpenSearchDashboardsPluginStart } from 'public'; -import { createStartServicesGetter } from '../../../src/plugins/opensearch_dashboards_utils/public'; +import { VisAugmenterStart } from '../../../src/plugins/vis_augmenter/public'; declare module '../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -36,51 +46,54 @@ export interface AnomalyDetectionSetupDeps { export interface AnomalyDetectionStartDeps { embeddable: EmbeddableStart; + visAugmenter: VisAugmenterStart; } -export class AnomalyDetectionOpenSearchDashboardsPlugin implements - Plugin { - - public setup(core: CoreSetup, plugins: any) { - core.application.register({ - id: PLUGIN_NAME, - title: 'Anomaly Detection', - category: { - id: 'opensearch', - label: 'OpenSearch Plugins', - order: 2000, - }, - order: 5000, - mount: async (params: AppMountParameters) => { - const { renderApp } = await import('./anomaly_detection_app'); - const [coreStart] = await core.getStartServices(); - return renderApp(coreStart, params); - }, - }); +export class AnomalyDetectionOpenSearchDashboardsPlugin + implements Plugin +{ + public setup(core: CoreSetup, plugins: any) { + core.application.register({ + id: PLUGIN_NAME, + title: 'Anomaly Detection', + category: { + id: 'opensearch', + label: 'OpenSearch Plugins', + order: 2000, + }, + order: 5000, + mount: async (params: AppMountParameters) => { + const { renderApp } = await import('./anomaly_detection_app'); + const [coreStart] = await core.getStartServices(); + return renderApp(coreStart, params); + }, + }); - // Set the HTTP client so it can be pulled into expression fns to make - // direct server-side calls - setClient(core.http); + // Set the HTTP client so it can be pulled into expression fns to make + // direct server-side calls + setClient(core.http); - // Create context menu actions. Pass core, to access service for flyouts. - const actions = getActions(); + // Create context menu actions. Pass core, to access service for flyouts. + const actions = getActions(); - // Add actions to uiActions - actions.forEach((action) => { - plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); - }); + // Add actions to uiActions + actions.forEach((action) => { + plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); + }); - // registers the expression function used to render anomalies on an Augmented Visualization - plugins.expressions.registerFunction(overlayAnomaliesFunction); - return {}; - } + // registers the expression function used to render anomalies on an Augmented Visualization + plugins.expressions.registerFunction(overlayAnomaliesFunction); + return {}; + } - public start( - core: CoreStart, - {embeddable }: AnomalyDetectionStartDeps - ): AnomalyDetectionOpenSearchDashboardsPluginStart { - setEmbeddable(embeddable); - setOverlays(core.overlays); - return {}; - } -} \ No newline at end of file + public start( + core: CoreStart, + { embeddable, visAugmenter }: AnomalyDetectionStartDeps + ): AnomalyDetectionOpenSearchDashboardsPluginStart { + setEmbeddable(embeddable); + setOverlays(core.overlays); + setSavedFeatureAnywhereLoader(visAugmenter.savedAugmentVisLoader); + setNotifications(core.notifications); + return {}; + } +} diff --git a/public/services.ts b/public/services.ts index 3857a95f..1908f443 100644 --- a/public/services.ts +++ b/public/services.ts @@ -3,7 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { CoreStart, OverlayStart } from '../../../src/core/public'; +import { + CoreStart, + 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'; @@ -14,8 +18,11 @@ export const [getSavedFeatureAnywhereLoader, setSavedFeatureAnywhereLoader] = export const [getClient, setClient] = createGetterSetter('http'); -export const [getEmbeddable, setEmbeddable] = +export const [getEmbeddable, setEmbeddable] = createGetterSetter('Embeddable'); -export const [getOverlays, setOverlays] = +export const [getOverlays, setOverlays] = createGetterSetter('Overlays'); + +export const [getNotifications, setNotifications] = + createGetterSetter('Notifications'); diff --git a/public/utils/constants.ts b/public/utils/constants.ts index 099e6a7e..17a8b86a 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -53,7 +53,8 @@ export const ANOMALY_RESULT_INDEX = '.opendistro-anomaly-results'; export const BASE_DOCS_LINK = 'https://opensearch.org/docs/monitoring-plugins'; -export const AD_DOCS_LINK = 'https://opensearch.org/docs/latest/observing-your-data/ad/index/'; +export const AD_DOCS_LINK = + 'https://opensearch.org/docs/latest/observing-your-data/ad/index/'; export const MAX_DETECTORS = 1000; diff --git a/public/utils/contextMenu/getActions.tsx b/public/utils/contextMenu/getActions.tsx index 4dcb05f6..0c1302e4 100644 --- a/public/utils/contextMenu/getActions.tsx +++ b/public/utils/contextMenu/getActions.tsx @@ -1,3 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + import React from 'react'; import { i18n } from '@osd/i18n'; import { EuiIconType } from '@elastic/eui'; @@ -31,10 +36,10 @@ export const getActions = () => { toMountPoint( overlay.close()} - /> + startingFlyout={startingFlyout} + embeddable={embeddable} + closeFlyout={() => overlay.close()} + /> ), { size: 'm', className: 'context-menu__flyout' } @@ -74,11 +79,8 @@ export const getActions = () => { icon: 'documentation' as EuiIconType, order: 98, onClick: () => { - window.open( - AD_DOCS_LINK, - '_blank' - ); + window.open(AD_DOCS_LINK, '_blank'); }, }, ].map((options) => createADAction({ ...options, grouping })); -}; \ No newline at end of file +};