From 73ace65386e15ce0e625e579c8c50609866ca3ed Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Tue, 29 Nov 2022 12:51:42 -0800 Subject: [PATCH 01/16] Register AD as dashboard option Signed-off-by: Jackie Han --- public/action/ad_dashboard_action.tsx | 49 ++++++ public/{plugin.ts => plugin.tsx} | 36 +++-- public/utils/contextMenu/FormikWrapper.tsx | 9 ++ .../utils/contextMenu/getContextMenuData.tsx | 150 ++++++++++++++++++ 4 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 public/action/ad_dashboard_action.tsx rename public/{plugin.ts => plugin.tsx} (68%) create mode 100644 public/utils/contextMenu/FormikWrapper.tsx create mode 100644 public/utils/contextMenu/getContextMenuData.tsx diff --git a/public/action/ad_dashboard_action.tsx b/public/action/ad_dashboard_action.tsx new file mode 100644 index 000000000..1b457ce51 --- /dev/null +++ b/public/action/ad_dashboard_action.tsx @@ -0,0 +1,49 @@ +import { i18n } from '@osd/i18n'; +import { IEmbeddable } from '../../../../src/plugins/dashboard/public/embeddable_plugin'; +import { + DASHBOARD_CONTAINER_TYPE, + DashboardContainer, +} from '../../../../src/plugins/dashboard/public'; +import { getContextMenuData as getMenuData } from '../utils/contextMenu/getContextMenuData'; +import { IncompatibleActionError, createAction } from '../../../../src/plugins/ui_actions/public'; +import { isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public'; + +export const ACTION_AD = 'ad'; + +function isDashboard(embeddable: IEmbeddable): embeddable is DashboardContainer { + return embeddable.type === DASHBOARD_CONTAINER_TYPE; +} + +export interface ActionContext { + embeddable: IEmbeddable; +} + +export const createADAction = () => + createAction({ + getDisplayName: ({ embeddable }: ActionContext) => { + if (!embeddable.parent || !isDashboard(embeddable.parent)) { + throw new IncompatibleActionError(); + } + return i18n.translate('dashboard.actions.adMenuItem.displayName', { + defaultMessage: 'Anomaly Detection', + }); + }, + type: ACTION_AD, + isCompatible: async ({ embeddable }: ActionContext) => { + const paramsType = embeddable.vis?.params?.type; + const seriesParams = embeddable.vis?.params?.seriesParams || []; + const series = embeddable.vis?.params?.series || []; + const isLineGraph = + seriesParams.find((item) => item.type === 'line') || + series.find((item) => item.chart_type === 'line'); + const isValidVis = isLineGraph && paramsType !== 'table'; + + return Boolean(embeddable.parent && isDashboard(embeddable.parent) && isValidVis); + }, + execute: async ({ embeddable }: ActionContext) => { + if (!isReferenceOrValueEmbeddable(embeddable)) { + throw new IncompatibleActionError(); + } + }, + getContextMenuData: getMenuData, + }); \ No newline at end of file diff --git a/public/plugin.ts b/public/plugin.tsx similarity index 68% rename from public/plugin.ts rename to public/plugin.tsx index 7ee985bff..f5d765333 100644 --- a/public/plugin.ts +++ b/public/plugin.tsx @@ -16,25 +16,24 @@ import { Plugin, PluginInitializerContext, } from '../../../src/core/public'; -import { - AnomalyDetectionOpenSearchDashboardsPluginSetup, - AnomalyDetectionOpenSearchDashboardsPluginStart, -} from '.'; +import { createADAction, ACTION_AD } from './actions/ad_dashboard_action'; +import { CONTEXT_MENU_TRIGGER } from '../../../src/plugins/embeddable/public'; + +declare module '../../../src/plugins/ui_actions/public' { + export interface ActionContextMapping { + [ACTION_AD]: {}; + } +} export class AnomalyDetectionOpenSearchDashboardsPlugin implements - Plugin< - AnomalyDetectionOpenSearchDashboardsPluginSetup, - AnomalyDetectionOpenSearchDashboardsPluginStart - > + Plugin { constructor(private readonly initializerContext: PluginInitializerContext) { // can retrieve config from initializerContext } - public setup( - core: CoreSetup - ): AnomalyDetectionOpenSearchDashboardsPluginSetup { + public setup(core: CoreSetup, plugins) { core.application.register({ id: 'anomaly-detection-dashboards', title: 'Anomaly Detection', @@ -50,12 +49,15 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin return renderApp(coreStart, params); }, }); - return {}; - } - public start( - core: CoreStart - ): AnomalyDetectionOpenSearchDashboardsPluginStart { - return {}; + const alertingAction = createADAction(); + const { uiActions } = plugins; + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, alertingAction); } + + public start() {} + + public stop() {} } + + diff --git a/public/utils/contextMenu/FormikWrapper.tsx b/public/utils/contextMenu/FormikWrapper.tsx new file mode 100644 index 000000000..75e5f908a --- /dev/null +++ b/public/utils/contextMenu/FormikWrapper.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import { useFormik, FormikProvider } from 'formik'; + +const FormikWrapper = ({ getFormikOptions, children, ...props }) => { + const formik = useFormik(getFormikOptions()); + return {children}; +}; + +export default FormikWrapper; \ No newline at end of file diff --git a/public/utils/contextMenu/getContextMenuData.tsx b/public/utils/contextMenu/getContextMenuData.tsx new file mode 100644 index 000000000..8408091d4 --- /dev/null +++ b/public/utils/contextMenu/getContextMenuData.tsx @@ -0,0 +1,150 @@ +import React from 'react'; +import { + EuiLink, + EuiText, + EuiCallOut, + EuiSpacer, + EuiIcon, + EuiToolTip, + EuiContextMenuPanelDescriptor, +} from '@elastic/eui'; +import { v4 as uuid } from 'uuid'; +import './styles.scss'; +import { getInitialValues } from './helpers'; +import { GetActionContextMenuDataArgs, Action } from '../../../../../src/plugins/ui_actions/public'; +import FormikWrapper from './FormikWrapper'; + +export const getContextMenuData: Action['getContextMenuData'] = ( + options: GetActionContextMenuDataArgs +) => { + const initialValues = getInitialValues(); + const { anomalies, detectors } = initialValues; + const getFormikOptions = () => ({ + initialValues, + onSubmit: (values) => { + console.log(values); + }, + }); + const detectorId = uuid(); + const createAnomalyDetectorId = uuid(); + const manageDetectorId = uuid(); + const viewAnomaliesByTriggerId = uuid(); + const additionalFirstPanelGroups = [ + { + name: 'Initial group', + order: 11, + items: [ + { + name: 'Anomaly Detection', + icon: 'nested', + panel: detectorId, + }, + ], + }, + { + name: 'View events', + isTitleVisible: true, + order: 10, + items: [ + { + name: anomalies.length ? ( + `Anomalies (${anomalies.length})` + ) : ( + + Anomalies{' '} + + + + + + + ), + icon: 'nested', + panel: viewAnomaliesByTriggerId, + className: anomalies.length ? '' : 'ad-dashboards-context-menu__no-action', + disabled: !anomalies.length, + }, + ], + }, + ]; + const additionalPanels: EuiContextMenuPanelDescriptor[] = [ + { + id: detectorId, + width: 300, + title: 'Anomaly Detection', + items: [ + { + name: 'Create anomaly detector', + icon: 'plusInCircle', + panel: createAnomalyDetectorId, + }, + { + name: `Manage detectors${detectors.length ? ` (${detectors.length})` : ''}`, + icon: 'wrench', + panel: manageDetectorId, + }, + { + isSeparator: true, + key: 'sep', + }, + { + className: 'ad-dashboards-context-menu__text-content', + name: ( + <> + + Learn more about{' '} + + Anomaly Detection Anywhere + + + + + + Share your feedback for the feature by creating on issue on{' '} + + GitHub + + + + + ), + }, + ], + }, + { + id: createAnomalyDetectorId, + width: 400, + title: 'Create anomaly detector', + content: ( + + + + ), + }, + { + id: manageDetectorId, + width: 400, + title: 'Manage detectors', + content: ( + + + + ), + }, + { + id: viewAnomaliesByTriggerId, + width: 400, + title: 'View Anomalies by Trigger', + content: ( + + + + ), + }, + ]; + + return { + additionalFirstPanelGroups, + additionalPanels, + }; +}; From 56dd51341aa63fc2c33c1fa6e8e9132dc8671240 Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Tue, 13 Dec 2022 09:23:58 -0700 Subject: [PATCH 02/16] Update OPTIONS page to remove Anomalies section Signed-off-by: Jackie Han --- .../utils/contextMenu/getContextMenuData.tsx | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/public/utils/contextMenu/getContextMenuData.tsx b/public/utils/contextMenu/getContextMenuData.tsx index 8408091d4..ed498b964 100644 --- a/public/utils/contextMenu/getContextMenuData.tsx +++ b/public/utils/contextMenu/getContextMenuData.tsx @@ -41,31 +41,6 @@ export const getContextMenuData: Action['getContextMenuData'] = ( }, ], }, - { - name: 'View events', - isTitleVisible: true, - order: 10, - items: [ - { - name: anomalies.length ? ( - `Anomalies (${anomalies.length})` - ) : ( - - Anomalies{' '} - - - - - - - ), - icon: 'nested', - panel: viewAnomaliesByTriggerId, - className: anomalies.length ? '' : 'ad-dashboards-context-menu__no-action', - disabled: !anomalies.length, - }, - ], - }, ]; const additionalPanels: EuiContextMenuPanelDescriptor[] = [ { @@ -131,16 +106,6 @@ export const getContextMenuData: Action['getContextMenuData'] = ( ), }, - { - id: viewAnomaliesByTriggerId, - width: 400, - title: 'View Anomalies by Trigger', - content: ( - - - - ), - }, ]; return { From 62a50f224c87861b755f9fadfa52f3893791d747 Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Tue, 20 Dec 2022 14:05:02 -0700 Subject: [PATCH 03/16] create ad general page Signed-off-by: Jackie Han --- .../CreateAnomalyDetector/index.js | 89 +++++++++++++++++++ .../CreateAnomalyDetector/styles.scss | 5 ++ .../ContextMenu/Notifications/index.js | 23 +++++ public/plugin.tsx | 9 +- .../utils/contextMenu/getContextMenuData.tsx | 33 +++---- public/utils/contextMenu/styles.scss | 25 ++++++ 6 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 public/components/ContextMenu/CreateAnomalyDetector/index.js create mode 100644 public/components/ContextMenu/CreateAnomalyDetector/styles.scss create mode 100644 public/components/ContextMenu/Notifications/index.js create mode 100644 public/utils/contextMenu/styles.scss diff --git a/public/components/ContextMenu/CreateAnomalyDetector/index.js b/public/components/ContextMenu/CreateAnomalyDetector/index.js new file mode 100644 index 000000000..5fa9a4c38 --- /dev/null +++ b/public/components/ContextMenu/CreateAnomalyDetector/index.js @@ -0,0 +1,89 @@ +import React from 'react'; +import { + EuiLink, + EuiText, + EuiHorizontalRule, + EuiSpacer, + EuiPanel, + EuiIcon, + EuiFlexItem, + EuiFlexGroup, + EuiButton, +} from '@elastic/eui'; +import { useField, useFormikContext } from 'formik'; +import Notifications from '../Notifications'; +import FormikWrapper from '../../../utils/contextMenu/FormikWrapper'; +import './styles.scss'; +import { toMountPoint } from '../../../../../../src/plugins/opensearch_dashboards_react/public'; + +export const CreateAnomalyDetector = (props) => { + const { overlays, closeMenu } = props; + const { values } = useFormikContext(); + const [name] = useField('name'); + + const onOpenAdvanced = () => { + // Prepare advanced flyout with new formik provider of current values + const getFormikOptions = () => ({ + initialValues: values, + onSubmit: (values) => { + console.log(values); + }, + }); + + const flyout = overlays.openFlyout( + toMountPoint( + + flyout.close() }} /> + + ) + ); + + // Close context menu + closeMenu(); + }; + + return ( + <> + + + {name.value} + + + + Detector interval: 10 minutes; Window delay: 1 minute + + + + {/* not sure about the select features part */} + + + + + + + + + + Advanced settings + + + + + + Create + + + + + + ); +}; \ No newline at end of file diff --git a/public/components/ContextMenu/CreateAnomalyDetector/styles.scss b/public/components/ContextMenu/CreateAnomalyDetector/styles.scss new file mode 100644 index 000000000..2316fd381 --- /dev/null +++ b/public/components/ContextMenu/CreateAnomalyDetector/styles.scss @@ -0,0 +1,5 @@ +.create-anomaly-detector { + &__create { + align-self: flex-end; + } +} \ No newline at end of file diff --git a/public/components/ContextMenu/Notifications/index.js b/public/components/ContextMenu/Notifications/index.js new file mode 100644 index 000000000..b0762c415 --- /dev/null +++ b/public/components/ContextMenu/Notifications/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { EuiLink, EuiText, EuiSpacer, EuiPanel, EuiIcon, EuiFormRow } from '@elastic/eui'; + +export const Notifications = () => ( + <> + + + + The anomalies will appear on the visualization when the anomaly grade is above 0.7 and anomaly confidence is below 0.7. + Additional notification can be configured. + + + + + + + + Add notifications + + + + +); \ No newline at end of file diff --git a/public/plugin.tsx b/public/plugin.tsx index f5d765333..7f7969522 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -12,12 +12,11 @@ import { AppMountParameters, CoreSetup, - CoreStart, Plugin, PluginInitializerContext, } from '../../../src/core/public'; -import { createADAction, ACTION_AD } from './actions/ad_dashboard_action'; import { CONTEXT_MENU_TRIGGER } from '../../../src/plugins/embeddable/public'; +import { ACTION_AD, createADAction } from './action/ad_dashboard_action'; declare module '../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -45,14 +44,14 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin order: 5000, mount: async (params: AppMountParameters) => { const { renderApp } = await import('./anomaly_detection_app'); - const [coreStart, depsStart] = await core.getStartServices(); + const [coreStart] = await core.getStartServices(); return renderApp(coreStart, params); }, }); - const alertingAction = createADAction(); + const adAction = createADAction(); const { uiActions } = plugins; - uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, alertingAction); + uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, adAction); } public start() {} diff --git a/public/utils/contextMenu/getContextMenuData.tsx b/public/utils/contextMenu/getContextMenuData.tsx index ed498b964..b2fd869f0 100644 --- a/public/utils/contextMenu/getContextMenuData.tsx +++ b/public/utils/contextMenu/getContextMenuData.tsx @@ -4,31 +4,20 @@ import { EuiText, EuiCallOut, EuiSpacer, - EuiIcon, - EuiToolTip, EuiContextMenuPanelDescriptor, } from '@elastic/eui'; import { v4 as uuid } from 'uuid'; +import CreateAnomalyDetector from '../../components/contextMenu/CreateAnomalyDetector'; import './styles.scss'; -import { getInitialValues } from './helpers'; import { GetActionContextMenuDataArgs, Action } from '../../../../../src/plugins/ui_actions/public'; import FormikWrapper from './FormikWrapper'; export const getContextMenuData: Action['getContextMenuData'] = ( options: GetActionContextMenuDataArgs ) => { - const initialValues = getInitialValues(); - const { anomalies, detectors } = initialValues; - const getFormikOptions = () => ({ - initialValues, - onSubmit: (values) => { - console.log(values); - }, - }); const detectorId = uuid(); const createAnomalyDetectorId = uuid(); const manageDetectorId = uuid(); - const viewAnomaliesByTriggerId = uuid(); const additionalFirstPanelGroups = [ { name: 'Initial group', @@ -96,16 +85,16 @@ export const getContextMenuData: Action['getContextMenuData'] = ( ), }, - { - id: manageDetectorId, - width: 400, - title: 'Manage detectors', - content: ( - - - - ), - }, + // { + // id: manageDetectorId, + // width: 400, + // title: 'Manage detectors', + // content: ( + // + // + // + // ), + // }, ]; return { diff --git a/public/utils/contextMenu/styles.scss b/public/utils/contextMenu/styles.scss new file mode 100644 index 000000000..69ebfcb91 --- /dev/null +++ b/public/utils/contextMenu/styles.scss @@ -0,0 +1,25 @@ +@import '@elastic/eui/src/global_styling/variables/index'; + +.ad-dashboards-context-menu { + &__text-content { + &:hover { + text-decoration: none; + } + } + + &__no-action { + cursor: default; + + &:hover, + &:active { + text-decoration: none; + background-color: inherit; + } + } + + &__view-events-text { + h5 { + color: inherit; + } + } +} \ No newline at end of file From 5de53a2a3d12beda708bc2fffefe9912145d2202 Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Fri, 13 Jan 2023 09:14:31 -0800 Subject: [PATCH 04/16] update context menu grouping Signed-off-by: Jackie Han --- public/action/ad_dashboard_action.tsx | 38 +++++-- .../FormikWrapper/index.js} | 2 +- public/plugin.tsx | 18 +-- public/utils/contextMenu/action.tsx | 71 ++++++++++++ .../utils/contextMenu/getContextMenuData.tsx | 104 ------------------ public/utils/contextMenu/helper.js | 13 +++ 6 files changed, 123 insertions(+), 123 deletions(-) rename public/{utils/contextMenu/FormikWrapper.tsx => components/FeatureAnywhereContextMenu/FormikWrapper/index.js} (90%) create mode 100644 public/utils/contextMenu/action.tsx delete mode 100644 public/utils/contextMenu/getContextMenuData.tsx create mode 100644 public/utils/contextMenu/helper.js diff --git a/public/action/ad_dashboard_action.tsx b/public/action/ad_dashboard_action.tsx index 1b457ce51..f5a5db157 100644 --- a/public/action/ad_dashboard_action.tsx +++ b/public/action/ad_dashboard_action.tsx @@ -1,12 +1,11 @@ -import { i18n } from '@osd/i18n'; import { IEmbeddable } from '../../../../src/plugins/dashboard/public/embeddable_plugin'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer, } from '../../../../src/plugins/dashboard/public'; -import { getContextMenuData as getMenuData } from '../utils/contextMenu/getContextMenuData'; -import { IncompatibleActionError, createAction } from '../../../../src/plugins/ui_actions/public'; +import { IncompatibleActionError, createAction, Action } from '../../../../src/plugins/ui_actions/public'; import { isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public'; +import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; export const ACTION_AD = 'ad'; @@ -18,17 +17,35 @@ export interface ActionContext { embeddable: IEmbeddable; } -export const createADAction = () => +export interface CreateOptions { + grouping: Action['grouping']; + title: string; + icon: EuiIconType; + id: string; + order: number; + onClick: Function; +} + +export const createADAction = ({ + grouping, + title, + icon, + id, + order, + onClick, +}: CreateOptions) => createAction({ + id, + order, getDisplayName: ({ embeddable }: ActionContext) => { if (!embeddable.parent || !isDashboard(embeddable.parent)) { throw new IncompatibleActionError(); } - return i18n.translate('dashboard.actions.adMenuItem.displayName', { - defaultMessage: 'Anomaly Detection', - }); + return title; }, + getIconType: () => icon, type: ACTION_AD, + grouping, isCompatible: async ({ embeddable }: ActionContext) => { const paramsType = embeddable.vis?.params?.type; const seriesParams = embeddable.vis?.params?.seriesParams || []; @@ -37,13 +54,14 @@ export const createADAction = () => seriesParams.find((item) => item.type === 'line') || series.find((item) => item.chart_type === 'line'); const isValidVis = isLineGraph && paramsType !== 'table'; - return Boolean(embeddable.parent && isDashboard(embeddable.parent) && isValidVis); }, execute: async ({ embeddable }: ActionContext) => { if (!isReferenceOrValueEmbeddable(embeddable)) { throw new IncompatibleActionError(); } + + onClick({ embeddable }); }, - getContextMenuData: getMenuData, - }); \ No newline at end of file + }); + \ No newline at end of file diff --git a/public/utils/contextMenu/FormikWrapper.tsx b/public/components/FeatureAnywhereContextMenu/FormikWrapper/index.js similarity index 90% rename from public/utils/contextMenu/FormikWrapper.tsx rename to public/components/FeatureAnywhereContextMenu/FormikWrapper/index.js index 75e5f908a..c76b1e605 100644 --- a/public/utils/contextMenu/FormikWrapper.tsx +++ b/public/components/FeatureAnywhereContextMenu/FormikWrapper/index.js @@ -6,4 +6,4 @@ const FormikWrapper = ({ getFormikOptions, children, ...props }) => { return {children}; }; -export default FormikWrapper; \ No newline at end of file +export default FormikWrapper; diff --git a/public/plugin.tsx b/public/plugin.tsx index 7f7969522..bcf0b14c3 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -17,6 +17,8 @@ import { } from '../../../src/core/public'; import { CONTEXT_MENU_TRIGGER } from '../../../src/plugins/embeddable/public'; import { ACTION_AD, createADAction } from './action/ad_dashboard_action'; +import { PLUGIN_NAME } from './utils/constants'; +import { getActions } from './utils/contextMenu/action'; declare module '../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -28,13 +30,9 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin implements Plugin { - constructor(private readonly initializerContext: PluginInitializerContext) { - // can retrieve config from initializerContext - } - public setup(core: CoreSetup, plugins) { core.application.register({ - id: 'anomaly-detection-dashboards', + id: PLUGIN_NAME, title: 'Anomaly Detection', category: { id: 'opensearch', @@ -49,9 +47,13 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin }, }); - const adAction = createADAction(); - const { uiActions } = plugins; - uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, adAction); + // Create context menu actions. Pass core, to access service for flyouts. + const actions = getActions({ core }); + + // Add actions to uiActions + actions.forEach((action) => { + plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action); + }); } public start() {} diff --git a/public/utils/contextMenu/action.tsx b/public/utils/contextMenu/action.tsx new file mode 100644 index 000000000..98031018f --- /dev/null +++ b/public/utils/contextMenu/action.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; +import { Action } from '../../../../../src/plugins/ui_actions/public'; +import { createADAction } from 'public/action/ad_dashboard_action'; + +// This is used to create all actions in the same context menu +const grouping: Action['grouping'] = [ + { + id: 'ad-dashboard-context-menu', + getDisplayName: () => 'Anomaly Detector', + getIconType: () => 'apmTrace', + order: 200, + }, +]; + +export const getActions = ({ core }) => + [ + { + grouping, + id: 'createAnomalyDetector', + title: i18n.translate( + 'dashboard.actions.adMenuItem.createAnomalyDetector.displayName', + { + defaultMessage: 'Create anomaly detector', + } + ), + icon: 'plusInCircle' as EuiIconType, + order: 100, + onClick: async ({ embeddable }) => { + console.log("create ad"); + + // openFlyout( + // toMountPoint( + // + // < {...{ embeddable }} /> + // + // ), + // { size: 'l' } + // ); + + }, + }, + { + grouping, + id: 'manageAnomalyDetector', + title: i18n.translate('dashboard.actions.alertingMenuItem.manageAnomalyDetector.displayName', { + defaultMessage: 'Manage anomaly detector', + }), + icon: 'wrench' as EuiIconType, + order: 99, + onClick: async ({ embeddable }) => { + console.log("manage ad"); + }, + }, + { + id: 'documentation', + title: i18n.translate('dashboard.actions.adMenuItem.documentation.displayName', { + defaultMessage: 'Documentation', + }), + icon: 'documentation' as EuiIconType, + order: 98, + onClick: () => { + window.open( + 'https://opensearch.org/docs/latest/monitoring-plugins/alerting/index/', + '_blank' + ); + }, + }, + ] + //.map((options) => createADAction({ ...options, grouping })); diff --git a/public/utils/contextMenu/getContextMenuData.tsx b/public/utils/contextMenu/getContextMenuData.tsx deleted file mode 100644 index b2fd869f0..000000000 --- a/public/utils/contextMenu/getContextMenuData.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import { - EuiLink, - EuiText, - EuiCallOut, - EuiSpacer, - EuiContextMenuPanelDescriptor, -} from '@elastic/eui'; -import { v4 as uuid } from 'uuid'; -import CreateAnomalyDetector from '../../components/contextMenu/CreateAnomalyDetector'; -import './styles.scss'; -import { GetActionContextMenuDataArgs, Action } from '../../../../../src/plugins/ui_actions/public'; -import FormikWrapper from './FormikWrapper'; - -export const getContextMenuData: Action['getContextMenuData'] = ( - options: GetActionContextMenuDataArgs -) => { - const detectorId = uuid(); - const createAnomalyDetectorId = uuid(); - const manageDetectorId = uuid(); - const additionalFirstPanelGroups = [ - { - name: 'Initial group', - order: 11, - items: [ - { - name: 'Anomaly Detection', - icon: 'nested', - panel: detectorId, - }, - ], - }, - ]; - const additionalPanels: EuiContextMenuPanelDescriptor[] = [ - { - id: detectorId, - width: 300, - title: 'Anomaly Detection', - items: [ - { - name: 'Create anomaly detector', - icon: 'plusInCircle', - panel: createAnomalyDetectorId, - }, - { - name: `Manage detectors${detectors.length ? ` (${detectors.length})` : ''}`, - icon: 'wrench', - panel: manageDetectorId, - }, - { - isSeparator: true, - key: 'sep', - }, - { - className: 'ad-dashboards-context-menu__text-content', - name: ( - <> - - Learn more about{' '} - - Anomaly Detection Anywhere - - - - - - Share your feedback for the feature by creating on issue on{' '} - - GitHub - - - - - ), - }, - ], - }, - { - id: createAnomalyDetectorId, - width: 400, - title: 'Create anomaly detector', - content: ( - - - - ), - }, - // { - // id: manageDetectorId, - // width: 400, - // title: 'Manage detectors', - // content: ( - // - // - // - // ), - // }, - ]; - - return { - additionalFirstPanelGroups, - additionalPanels, - }; -}; diff --git a/public/utils/contextMenu/helper.js b/public/utils/contextMenu/helper.js new file mode 100644 index 000000000..669ace08d --- /dev/null +++ b/public/utils/contextMenu/helper.js @@ -0,0 +1,13 @@ +export const getInitialValues = () => ({ + ...{ ...FORMIK_INITIAL_VALUES, name: 'Monitor 1' }, + triggers: [ + { + ...FORMIK_INITIAL_TRIGGER_CONDITION_VALUES, + name: 'New trigger', + id: Date.now(), + severity: '1', + }, + ], + monitors: getInitialMonitors(), + alerts: getInitialAlerts(), +}); \ No newline at end of file From 1784bbd9dae2a2355b872f89a902438eebf1856f Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Fri, 20 Jan 2023 09:37:35 -0800 Subject: [PATCH 05/16] Correct flyout and add partial create detector page Signed-off-by: Jackie Han --- .../CreateAnomalyDetector/index.js | 79 +++++++++++++++++++ .../CreateAnomalyDetector/styles.scss | 25 ++++++ public/utils/contextMenu/action.tsx | 33 +++++--- 3 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js new file mode 100644 index 000000000..4247992f2 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import { + EuiText, + EuiHorizontalRule, + EuiFlexItem, + EuiFlexGroup, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiTitle, + EuiAccordion, +} from '@elastic/eui'; +import './styles.scss'; +import { EmbeddablePanel } from '../../../../../../src/plugins/embeddable/public'; + +const accordions = ['detectorDetails', 'features', 'categoricalFields', 'shingleSize', 'alerts', 'triggers'].reduce( + (acc, cur) => ({ ...acc, [cur]: cur }), + {} +); + +function CreateAnomalyDetector({ embeddable }) { + const [accordionOpen, setAccordionOpen] = useState(accordions.triggers); + + return ( +
+ + +

Create anomaly detector

+
+
+ + + +
+ Promise.resolve([])} + getAllEmbeddableFactories={() => []} + getEmbeddableFactory={() => null} + notifications={{}} + application={{}} + overlays={{}} + inspector={{ isAvailable: () => null }} + SavedObjectFinder={() => null} + /> +
+
+ + +
Detector Details
+ {/* {accordionOpen !== accordions.detectorDetails && ( + <> + {name.value} + + {anomalyDetectorText} + + + )} */} + + } + forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} + onToggle={() => + setAccordionOpen( + accordionOpen !== accordions.detectorDetails && accordions.detectorDetails + ) + } + > +
+ +
+
+
+
+ ) +} +export default CreateAnomalyDetector; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss new file mode 100644 index 000000000..bc3581c35 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss @@ -0,0 +1,25 @@ +.create-anomaly-detector { + .euiFlexItem.create-anomaly-detector__aside { + width: 400px; + flex-grow: 0; + flex-shrink: 0; + flex-basis: auto; + } + + .euiAccordion__button { + align-items: flex-start; + + &:hover, + &:focus { + text-decoration: none; + + h6 { + text-decoration: underline; + } + } + } + + &__vis { + height: 400px; + } +} diff --git a/public/utils/contextMenu/action.tsx b/public/utils/contextMenu/action.tsx index 98031018f..364d4c3b8 100644 --- a/public/utils/contextMenu/action.tsx +++ b/public/utils/contextMenu/action.tsx @@ -1,8 +1,11 @@ import React from 'react'; import { i18n } from '@osd/i18n'; import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; +import { toMountPoint } from '../../../../../src/plugins/opensearch_dashboards_react/public'; import { Action } from '../../../../../src/plugins/ui_actions/public'; -import { createADAction } from 'public/action/ad_dashboard_action'; +import { createADAction } from '../../action/ad_dashboard_action'; +import FormikWrapper from '../../components/FeatureAnywhereContextMenu/FormikWrapper'; +import CreateAnomalyDetector from '../../components/FeatureAnywhereContextMenu/CreateAnomalyDetector'; // This is used to create all actions in the same context menu const grouping: Action['grouping'] = [ @@ -28,17 +31,21 @@ export const getActions = ({ core }) => icon: 'plusInCircle' as EuiIconType, order: 100, onClick: async ({ embeddable }) => { - console.log("create ad"); - - // openFlyout( - // toMountPoint( - // - // < {...{ embeddable }} /> - // - // ), - // { size: 'l' } - // ); - + const services = await core.getStartServices(); + const openFlyout = services[0].overlays.openFlyout; + // const getFormikOptions = () => ({ + // initialValues: getInitialValues(), + // onSubmit: (values) => { + // console.log('Submitting createAlertingMonitor'); + // console.log(values); + // }, + // }); + openFlyout( + toMountPoint( + + ), + { size: 'l' } + ); }, }, { @@ -68,4 +75,4 @@ export const getActions = ({ core }) => }, }, ] - //.map((options) => createADAction({ ...options, grouping })); + .map((options) => createADAction({ ...options, grouping })); From 2adaaf89633737208c52d467c84e92653adb22af Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Mon, 30 Jan 2023 22:55:41 -0800 Subject: [PATCH 06/16] Add Detector Detail and Feature Accordion Signed-off-by: Jackie Han --- .../DetectorDetails/index.js | 87 ++++++++++++++++++ .../CreateAnomalyDetector/Features/index.js | 89 +++++++++++++++++++ .../CreateAnomalyDetector/index.js | 88 +++++++++++++++--- 3 files changed, 254 insertions(+), 10 deletions(-) create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/Features/index.js diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js new file mode 100644 index 000000000..0fa7d9ba5 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js @@ -0,0 +1,87 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + EuiLink, + EuiText, + EuiSpacer, + EuiPanel, + EuiIcon, + EuiAccordion, + EuiFormRow, + EuiFieldText, + EuiSelect, + EuiCheckbox +} from '@elastic/eui'; +const DetectorDetails = () => { + const intervalOptions = [ + { value: 'option_one', text: '10 minutes' }, + { value: 'option_two', text: '1 minutes' }, + { value: 'option_three', text: '5 minutes' }, + ]; + const [intervalValue, setIntervalalue] = useState(intervalOptions[0].value); + const intervalOnChange = e => { + setIntervalalue(e.target.value); + }; + + const delayOptions = [ + { value: 'option_one', text: '10 minutes' }, + { value: 'option_two', text: '1 minutes' }, + { value: 'option_three', text: '5 minutes' }, + ]; + const [delayValue, setDelayValue] = useState(delayOptions[0].value); + const delayOnChange = e => { + setDelayValue(e.target.value); + }; + + const [checked, setChecked] = useState(false); + const onCustomerResultIndexCheckboxChange = e => { + setChecked(e.target.checked); + }; + + return ( + <> + + + + + + + + + + + + intervalOnChange(e)} + /> + + + delayOnChange(e)} + /> + + + + + onCustomerResultIndexCheckboxChange(e)} + /> + + + + ); +} + +export default DetectorDetails; \ No newline at end of file diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/Features/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/Features/index.js new file mode 100644 index 000000000..1bdb6dd2a --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/Features/index.js @@ -0,0 +1,89 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + EuiHorizontalRule, + EuiTextColor, + EuiPanel, + EuiIcon, + EuiAccordion, + EuiFormRow, + EuiFieldText, + EuiSelect, + EuiFlexItem, + EuiFlexGroup +} from '@elastic/eui'; + +const Features = () => { + const aggMethodOptions = [ + { value: 'avg', text: 'AVG' }, + { value: 'sum', text: 'SUM' }, + ]; + const [aggMethodValue, setAggMethodValue] = useState(aggMethodOptions[0].value); + const aggMethodOnChange = e => { + setAggMethodValue(e.target.value); + }; + + return ( + <> + + + + + + Find anomalies based on + + + + + + + + Aggregation method + + aggMethodOnChange(e)} + /> + + + + + + + + + Find anomalies based on + + + + + + + + Aggregation method + + aggMethodOnChange(e)} + /> + + + + {/* + +

Selected aggration method is incompatible

+
*/} +
+ + + ); +} + +export default Features; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js index 4247992f2..58331ffc5 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiText, EuiHorizontalRule, @@ -8,9 +8,12 @@ import { EuiFlyoutBody, EuiTitle, EuiAccordion, + EuiSpacer } from '@elastic/eui'; import './styles.scss'; import { EmbeddablePanel } from '../../../../../../src/plugins/embeddable/public'; +import DetectorDetails from './DetectorDetails'; +import Features from './Features'; const accordions = ['detectorDetails', 'features', 'categoricalFields', 'shingleSize', 'alerts', 'triggers'].reduce( (acc, cur) => ({ ...acc, [cur]: cur }), @@ -20,6 +23,41 @@ const accordions = ['detectorDetails', 'features', 'categoricalFields', 'shingle function CreateAnomalyDetector({ embeddable }) { const [accordionOpen, setAccordionOpen] = useState(accordions.triggers); + // useEffect(() => { + // async function createVisEmbeddable() { + // try { + // const getFactory = getEmbeddable().getEmbeddableFactory('visualization'); + + // // fetching the current context from the data plugin + // const contextInput = { + // filters: getQueryService().filterManager.getFilters(), + // query: getQueryService().queryString.getQuery(), + // timeRange: getQueryService().timefilter.timefilter.getTime(), + // }; + + // const embeddable = (await getFactory?.createFromSavedObject( + // props.savedObjectId, + // contextInput + // )) as IEmbeddable | ErrorEmbeddable; + + // // updating the input so we don't auto-refresh + // embeddable.updateInput({ + // // @ts-ignore + // refreshConfig: { + // value: 0, + // pause: true, + // }, + // }); + + // setEmbeddableObj(embeddable); + // } catch (err) { + // console.log(err); + // } + // } + // createVisEmbeddable(); + // // TODO: add more if needed + // }, [props.savedObjectId]); + return (
@@ -51,16 +89,9 @@ function CreateAnomalyDetector({ embeddable }) { buttonContent={
Detector Details
- {/* {accordionOpen !== accordions.detectorDetails && ( - <> - {name.value} - - {anomalyDetectorText} - - - )} */}
} + initialIsOpen={true} forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} onToggle={() => setAccordionOpen( @@ -68,8 +99,45 @@ function CreateAnomalyDetector({ embeddable }) { ) } > + + + + + +
Features
+ + } + initialIsOpen={true} + // forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} + // onToggle={() => + // setAccordionOpen( + // accordionOpen !== accordions.detectorDetails && accordions.detectorDetails + // ) + // } + > + +
+ + +
Shingle Size
+ + } + initialIsOpen={false} + // forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} + // onToggle={() => + // setAccordionOpen( + // accordionOpen !== accordions.detectorDetails && accordions.detectorDetails + // ) + // } + > + {/* */}
- From 4ddfea7d4dabb44e0fce0e473eb748cb13e787ff Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Fri, 3 Mar 2023 11:38:15 -0800 Subject: [PATCH 07/16] new commit Signed-off-by: Jackie Han --- .../DetectorDetails/index.js | 4 +- .../ShingleSize/index.js | 38 +++++ .../CreateAnomalyDetector/index.js | 156 +++++++++--------- 3 files changed, 119 insertions(+), 79 deletions(-) create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js index 0fa7d9ba5..c97d52995 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js @@ -46,9 +46,7 @@ const DetectorDetails = () => { /> - + diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js new file mode 100644 index 000000000..8e435f744 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js @@ -0,0 +1,38 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + EuiHorizontalRule, + EuiTextColor, + EuiPanel, + EuiIcon, + EuiAccordion, + EuiFormRow, + EuiFieldText, + EuiSelect, + EuiFlexItem, + EuiFlexGroup +} from '@elastic/eui'; + +const ShingleSize = () => { + + return ( + <> + +

+ + Set the number of intervals to consider in a detection window for your model. + The anomaly detector expects the shingle size to be in the range of 1 and 60. + The default shingle size is 8. We recommend that you don't choose 1 unless you have + two or more features. Smaller values might increase recall but also false positives. + Larger values might be useful for ignoring noise in a signal. + +

+ + + +
+ + + ); +} + +export default ShingleSize; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js index 58331ffc5..e53ffd947 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js @@ -8,14 +8,16 @@ import { EuiFlyoutBody, EuiTitle, EuiAccordion, - EuiSpacer + EuiSpacer, + EuiFlyout } from '@elastic/eui'; import './styles.scss'; import { EmbeddablePanel } from '../../../../../../src/plugins/embeddable/public'; import DetectorDetails from './DetectorDetails'; import Features from './Features'; +import ShingleSize from './ShingleSize'; -const accordions = ['detectorDetails', 'features', 'categoricalFields', 'shingleSize', 'alerts', 'triggers'].reduce( +const accordions = ['detectorDetails', 'features', 'shingleSize', 'alerts', 'triggers'].reduce( (acc, cur) => ({ ...acc, [cur]: cur }), {} ); @@ -60,87 +62,89 @@ function CreateAnomalyDetector({ embeddable }) { return (
- - -

Create anomaly detector

-
-
- - - -
- Promise.resolve([])} - getAllEmbeddableFactories={() => []} - getEmbeddableFactory={() => null} - notifications={{}} - application={{}} - overlays={{}} - inspector={{ isAvailable: () => null }} - SavedObjectFinder={() => null} - /> -
-
- - -
Detector Details
- - } - initialIsOpen={true} - forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} - onToggle={() => - setAccordionOpen( - accordionOpen !== accordions.detectorDetails && accordions.detectorDetails - ) - } - > - - -
- - + + +

Create anomaly detector

+
+
+ + + +
+ Promise.resolve([])} + getAllEmbeddableFactories={() => []} + getEmbeddableFactory={() => null} + notifications={{}} + application={{}} + overlays={{}} + inspector={{ isAvailable: () => null }} + SavedObjectFinder={() => null} + /> +
+
+ + -
Features
+
DETECTOR DETAILS
} initialIsOpen={true} - // forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} - // onToggle={() => - // setAccordionOpen( - // accordionOpen !== accordions.detectorDetails && accordions.detectorDetails - // ) - // } - > - -
- - -
Shingle Size
- + forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} + onToggle={() => + setAccordionOpen( + accordionOpen !== accordions.detectorDetails && accordions.detectorDetails + ) } - initialIsOpen={false} - // forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} - // onToggle={() => - // setAccordionOpen( - // accordionOpen !== accordions.detectorDetails && accordions.detectorDetails - // ) - // } > - {/* */} -
-
-
-
+ + +
+ + +
FEATURES
+ + } + initialIsOpen={true} + // forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} + // onToggle={() => + // setAccordionOpen( + // accordionOpen !== accordions.detectorDetails && accordions.detectorDetails + // ) + // } + > + +
+ + +
SHINGLE SIZE
+ + } + initialIsOpen={false} + // forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} + // onToggle={() => + // setAccordionOpen( + // accordionOpen !== accordions.detectorDetails && accordions.detectorDetails + // ) + // } + > + +
+
+
+
+
) } From 7ba3cb77747404dea11e4e5f86e89b7af3354b36 Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Mon, 20 Mar 2023 10:59:48 -0700 Subject: [PATCH 08/16] setup create page with checkables Signed-off-by: Jackie Han --- opensearch_dashboards.json | 9 +- .../ShingleSize/index.js | 38 -------- .../CreateAnomalyDetector/index.js | 86 +++++++------------ 3 files changed, 39 insertions(+), 94 deletions(-) delete mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index 6868b4ed1..a88771b0a 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -3,7 +3,14 @@ "version": "3.0.0.0", "opensearchDashboardsVersion": "3.0.0", "configPath": ["anomaly_detection_dashboards"], - "requiredPlugins": ["navigation"], + "requiredPlugins": [ + "navigation", + "uiActions", + "dashboard", + "embeddable", + "opensearchDashboardsReact", + "savedObjects" + ], "optionalPlugins": [], "server": true, "ui": true diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js deleted file mode 100644 index 8e435f744..000000000 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js +++ /dev/null @@ -1,38 +0,0 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { - EuiHorizontalRule, - EuiTextColor, - EuiPanel, - EuiIcon, - EuiAccordion, - EuiFormRow, - EuiFieldText, - EuiSelect, - EuiFlexItem, - EuiFlexGroup -} from '@elastic/eui'; - -const ShingleSize = () => { - - return ( - <> - -

- - Set the number of intervals to consider in a detection window for your model. - The anomaly detector expects the shingle size to be in the range of 1 and 60. - The default shingle size is 8. We recommend that you don't choose 1 unless you have - two or more features. Smaller values might increase recall but also false positives. - Larger values might be useful for ignoring noise in a signal. - -

- - - -
- - - ); -} - -export default ShingleSize; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js index e53ffd947..86c0c1c94 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js @@ -7,15 +7,17 @@ import { EuiFlyoutHeader, EuiFlyoutBody, EuiTitle, - EuiAccordion, + EuiCheckableCard, EuiSpacer, EuiFlyout } from '@elastic/eui'; +// import { +// OuiCheckableCard +// } from '@elastic/eui'; import './styles.scss'; import { EmbeddablePanel } from '../../../../../../src/plugins/embeddable/public'; import DetectorDetails from './DetectorDetails'; import Features from './Features'; -import ShingleSize from './ShingleSize'; const accordions = ['detectorDetails', 'features', 'shingleSize', 'alerts', 'triggers'].reduce( (acc, cur) => ({ ...acc, [cur]: cur }), @@ -23,53 +25,44 @@ const accordions = ['detectorDetails', 'features', 'shingleSize', 'alerts', 'tri ); function CreateAnomalyDetector({ embeddable }) { + const [radio, setRadio] = useState('createRadio'); const [accordionOpen, setAccordionOpen] = useState(accordions.triggers); - // useEffect(() => { - // async function createVisEmbeddable() { - // try { - // const getFactory = getEmbeddable().getEmbeddableFactory('visualization'); - - // // fetching the current context from the data plugin - // const contextInput = { - // filters: getQueryService().filterManager.getFilters(), - // query: getQueryService().queryString.getQuery(), - // timeRange: getQueryService().timefilter.timefilter.getTime(), - // }; - - // const embeddable = (await getFactory?.createFromSavedObject( - // props.savedObjectId, - // contextInput - // )) as IEmbeddable | ErrorEmbeddable; - - // // updating the input so we don't auto-refresh - // embeddable.updateInput({ - // // @ts-ignore - // refreshConfig: { - // value: 0, - // pause: true, - // }, - // }); - - // setEmbeddableObj(embeddable); - // } catch (err) { - // console.log(err); - // } - // } - // createVisEmbeddable(); - // // TODO: add more if needed - // }, [props.savedObjectId]); return (
-

Create anomaly detector

+

Add anomaly detector

+ + setRadio('createRadio')} + /> + + + setRadio('associateRadio')} + /> + + + + This is a short description of the feature to get users exicted. Learn more in the documentation. + + + {/*
- -
SHINGLE SIZE
- - } - initialIsOpen={false} - // forceState={accordionOpen === accordions.detectorDetails ? 'open' : 'closed'} - // onToggle={() => - // setAccordionOpen( - // accordionOpen !== accordions.detectorDetails && accordions.detectorDetails - // ) - // } - > - -
- + */}
From e4ac1adc20cca420210542556b86f231d6366ed0 Mon Sep 17 00:00:00 2001 From: Jackie Han Date: Mon, 20 Mar 2023 15:11:41 -0700 Subject: [PATCH 09/16] run yarn prettier against all files Signed-off-by: Jackie Han --- public/action/ad_dashboard_action.tsx | 15 ++++-- .../CreateAnomalyDetector/index.js | 6 ++- .../CreateAnomalyDetector/styles.scss | 2 +- .../ContextMenu/Notifications/index.js | 16 +++++-- .../DetectorDetails/index.js | 45 +++++++++--------- .../CreateAnomalyDetector/Features/index.js | 47 +++++++------------ .../CreateAnomalyDetector/index.js | 40 +++++++++------- .../FormikWrapper/index.js | 4 +- public/plugin.tsx | 7 +-- public/utils/contextMenu/action.tsx | 27 ++++++----- public/utils/contextMenu/helper.js | 2 +- public/utils/contextMenu/styles.scss | 2 +- 12 files changed, 110 insertions(+), 103 deletions(-) diff --git a/public/action/ad_dashboard_action.tsx b/public/action/ad_dashboard_action.tsx index f5a5db157..f5de0fcd9 100644 --- a/public/action/ad_dashboard_action.tsx +++ b/public/action/ad_dashboard_action.tsx @@ -3,13 +3,19 @@ import { DASHBOARD_CONTAINER_TYPE, DashboardContainer, } from '../../../../src/plugins/dashboard/public'; -import { IncompatibleActionError, createAction, Action } from '../../../../src/plugins/ui_actions/public'; +import { + IncompatibleActionError, + createAction, + Action, +} from '../../../../src/plugins/ui_actions/public'; import { isReferenceOrValueEmbeddable } from '../../../../src/plugins/embeddable/public'; import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; export const ACTION_AD = 'ad'; -function isDashboard(embeddable: IEmbeddable): embeddable is DashboardContainer { +function isDashboard( + embeddable: IEmbeddable +): embeddable is DashboardContainer { return embeddable.type === DASHBOARD_CONTAINER_TYPE; } @@ -54,7 +60,9 @@ export const createADAction = ({ seriesParams.find((item) => item.type === 'line') || series.find((item) => item.chart_type === 'line'); const isValidVis = isLineGraph && paramsType !== 'table'; - return Boolean(embeddable.parent && isDashboard(embeddable.parent) && isValidVis); + return Boolean( + embeddable.parent && isDashboard(embeddable.parent) && isValidVis + ); }, execute: async ({ embeddable }: ActionContext) => { if (!isReferenceOrValueEmbeddable(embeddable)) { @@ -64,4 +72,3 @@ export const createADAction = ({ onClick({ embeddable }); }, }); - \ No newline at end of file diff --git a/public/components/ContextMenu/CreateAnomalyDetector/index.js b/public/components/ContextMenu/CreateAnomalyDetector/index.js index 5fa9a4c38..e8448681f 100644 --- a/public/components/ContextMenu/CreateAnomalyDetector/index.js +++ b/public/components/ContextMenu/CreateAnomalyDetector/index.js @@ -33,7 +33,9 @@ export const CreateAnomalyDetector = (props) => { const flyout = overlays.openFlyout( toMountPoint( - flyout.close() }} /> + flyout.close() }} + /> ) ); @@ -86,4 +88,4 @@ export const CreateAnomalyDetector = (props) => { ); -}; \ No newline at end of file +}; diff --git a/public/components/ContextMenu/CreateAnomalyDetector/styles.scss b/public/components/ContextMenu/CreateAnomalyDetector/styles.scss index 2316fd381..8dc2ec9f2 100644 --- a/public/components/ContextMenu/CreateAnomalyDetector/styles.scss +++ b/public/components/ContextMenu/CreateAnomalyDetector/styles.scss @@ -2,4 +2,4 @@ &__create { align-self: flex-end; } -} \ No newline at end of file +} diff --git a/public/components/ContextMenu/Notifications/index.js b/public/components/ContextMenu/Notifications/index.js index b0762c415..2fe066ddd 100644 --- a/public/components/ContextMenu/Notifications/index.js +++ b/public/components/ContextMenu/Notifications/index.js @@ -1,13 +1,21 @@ import React from 'react'; -import { EuiLink, EuiText, EuiSpacer, EuiPanel, EuiIcon, EuiFormRow } from '@elastic/eui'; +import { + EuiLink, + EuiText, + EuiSpacer, + EuiPanel, + EuiIcon, + EuiFormRow, +} from '@elastic/eui'; export const Notifications = () => ( <> - The anomalies will appear on the visualization when the anomaly grade is above 0.7 and anomaly confidence is below 0.7. - Additional notification can be configured. + The anomalies will appear on the visualization when the anomaly grade + is above 0.7 and anomaly confidence is below 0.7. Additional + notification can be configured. @@ -20,4 +28,4 @@ export const Notifications = () => ( -); \ No newline at end of file +); diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js index c97d52995..2723804fa 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/DetectorDetails/index.js @@ -9,7 +9,7 @@ import { EuiFormRow, EuiFieldText, EuiSelect, - EuiCheckbox + EuiCheckbox, } from '@elastic/eui'; const DetectorDetails = () => { const intervalOptions = [ @@ -18,7 +18,7 @@ const DetectorDetails = () => { { value: 'option_three', text: '5 minutes' }, ]; const [intervalValue, setIntervalalue] = useState(intervalOptions[0].value); - const intervalOnChange = e => { + const intervalOnChange = (e) => { setIntervalalue(e.target.value); }; @@ -28,58 +28,55 @@ const DetectorDetails = () => { { value: 'option_three', text: '5 minutes' }, ]; const [delayValue, setDelayValue] = useState(delayOptions[0].value); - const delayOnChange = e => { + const delayOnChange = (e) => { setDelayValue(e.target.value); }; const [checked, setChecked] = useState(false); - const onCustomerResultIndexCheckboxChange = e => { + const onCustomerResultIndexCheckboxChange = (e) => { setChecked(e.target.checked); }; return ( <> - - + + - - + + - + intervalOnChange(e)} + onChange={(e) => intervalOnChange(e)} /> - - + delayOnChange(e)} + onChange={(e) => delayOnChange(e)} /> - onCustomerResultIndexCheckboxChange(e)} - /> + onCustomerResultIndexCheckboxChange(e)} + /> - ); -} +}; -export default DetectorDetails; \ No newline at end of file +export default DetectorDetails; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/Features/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/Features/index.js index 1bdb6dd2a..77774a1fa 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/Features/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/Features/index.js @@ -9,7 +9,7 @@ import { EuiFieldText, EuiSelect, EuiFlexItem, - EuiFlexGroup + EuiFlexGroup, } from '@elastic/eui'; const Features = () => { @@ -17,61 +17,51 @@ const Features = () => { { value: 'avg', text: 'AVG' }, { value: 'sum', text: 'SUM' }, ]; - const [aggMethodValue, setAggMethodValue] = useState(aggMethodOptions[0].value); - const aggMethodOnChange = e => { + const [aggMethodValue, setAggMethodValue] = useState( + aggMethodOptions[0].value + ); + const aggMethodOnChange = (e) => { setAggMethodValue(e.target.value); }; return ( <> - + - - Find anomalies based on - - - + Find anomalies based on + + - - Aggregation method - + Aggregation method aggMethodOnChange(e)} + onChange={(e) => aggMethodOnChange(e)} /> - + - - Find anomalies based on - - - + Find anomalies based on + + - - Aggregation method - + Aggregation method aggMethodOnChange(e)} + onChange={(e) => aggMethodOnChange(e)} /> @@ -81,9 +71,8 @@ const Features = () => {

Selected aggration method is incompatible

*/}
- ); -} +}; export default Features; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js index 86c0c1c94..c0d804717 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js @@ -9,7 +9,7 @@ import { EuiTitle, EuiCheckableCard, EuiSpacer, - EuiFlyout + EuiFlyout, } from '@elastic/eui'; // import { // OuiCheckableCard @@ -19,16 +19,18 @@ import { EmbeddablePanel } from '../../../../../../src/plugins/embeddable/public import DetectorDetails from './DetectorDetails'; import Features from './Features'; -const accordions = ['detectorDetails', 'features', 'shingleSize', 'alerts', 'triggers'].reduce( - (acc, cur) => ({ ...acc, [cur]: cur }), - {} -); +const accordions = [ + 'detectorDetails', + 'features', + 'shingleSize', + 'alerts', + 'triggers', +].reduce((acc, cur) => ({ ...acc, [cur]: cur }), {}); function CreateAnomalyDetector({ embeddable }) { const [radio, setRadio] = useState('createRadio'); const [accordionOpen, setAccordionOpen] = useState(accordions.triggers); - return (
@@ -40,13 +42,13 @@ function CreateAnomalyDetector({ embeddable }) { - setRadio('createRadio')} - /> + setRadio('createRadio')} + /> setRadio('associateRadio')} - /> + /> - This is a short description of the feature to get users exicted. Learn more in the documentation. - - + + This is a short description of the feature to get users exicted. + Learn more in the documentation. + + {/*
@@ -122,6 +126,6 @@ function CreateAnomalyDetector({ embeddable }) {
- ) + ); } export default CreateAnomalyDetector; diff --git a/public/components/FeatureAnywhereContextMenu/FormikWrapper/index.js b/public/components/FeatureAnywhereContextMenu/FormikWrapper/index.js index c76b1e605..c0e664fb2 100644 --- a/public/components/FeatureAnywhereContextMenu/FormikWrapper/index.js +++ b/public/components/FeatureAnywhereContextMenu/FormikWrapper/index.js @@ -3,7 +3,9 @@ import { useFormik, FormikProvider } from 'formik'; const FormikWrapper = ({ getFormikOptions, children, ...props }) => { const formik = useFormik(getFormikOptions()); - return {children}; + return ( + {children} + ); }; export default FormikWrapper; diff --git a/public/plugin.tsx b/public/plugin.tsx index bcf0b14c3..b6cd31d0a 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -26,10 +26,7 @@ declare module '../../../src/plugins/ui_actions/public' { } } -export class AnomalyDetectionOpenSearchDashboardsPlugin - implements - Plugin -{ +export class AnomalyDetectionOpenSearchDashboardsPlugin implements Plugin { public setup(core: CoreSetup, plugins) { core.application.register({ id: PLUGIN_NAME, @@ -60,5 +57,3 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin public stop() {} } - - diff --git a/public/utils/contextMenu/action.tsx b/public/utils/contextMenu/action.tsx index 364d4c3b8..338939cd1 100644 --- a/public/utils/contextMenu/action.tsx +++ b/public/utils/contextMenu/action.tsx @@ -41,9 +41,7 @@ export const getActions = ({ core }) => // }, // }); openFlyout( - toMountPoint( - - ), + toMountPoint(), { size: 'l' } ); }, @@ -51,20 +49,26 @@ export const getActions = ({ core }) => { grouping, id: 'manageAnomalyDetector', - title: i18n.translate('dashboard.actions.alertingMenuItem.manageAnomalyDetector.displayName', { - defaultMessage: 'Manage anomaly detector', - }), + title: i18n.translate( + 'dashboard.actions.alertingMenuItem.manageAnomalyDetector.displayName', + { + defaultMessage: 'Manage anomaly detector', + } + ), icon: 'wrench' as EuiIconType, order: 99, onClick: async ({ embeddable }) => { - console.log("manage ad"); + console.log('manage ad'); }, }, { id: 'documentation', - title: i18n.translate('dashboard.actions.adMenuItem.documentation.displayName', { - defaultMessage: 'Documentation', - }), + title: i18n.translate( + 'dashboard.actions.adMenuItem.documentation.displayName', + { + defaultMessage: 'Documentation', + } + ), icon: 'documentation' as EuiIconType, order: 98, onClick: () => { @@ -74,5 +78,4 @@ export const getActions = ({ core }) => ); }, }, - ] - .map((options) => createADAction({ ...options, grouping })); + ].map((options) => createADAction({ ...options, grouping })); diff --git a/public/utils/contextMenu/helper.js b/public/utils/contextMenu/helper.js index 669ace08d..2ba1729d1 100644 --- a/public/utils/contextMenu/helper.js +++ b/public/utils/contextMenu/helper.js @@ -10,4 +10,4 @@ export const getInitialValues = () => ({ ], monitors: getInitialMonitors(), alerts: getInitialAlerts(), -}); \ No newline at end of file +}); diff --git a/public/utils/contextMenu/styles.scss b/public/utils/contextMenu/styles.scss index 69ebfcb91..89ee5baf7 100644 --- a/public/utils/contextMenu/styles.scss +++ b/public/utils/contextMenu/styles.scss @@ -22,4 +22,4 @@ color: inherit; } } -} \ No newline at end of file +} From 5eb17b3f55e3e4a4713389e18baba0f33f745d81 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Mon, 6 Mar 2023 12:35:09 -0800 Subject: [PATCH 10/16] working js manage detectors Signed-off-by: Amit Galitzky --- opensearch_dashboards.json | 8 ++ .../AssociatedDetectors/helpers.js | 53 ++++++++ .../AssociatedDetectors/index.js | 114 ++++++++++++++++++ .../AssociatedDetectors/styles.scss | 16 +++ .../CreateAnomalyDetector/index.js | 30 +++++ public/utils/contextMenu/action.tsx | 20 +-- 6 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/helpers.js create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.js create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index a88771b0a..65026cf92 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -3,6 +3,7 @@ "version": "3.0.0.0", "opensearchDashboardsVersion": "3.0.0", "configPath": ["anomaly_detection_dashboards"], +<<<<<<< HEAD "requiredPlugins": [ "navigation", "uiActions", @@ -11,6 +12,13 @@ "opensearchDashboardsReact", "savedObjects" ], +======= + "requiredPlugins": ["navigation", "uiActions", + "dashboard", + "embeddable", + "opensearchDashboardsReact", + "savedObjects"], +>>>>>>> 6e93ee7 (working js manage detectors) "optionalPlugins": [], "server": true, "ui": true diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/helpers.js b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/helpers.js new file mode 100644 index 000000000..05a361fe1 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/helpers.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { EuiHealth } from '@elastic/eui'; + + +export const stateToLabel = { + running: { label: 'Running', color: 'success' }, + initializing: { label: 'Initializing', color: 'active' }, +}; + +export const getColumns = ({ onUnlink, onView }) => [ + { + field: 'name', + name: 'Detector', + sortable: true, + truncateText: true, + width: '50%', + }, + { + field: 'state', + name: 'Real-time state', + sortable: true, + width: '105px', + render: (state) => ( + {stateToLabel[state].label} + ), + }, + { + field: 'occurance', + name: 'Anomalies/24hr', + sortable: true, + truncateText: true, + width: '50%', + }, + { + name: 'Actions', + actions: [ + { + type: 'icon', + name: 'Unlink Detector', + description: 'Unlink Detector', + icon: 'unlink', + onClick: onUnlink, + } + ], + }, +]; + +export const search = { + box: { + incremental: true, + schema: true, + }, +}; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.js b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.js new file mode 100644 index 000000000..18a766f84 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.js @@ -0,0 +1,114 @@ +import React, { useCallback, useMemo } from 'react'; +import { + EuiFlyoutHeader, + EuiTitle, + EuiText, + EuiSpacer, + EuiInMemoryTable, + EuiFlyoutBody, + EuiEmptyPrompt, + EuiButton, + EuiFlyout, +} from '@elastic/eui'; +import uuidv4 from 'uuid/v4'; +import './styles.scss'; +import { getColumns, search } from './helpers'; +const closeFlyout = () => setIsFlyoutVisible(false); + +const AssociatedDetectors = ({ embeddable, closeFlyout }) => { + const title = embeddable.getTitle(); + const onUnlink = useCallback( + (item) => { + console.log('onUnlink', item); + closeFlyout(); + }, + [closeFlyout] + ); + const onView = useCallback( + (item) => { + console.log('onView', item); + closeFlyout(); + }, + [closeFlyout] + ); + const columns = useMemo(() => getColumns({ onUnlink, onView }), [ + onUnlink, + ]); + const detectors = [ + { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, + ]; + + + const empty = ( + No anomaly detectors to display} + titleSize="s" + body={`There are no anomaly detectors associated with ${title} visualization. + You will need to add a detector to the visualization to be able to list it here`} + actions={ + setPanel('add')}> + Add anomaly detector + + } + /> + ); + const tableProps = { + items: detectors, + columns, + search: { + box: { + disabled: detectors.length === 0, + incremental: true, + schema: true, + }, + }, + hasActions: true, + pagination: true, + sorting: true, + message: empty, + }; + + + + return ( + //
+ + + +

+ Associated detectors {detectors.length > 0 ? `(${detectors.length})` : ''} +

+
+
+ + +

{title}

+
+ + +
+
+ + //
+ ); +}; + +export default AssociatedDetectors; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss new file mode 100644 index 000000000..e6520e0e6 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss @@ -0,0 +1,16 @@ +@import '@elastic/eui/src/global_styling/variables/index'; + +.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/CreateAnomalyDetector/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js index c0d804717..1abd33966 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js @@ -18,6 +18,11 @@ import './styles.scss'; import { EmbeddablePanel } from '../../../../../../src/plugins/embeddable/public'; import DetectorDetails from './DetectorDetails'; import Features from './Features'; +<<<<<<< HEAD +======= +import ShingleSize from './ShingleSize'; +import { util } from 'node-forge'; +>>>>>>> 6e93ee7 (working js manage detectors) const accordions = [ 'detectorDetails', @@ -30,7 +35,32 @@ const accordions = [ function CreateAnomalyDetector({ embeddable }) { const [radio, setRadio] = useState('createRadio'); const [accordionOpen, setAccordionOpen] = useState(accordions.triggers); + + function simpleStringify (object){ + var simpleObject = {}; + for (var prop in object ){ + if (!object.hasOwnProperty(prop)){ + continue; + } + if (typeof(object[prop]) == 'object'){ + continue; + } + if (typeof(object[prop]) == 'function'){ + continue; + } + simpleObject[prop] = object[prop]; + } + return JSON.stringify(simpleObject); // returns cleaned up JSON +}; + + console.log("embeddable: " + simpleStringify(embeddable)) + console.log("embed json: " + (JSON.parse(simpleStringify(embeddable)))) +// // safely handles circular references +<<<<<<< HEAD +======= + +>>>>>>> 6e93ee7 (working js manage detectors) return (
diff --git a/public/utils/contextMenu/action.tsx b/public/utils/contextMenu/action.tsx index 338939cd1..2f1b7832c 100644 --- a/public/utils/contextMenu/action.tsx +++ b/public/utils/contextMenu/action.tsx @@ -4,8 +4,8 @@ import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; import { toMountPoint } from '../../../../../src/plugins/opensearch_dashboards_react/public'; import { Action } from '../../../../../src/plugins/ui_actions/public'; import { createADAction } from '../../action/ad_dashboard_action'; -import FormikWrapper from '../../components/FeatureAnywhereContextMenu/FormikWrapper'; import CreateAnomalyDetector from '../../components/FeatureAnywhereContextMenu/CreateAnomalyDetector'; +import AssociatedDetectors from '../../components/FeatureAnywhereContextMenu/AssociatedDetectors'; // This is used to create all actions in the same context menu const grouping: Action['grouping'] = [ @@ -33,13 +33,6 @@ export const getActions = ({ core }) => onClick: async ({ embeddable }) => { const services = await core.getStartServices(); const openFlyout = services[0].overlays.openFlyout; - // const getFormikOptions = () => ({ - // initialValues: getInitialValues(), - // onSubmit: (values) => { - // console.log('Submitting createAlertingMonitor'); - // console.log(values); - // }, - // }); openFlyout( toMountPoint(), { size: 'l' } @@ -58,7 +51,18 @@ export const getActions = ({ core }) => icon: 'wrench' as EuiIconType, order: 99, onClick: async ({ embeddable }) => { +<<<<<<< HEAD console.log('manage ad'); +======= + const services = await core.getStartServices(); + const openFlyout = services[0].overlays.openFlyout; + const overlay = openFlyout( + toMountPoint( + overlay.close(), core, services }} /> + ), + { size: 'l' } + ); +>>>>>>> 6e93ee7 (working js manage detectors) }, }, { From 08cd69ee1d35a9de486bd1ad50c07089cb5e00e9 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Mon, 20 Mar 2023 10:54:51 -0700 Subject: [PATCH 11/16] adding associated detectors page Signed-off-by: Amit Galitzky --- opensearch_dashboards.json | 8 - .../components/EmptyMessage/EmptyMessage.tsx | 37 +++ .../containers/AssociatedDetectors.tsx | 246 ++++++++++++++++++ .../AssociatedDetectors/helpers.js | 53 ---- .../AssociatedDetectors/index.js | 114 -------- .../AssociatedDetectors/index.ts | 12 + .../AssociatedDetectors/utils/helpers.tsx | 82 ++++++ .../ShingleSize/index.js | 37 +++ .../CreateAnomalyDetector/index.js | 7 - public/expressions/index.ts | 12 + public/expressions/overlay_anomalies.ts | 10 + public/plugin.tsx | 8 +- public/services.ts | 35 +++ public/utils/contextMenu/action.tsx | 13 +- 14 files changed, 488 insertions(+), 186 deletions(-) create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx delete mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/helpers.js delete mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.js create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.ts create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx create mode 100644 public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js create mode 100644 public/expressions/index.ts create mode 100644 public/expressions/overlay_anomalies.ts create mode 100644 public/services.ts diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index 65026cf92..a88771b0a 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -3,7 +3,6 @@ "version": "3.0.0.0", "opensearchDashboardsVersion": "3.0.0", "configPath": ["anomaly_detection_dashboards"], -<<<<<<< HEAD "requiredPlugins": [ "navigation", "uiActions", @@ -12,13 +11,6 @@ "opensearchDashboardsReact", "savedObjects" ], -======= - "requiredPlugins": ["navigation", "uiActions", - "dashboard", - "embeddable", - "opensearchDashboardsReact", - "savedObjects"], ->>>>>>> 6e93ee7 (working js manage detectors) "optionalPlugins": [], "server": true, "ui": true diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx new file mode 100644 index 000000000..996de747e --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +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 EmptyAssociatedDetectorFlyoutMessage = ( + props: EmptyDetectorProps +) => ( + No anomaly detectors to display} + titleSize="s" + data-test-subj="emptyAssociatedDetectorFlyoutMessage" + style={{ maxWidth: '45em' }} + body={ + +

{`There are no anomaly detectors associated with ${props.embeddableTitle} visualization.`}

+ {/*

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

*/} +
+ } + /> +); diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx new file mode 100644 index 000000000..bcf26cb8d --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -0,0 +1,246 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import React, { useCallback, useMemo, useEffect, useState } from 'react'; +import { + EuiFlyoutHeader, + EuiTitle, + EuiText, + EuiSpacer, + EuiInMemoryTable, + EuiFlyoutBody, + EuiButton, + EuiFlyout, + EuiFlexItem, +} from '@elastic/eui'; +import { get } from 'lodash'; +import '../styles.scss'; +import { getColumns } from '../utils/helpers'; +import { CoreServicesContext } from '../../../../components/CoreServices/CoreServices'; +import { CoreStart } from '../../../../../../../src/core/public'; +import { useDispatch, useSelector } from 'react-redux'; +import { AppState } from '../../../../redux/reducers'; +import { DetectorListItem } from '../../../../models/interfaces'; +import { getSavedFeatureAnywhereLoader } 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 { EmptyAssociatedDetectorFlyoutMessage } from '../components/EmptyMessage/EmptyMessage'; +import { + createAugmentVisSavedObject, + ISavedAugmentVis, + VisLayerExpressionFn, +} from '../../../../../../../src/plugins/vis_augmenter/public'; + +export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { + 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 embeddableTitle = embeddable.getTitle(); + const [selectedDetectors, setSelectedDetectors] = useState( + [] as DetectorListItem[] + ); + + 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]); + + useEffect(() => { + getDetectors(); + }, []); + + // Handle all filtering / sorting of detectors + useEffect(() => { + const savedObjectLoader: SavedObjectLoader = + getSavedFeatureAnywhereLoader(); + // 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); + } + }); + }, [allDetectors]); + + // cross checks all the detectors that exist with all the savedAugment Objects to only display ones + const getAssociatedDetectors = ( + detectors: DetectorListItem[], + savedAugmentObjects: ISavedAugmentVis[] + ) => { + const savedAugmentForThisVisualization: ISavedAugmentVis[] = + savedAugmentObjects.filter( + (savedObj) => get(savedObj, 'visId', '') === embeddable.vis.id + ); + const savedAugmentDetectorsSet = new Set( + savedAugmentForThisVisualization.map((savedObject) => + get(savedObject, 'pluginResourceId', '') + ) + ); + const detectorsToDisplay = detectors.filter((detector) => + savedAugmentDetectorsSet.has(detector.id) + ); + console.log('detectorsToDisplay: ' + JSON.stringify(detectorsToDisplay)); + return detectorsToDisplay; + }; + + const getDetectors = async () => { + dispatch(getDetectorList(GET_ALL_DETECTORS_QUERY_PARAMS)); + }; + + // This method is only here for development/testing purposes. + const getSavedObjects = async () => { + const resp = await getSavedFeatureAnywhereLoader().findAll(); + console.log('response: ' + JSON.stringify(resp)); + }; + + // This method is only here for development/testing purposes. + const createSavedObjects = async () => { + enum VisLayerTypes { + PointInTimeEvents = 'PointInTimeEvents', + } + const fn = { + type: VisLayerTypes.PointInTimeEvents, + name: 'test-fn', + args: { + testArg: selectedDetectors[0].id, + }, + } as VisLayerExpressionFn; + + const savedObjectToCreate = { + title: 'test-title', + pluginResourceId: selectedDetectors[0].id, + visId: embeddable.vis.id, + savedObjectType: 'visualization', + visLayerExpressionFn: fn, + } as ISavedAugmentVis; + + const savedObject = await createAugmentVisSavedObject(savedObjectToCreate); + console.log('savedObject: ' + JSON.stringify(savedObject)); + + const response = await savedObject.save({}); + console.log('response: ' + JSON.stringify(response)); + }; + + const onUnlink = useCallback( + (item) => { + console.log('onUnlink', item); + closeFlyout(); + }, + [closeFlyout] + ); + const onView = useCallback( + (item) => { + console.log('onView', item); + closeFlyout(); + }, + [closeFlyout] + ); + const columns = useMemo(() => getColumns({ onUnlink, onView }), [onUnlink]); + + const tableProps = { + items: selectedDetectors, + columns, + search: { + box: { + disabled: selectedDetectors.length === 0, + incremental: true, + schema: true, + }, + }, + hasActions: true, + pagination: true, + sorting: true, + message: isLoading ? ( + 'Loading detectors...' + ) : ( + + ), + }; + + return ( + //
+ + + +

Associated anomaly detectors

+
+
+ + {/* below buttons are just here for development/testing purposes*/} + + { + createSavedObjects(); + }} + > + Create saved objects{' '} + + + + { + getSavedObjects(); + }} + > + Get Saved Objects + + + +

{embeddableTitle}

+
+ + +
+
+ + //
+ ); +}; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/helpers.js b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/helpers.js deleted file mode 100644 index 05a361fe1..000000000 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/helpers.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { EuiHealth } from '@elastic/eui'; - - -export const stateToLabel = { - running: { label: 'Running', color: 'success' }, - initializing: { label: 'Initializing', color: 'active' }, -}; - -export const getColumns = ({ onUnlink, onView }) => [ - { - field: 'name', - name: 'Detector', - sortable: true, - truncateText: true, - width: '50%', - }, - { - field: 'state', - name: 'Real-time state', - sortable: true, - width: '105px', - render: (state) => ( - {stateToLabel[state].label} - ), - }, - { - field: 'occurance', - name: 'Anomalies/24hr', - sortable: true, - truncateText: true, - width: '50%', - }, - { - name: 'Actions', - actions: [ - { - type: 'icon', - name: 'Unlink Detector', - description: 'Unlink Detector', - icon: 'unlink', - onClick: onUnlink, - } - ], - }, -]; - -export const search = { - box: { - incremental: true, - schema: true, - }, -}; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.js b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.js deleted file mode 100644 index 18a766f84..000000000 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.js +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { - EuiFlyoutHeader, - EuiTitle, - EuiText, - EuiSpacer, - EuiInMemoryTable, - EuiFlyoutBody, - EuiEmptyPrompt, - EuiButton, - EuiFlyout, -} from '@elastic/eui'; -import uuidv4 from 'uuid/v4'; -import './styles.scss'; -import { getColumns, search } from './helpers'; -const closeFlyout = () => setIsFlyoutVisible(false); - -const AssociatedDetectors = ({ embeddable, closeFlyout }) => { - const title = embeddable.getTitle(); - const onUnlink = useCallback( - (item) => { - console.log('onUnlink', item); - closeFlyout(); - }, - [closeFlyout] - ); - const onView = useCallback( - (item) => { - console.log('onView', item); - closeFlyout(); - }, - [closeFlyout] - ); - const columns = useMemo(() => getColumns({ onUnlink, onView }), [ - onUnlink, - ]); - const detectors = [ - { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - // { name: 'CPU_Usage_Detector_2', state: 'initializing', occurance: 3, id: uuidv4() }, - ]; - - - const empty = ( - No anomaly detectors to display} - titleSize="s" - body={`There are no anomaly detectors associated with ${title} visualization. - You will need to add a detector to the visualization to be able to list it here`} - actions={ - setPanel('add')}> - Add anomaly detector - - } - /> - ); - const tableProps = { - items: detectors, - columns, - search: { - box: { - disabled: detectors.length === 0, - incremental: true, - schema: true, - }, - }, - hasActions: true, - pagination: true, - sorting: true, - message: empty, - }; - - - - return ( - //
- - - -

- Associated detectors {detectors.length > 0 ? `(${detectors.length})` : ''} -

-
-
- - -

{title}

-
- - -
-
- - //
- ); -}; - -export default AssociatedDetectors; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.ts b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.ts new file mode 100644 index 000000000..a25a81fc3 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/index.ts @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +export { AssociatedDetectors } from './containers/AssociatedDetectors'; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx new file mode 100644 index 000000000..f5a9dbcae --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +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) => { + console.log('detector State: ' + state); + return ( + //@ts-ignore + {state} + ); +}; + +export const getColumns = ({ onUnlink, onView }) => + [ + { + 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: 'Unlink Detector', + description: 'Unlink Detector', + icon: 'unlink', + onClick: onUnlink, + }, + ], + }, + ] as EuiBasicTableColumn[]; + +export const search = { + box: { + incremental: true, + schema: true, + }, +}; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js new file mode 100644 index 000000000..2a3f0e0d1 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/ShingleSize/index.js @@ -0,0 +1,37 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { + EuiHorizontalRule, + EuiTextColor, + EuiPanel, + EuiIcon, + EuiAccordion, + EuiFormRow, + EuiFieldText, + EuiSelect, + EuiFlexItem, + EuiFlexGroup, +} from '@elastic/eui'; + +const ShingleSize = () => { + return ( + <> + +

+ + Set the number of intervals to consider in a detection window for + your model. The anomaly detector expects the shingle size to be in + the range of 1 and 60. The default shingle size is 8. We recommend + that you don't choose 1 unless you have two or more features. + Smaller values might increase recall but also false positives. + Larger values might be useful for ignoring noise in a signal. + +

+ + + +
+ + ); +}; + +export default ShingleSize; diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js index 1abd33966..131e55831 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js @@ -18,11 +18,8 @@ import './styles.scss'; import { EmbeddablePanel } from '../../../../../../src/plugins/embeddable/public'; import DetectorDetails from './DetectorDetails'; import Features from './Features'; -<<<<<<< HEAD -======= import ShingleSize from './ShingleSize'; import { util } from 'node-forge'; ->>>>>>> 6e93ee7 (working js manage detectors) const accordions = [ 'detectorDetails', @@ -57,10 +54,6 @@ function CreateAnomalyDetector({ embeddable }) { console.log("embed json: " + (JSON.parse(simpleStringify(embeddable)))) // // safely handles circular references -<<<<<<< HEAD -======= - ->>>>>>> 6e93ee7 (working js manage detectors) return (
diff --git a/public/expressions/index.ts b/public/expressions/index.ts new file mode 100644 index 000000000..70a6f1c9b --- /dev/null +++ b/public/expressions/index.ts @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +export * from './overlay_anomalies'; \ No newline at end of file diff --git a/public/expressions/overlay_anomalies.ts b/public/expressions/overlay_anomalies.ts new file mode 100644 index 000000000..133174fcf --- /dev/null +++ b/public/expressions/overlay_anomalies.ts @@ -0,0 +1,10 @@ +// /* +// * SPDX-License-Identifier: Apache-2.0 +// * +// * The OpenSearch Contributors require contributions made to +// * this file be licensed under the Apache-2.0 license or a +// * compatible open source license. +// * +// * Modifications Copyright OpenSearch Contributors. See +// * GitHub history for details. +// */ diff --git a/public/plugin.tsx b/public/plugin.tsx index b6cd31d0a..7e687afbd 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -12,13 +12,14 @@ import { AppMountParameters, CoreSetup, + CoreStart, Plugin, - PluginInitializerContext, } from '../../../src/core/public'; import { CONTEXT_MENU_TRIGGER } from '../../../src/plugins/embeddable/public'; import { ACTION_AD, createADAction } from './action/ad_dashboard_action'; import { PLUGIN_NAME } from './utils/constants'; import { getActions } from './utils/contextMenu/action'; +import { setSavedFeatureAnywhereLoader } from './services'; declare module '../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { @@ -53,7 +54,10 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin implements Plugin { }); } - public start() {} + public start(core: CoreStart, plugins) { + setSavedFeatureAnywhereLoader(plugins.visAugmenter.savedAugmentVisLoader) + return {}; +} public stop() {} } diff --git a/public/services.ts b/public/services.ts new file mode 100644 index 000000000..561ad3935 --- /dev/null +++ b/public/services.ts @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/public'; +import { SavedObjectLoader } from '../../../src/plugins/saved_objects/public'; + +export const [getSavedFeatureAnywhereLoader, setSavedFeatureAnywhereLoader] = + createGetterSetter('savedFeatureAnywhereLoader'); \ No newline at end of file diff --git a/public/utils/contextMenu/action.tsx b/public/utils/contextMenu/action.tsx index 2f1b7832c..91932b052 100644 --- a/public/utils/contextMenu/action.tsx +++ b/public/utils/contextMenu/action.tsx @@ -5,7 +5,10 @@ import { toMountPoint } from '../../../../../src/plugins/opensearch_dashboards_r import { Action } from '../../../../../src/plugins/ui_actions/public'; import { createADAction } from '../../action/ad_dashboard_action'; import CreateAnomalyDetector from '../../components/FeatureAnywhereContextMenu/CreateAnomalyDetector'; -import AssociatedDetectors from '../../components/FeatureAnywhereContextMenu/AssociatedDetectors'; +import { AssociatedDetectors } from '../../components/FeatureAnywhereContextMenu/AssociatedDetectors'; +import { CoreServicesContext } from '../../components/CoreServices/CoreServices'; +import { Provider } from 'react-redux'; +import configureStore from '../../redux/configureStore' // This is used to create all actions in the same context menu const grouping: Action['grouping'] = [ @@ -55,10 +58,16 @@ export const getActions = ({ core }) => console.log('manage ad'); ======= const services = await core.getStartServices(); + const http = services[0].http; + const store = configureStore(http); const openFlyout = services[0].overlays.openFlyout; const overlay = openFlyout( toMountPoint( - overlay.close(), core, services }} /> + + + overlay.close(), core, services }} /> + + ), { size: 'l' } ); From 8aaf979434e496eef0d16cc2a385ad0ed8c43451 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Mon, 20 Mar 2023 12:46:56 -0700 Subject: [PATCH 12/16] adding unlink modal confirmation Signed-off-by: Amit Galitzky --- .../ConfirmUnlinkDetectorModal.tsx | 80 ++++++++++++ .../containers/AssociatedDetectors.tsx | 117 +++++++++++++++--- .../AssociatedDetectors/utils/constants.tsx | 15 +++ .../AssociatedDetectors/utils/helpers.tsx | 4 +- 4 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx create mode 100644 public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx 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 000000000..315680767 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +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/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx index bcf26cb8d..e8271f88c 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -9,7 +9,7 @@ * GitHub history for details. */ -import React, { useCallback, useMemo, useEffect, useState } from 'react'; +import React, { useMemo, useEffect, useState } from 'react'; import { EuiFlyoutHeader, EuiTitle, @@ -21,7 +21,7 @@ import { EuiFlyout, EuiFlexItem, } from '@elastic/eui'; -import { get } from 'lodash'; +import { get, isEmpty } from 'lodash'; import '../styles.scss'; import { getColumns } from '../utils/helpers'; import { CoreServicesContext } from '../../../../components/CoreServices/CoreServices'; @@ -46,6 +46,16 @@ import { ISavedAugmentVis, VisLayerExpressionFn, } from '../../../../../../../src/plugins/vis_augmenter/public'; +import { ASSOCIATED_DETECTOR_ACTION } from '../utils/constants'; +import { ConfirmUnlinkDetectorModal } from '../components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal'; + +interface ConfirmModalState { + isOpen: boolean; + action: ASSOCIATED_DETECTOR_ACTION; + isListLoading: boolean; + isRequestingToClose: boolean; + affectedDetector: DetectorListItem; +} export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { const core = React.useContext(CoreServicesContext) as CoreStart; @@ -65,6 +75,20 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { [] 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, + } + ); + useEffect(() => { if ( errorGettingDetectors && @@ -81,6 +105,25 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { } }, [errorGettingDetectors]); + // Update modal state if user decides to close + useEffect(() => { + if (confirmModalState.isRequestingToClose) { + if (isLoading) { + setConfirmModalState({ + ...confirmModalState, + isListLoading: true, + }); + } else { + setConfirmModalState({ + ...confirmModalState, + isOpen: false, + isListLoading: false, + isRequestingToClose: false, + }); + } + } + }, [confirmModalState.isRequestingToClose, isLoading]); + useEffect(() => { getDetectors(); }, []); @@ -128,6 +171,34 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { return detectorsToDisplay; }; + const getUnlinkConfirmModal = () => { + if (confirmModalState.isOpen) { + return ( + + ); + } + }; + + const handleHideModal = () => { + setConfirmModalState({ + ...confirmModalState, + isOpen: false, + }); + }; + + const handleConfirmModal = () => { + setConfirmModalState({ + ...confirmModalState, + isRequestingToClose: true, + }); + }; + const getDetectors = async () => { dispatch(getDetectorList(GET_ALL_DETECTORS_QUERY_PARAMS)); }; @@ -166,21 +237,33 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { console.log('response: ' + JSON.stringify(response)); }; - const onUnlink = useCallback( - (item) => { - console.log('onUnlink', item); - closeFlyout(); - }, - [closeFlyout] - ); - const onView = useCallback( - (item) => { - console.log('onView', item); - closeFlyout(); - }, - [closeFlyout] + const onUnlinkDetector = async () => { + console.log('detectorToUnlink', detectorToUnlink); + }; + + const handleUnlinkDetectorAction = (detector: DetectorListItem) => { + //console.log('onUnlink: ', detector); + setDetectorToUnlink(detector); + if (!isEmpty(detector)) { + setConfirmModalState({ + isOpen: true, + action: ASSOCIATED_DETECTOR_ACTION.UNLINK, + isListLoading: false, + isRequestingToClose: false, + affectedDetector: detector, + }); + } else { + // might not need this + core.notifications.toasts.addWarning( + 'Make sure selected detector has not been deleted' + ); + } + }; + + const columns = useMemo( + () => getColumns({ handleUnlinkDetectorAction }), + [handleUnlinkDetectorAction] ); - const columns = useMemo(() => getColumns({ onUnlink, onView }), [onUnlink]); const tableProps = { items: selectedDetectors, @@ -214,6 +297,7 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { + {getUnlinkConfirmModal()} {/* below buttons are just here for development/testing purposes*/} { - //
); }; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx new file mode 100644 index 000000000..820782e07 --- /dev/null +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +export enum ASSOCIATED_DETECTOR_ACTION { + UNLINK, + } + \ No newline at end of file diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx index f5a9dbcae..6441fa378 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx @@ -24,7 +24,7 @@ export const renderState = (state: DETECTOR_STATE) => { ); }; -export const getColumns = ({ onUnlink, onView }) => +export const getColumns = ({ handleUnlinkDetectorAction }) => [ { field: 'name', @@ -68,7 +68,7 @@ export const getColumns = ({ onUnlink, onView }) => name: 'Unlink Detector', description: 'Unlink Detector', icon: 'unlink', - onClick: onUnlink, + onClick: handleUnlinkDetectorAction, }, ], }, From 4208e0c687f940cbd4ffb666d2d8f999365ec99a Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Mon, 20 Mar 2023 16:03:50 -0700 Subject: [PATCH 13/16] prettier formating and merge conflicts Signed-off-by: Amit Galitzky --- opensearch_dashboards.json | 4 +++- .../ConfirmUnlinkDetectorModal.tsx | 14 ++++++++++---- .../AssociatedDetectors/utils/constants.tsx | 5 ++--- public/expressions/index.ts | 2 +- public/plugin.tsx | 6 +++--- public/services.ts | 2 +- public/utils/contextMenu/action.tsx | 15 +++++++++------ 7 files changed, 29 insertions(+), 19 deletions(-) diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index a88771b0a..65bfc5f76 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -9,7 +9,9 @@ "dashboard", "embeddable", "opensearchDashboardsReact", - "savedObjects" + "savedObjects", + "visAugmenter", + "opensearchDashboardsUtils" ], "optionalPlugins": [], "server": true, diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx index 315680767..607c4f8ee 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal.tsx @@ -39,16 +39,22 @@ export const ConfirmUnlinkDetectorModal = ( 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. + + Removing association unlinks {props.detector.name} detector from the + visualization but does not delete it. The detector association can + be restored. + diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx index 820782e07..16d43420d 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/constants.tsx @@ -10,6 +10,5 @@ */ export enum ASSOCIATED_DETECTOR_ACTION { - UNLINK, - } - \ No newline at end of file + UNLINK, +} diff --git a/public/expressions/index.ts b/public/expressions/index.ts index 70a6f1c9b..f8a2fd64b 100644 --- a/public/expressions/index.ts +++ b/public/expressions/index.ts @@ -9,4 +9,4 @@ * GitHub history for details. */ -export * from './overlay_anomalies'; \ No newline at end of file +export * from './overlay_anomalies'; diff --git a/public/plugin.tsx b/public/plugin.tsx index 7e687afbd..deddbd005 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -55,9 +55,9 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin implements Plugin { } public start(core: CoreStart, plugins) { - setSavedFeatureAnywhereLoader(plugins.visAugmenter.savedAugmentVisLoader) - return {}; -} + setSavedFeatureAnywhereLoader(plugins.visAugmenter.savedAugmentVisLoader); + return {}; + } public stop() {} } diff --git a/public/services.ts b/public/services.ts index 561ad3935..c2bbaf379 100644 --- a/public/services.ts +++ b/public/services.ts @@ -32,4 +32,4 @@ import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_u import { SavedObjectLoader } from '../../../src/plugins/saved_objects/public'; export const [getSavedFeatureAnywhereLoader, setSavedFeatureAnywhereLoader] = - createGetterSetter('savedFeatureAnywhereLoader'); \ No newline at end of file + createGetterSetter('savedFeatureAnywhereLoader'); diff --git a/public/utils/contextMenu/action.tsx b/public/utils/contextMenu/action.tsx index 91932b052..c1ad426ab 100644 --- a/public/utils/contextMenu/action.tsx +++ b/public/utils/contextMenu/action.tsx @@ -8,7 +8,7 @@ import CreateAnomalyDetector from '../../components/FeatureAnywhereContextMenu/C import { AssociatedDetectors } from '../../components/FeatureAnywhereContextMenu/AssociatedDetectors'; import { CoreServicesContext } from '../../components/CoreServices/CoreServices'; import { Provider } from 'react-redux'; -import configureStore from '../../redux/configureStore' +import configureStore from '../../redux/configureStore'; // This is used to create all actions in the same context menu const grouping: Action['grouping'] = [ @@ -54,9 +54,6 @@ export const getActions = ({ core }) => icon: 'wrench' as EuiIconType, order: 99, onClick: async ({ embeddable }) => { -<<<<<<< HEAD - console.log('manage ad'); -======= const services = await core.getStartServices(); const http = services[0].http; const store = configureStore(http); @@ -65,13 +62,19 @@ export const getActions = ({ core }) => toMountPoint( - overlay.close(), core, services }} /> + overlay.close(), + core, + services, + }} + /> ), { size: 'l' } ); ->>>>>>> 6e93ee7 (working js manage detectors) }, }, { From d31e655f41584fef1c2002c1edeaaa9450ab2bf6 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Tue, 21 Mar 2023 13:03:51 -0700 Subject: [PATCH 14/16] add unlinking capability Signed-off-by: Amit Galitzky --- .../containers/AssociatedDetectors.tsx | 80 +++++++++++++++---- .../AssociatedDetectors/styles.scss | 24 +++--- .../CreateAnomalyDetector/index.js | 23 ------ public/utils/contextMenu/action.tsx | 14 +++- 4 files changed, 88 insertions(+), 53 deletions(-) diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx index e8271f88c..2f9e981f4 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -20,6 +20,7 @@ import { EuiButton, EuiFlyout, EuiFlexItem, + EuiFlexGroup, } from '@elastic/eui'; import { get, isEmpty } from 'lodash'; import '../styles.scss'; @@ -88,6 +89,7 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { affectedDetector: {} as DetectorListItem, } ); + const savedObjectLoader: SavedObjectLoader = getSavedFeatureAnywhereLoader(); useEffect(() => { if ( @@ -130,8 +132,6 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { // Handle all filtering / sorting of detectors useEffect(() => { - const savedObjectLoader: SavedObjectLoader = - getSavedFeatureAnywhereLoader(); // Gets all augmented saved objects savedObjectLoader.findAll().then((resp: any) => { if (resp != undefined) { @@ -171,6 +171,40 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { return detectorsToDisplay; }; + const onUnlinkDetector = async () => { + setIsLoadingFinalDetectors(true); + await savedObjectLoader.findAll().then(async (resp: any) => { + if (resp != undefined) { + const savedAugmentObjects: ISavedAugmentVis[] = get(resp, 'hits', []); + // gets all the saved object for this visualization + const savedAugmentForThisVisualization: ISavedAugmentVis[] = + savedAugmentObjects.filter( + (savedObj) => get(savedObj, 'visId', '') === embeddable.vis.id + ); + + const savedAugmentToUnlink = savedAugmentForThisVisualization.filter( + (savedObject) => + get(savedObject, 'pluginResourceId', '') === detectorToUnlink.id + ); + + const savedObjectToUnlinkId = get(savedAugmentToUnlink[0], 'id', ''); + await savedObjectLoader + .delete(savedObjectToUnlinkId) + .then((resp: any) => {}) + .catch((error) => { + core.notifications.toasts.addDanger( + prettifyErrorMessage( + `Error unlinking selected detector: ${error}` + ) + ); + }) + .finally(() => { + getDetectors(); + }); + } + }); + }; + const getUnlinkConfirmModal = () => { if (confirmModalState.isOpen) { return ( @@ -200,6 +234,7 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { }; const getDetectors = async () => { + console.log('inside getDetectors()'); dispatch(getDetectorList(GET_ALL_DETECTORS_QUERY_PARAMS)); }; @@ -209,6 +244,9 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { console.log('response: ' + JSON.stringify(resp)); }; + const onAssociateExistingDetector = async () => { + console.log('inside create anomaly detector'); + }; // This method is only here for development/testing purposes. const createSavedObjects = async () => { enum VisLayerTypes { @@ -221,10 +259,10 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { testArg: selectedDetectors[0].id, }, } as VisLayerExpressionFn; - + console.log('all Detectors: ' + JSON.stringify(allDetectors)); const savedObjectToCreate = { title: 'test-title', - pluginResourceId: selectedDetectors[0].id, + pluginResourceId: 'bNZIp4UB3stq6UHwpWwS', visId: embeddable.vis.id, savedObjectType: 'visualization', visLayerExpressionFn: fn, @@ -235,10 +273,7 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { const response = await savedObject.save({}); console.log('response: ' + JSON.stringify(response)); - }; - - const onUnlinkDetector = async () => { - console.log('detectorToUnlink', detectorToUnlink); + getDetectors(); }; const handleUnlinkDetectorAction = (detector: DetectorListItem) => { @@ -290,15 +325,35 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { return ( //
- +

Associated anomaly detectors

- + {getUnlinkConfirmModal()} {/* below buttons are just here for development/testing purposes*/} + + + +

{embeddableTitle}

+
+
+ + { + onAssociateExistingDetector(); + }} + > + Associate a detector + + +
+ + { @@ -317,11 +372,6 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { Get Saved Objects - -

{embeddableTitle}

-
- -
//
diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss index e6520e0e6..2243b5f96 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss @@ -1,16 +1,16 @@ @import '@elastic/eui/src/global_styling/variables/index'; -.associated-detectors { - height: 100%; - display: flex; - flex-direction: column; +// .associated-detectors { +// height: 100%; +// display: flex; +// flex-direction: column; - .euiFlyoutBody__overflowContent { - height: 100%; - padding-bottom: 0; - } +// .euiFlyoutBody__overflowContent { +// height: 100%; +// padding-bottom: 0; +// } - &__flex-group { - height: 100%; - } -} +// &__flex-group { +// height: 100%; +// } +// } diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js index 131e55831..c0d804717 100644 --- a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js +++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.js @@ -18,8 +18,6 @@ import './styles.scss'; import { EmbeddablePanel } from '../../../../../../src/plugins/embeddable/public'; import DetectorDetails from './DetectorDetails'; import Features from './Features'; -import ShingleSize from './ShingleSize'; -import { util } from 'node-forge'; const accordions = [ 'detectorDetails', @@ -32,27 +30,6 @@ const accordions = [ function CreateAnomalyDetector({ embeddable }) { const [radio, setRadio] = useState('createRadio'); const [accordionOpen, setAccordionOpen] = useState(accordions.triggers); - - function simpleStringify (object){ - var simpleObject = {}; - for (var prop in object ){ - if (!object.hasOwnProperty(prop)){ - continue; - } - if (typeof(object[prop]) == 'object'){ - continue; - } - if (typeof(object[prop]) == 'function'){ - continue; - } - simpleObject[prop] = object[prop]; - } - return JSON.stringify(simpleObject); // returns cleaned up JSON -}; - - console.log("embeddable: " + simpleStringify(embeddable)) - console.log("embed json: " + (JSON.parse(simpleStringify(embeddable)))) -// // safely handles circular references return (
diff --git a/public/utils/contextMenu/action.tsx b/public/utils/contextMenu/action.tsx index c1ad426ab..2a775c655 100644 --- a/public/utils/contextMenu/action.tsx +++ b/public/utils/contextMenu/action.tsx @@ -35,10 +35,18 @@ export const getActions = ({ core }) => order: 100, onClick: async ({ embeddable }) => { const services = await core.getStartServices(); + const http = services[0].http; + const store = configureStore(http); const openFlyout = services[0].overlays.openFlyout; openFlyout( - toMountPoint(), - { size: 'l' } + toMountPoint( + + + + + + ), + { size: 'm' } ); }, }, @@ -73,7 +81,7 @@ export const getActions = ({ core }) => ), - { size: 'l' } + { size: 'm' } ); }, }, From c8a29d1dd0290bcb23d0c9f01a61f2e6812e895c Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Tue, 21 Mar 2023 15:56:25 -0700 Subject: [PATCH 15/16] adding message for no search results Signed-off-by: Amit Galitzky --- .../components/EmptyMessage/EmptyMessage.tsx | 5 +-- .../containers/AssociatedDetectors.tsx | 37 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx index 996de747e..79eaddbc6 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx @@ -15,7 +15,7 @@ import React from 'react'; const FILTER_TEXT = 'There are no detectors matching your search'; interface EmptyDetectorProps { - //isFilterApplied: boolean; + isFilterApplied: boolean; embeddableTitle: string; } @@ -29,8 +29,7 @@ export const EmptyAssociatedDetectorFlyoutMessage = ( style={{ maxWidth: '45em' }} body={ -

{`There are no anomaly detectors associated with ${props.embeddableTitle} visualization.`}

- {/*

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

*/} +

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

} /> diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx index 2f9e981f4..ba8d8c1eb 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -129,7 +129,7 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { useEffect(() => { getDetectors(); }, []); - + // Handle all filtering / sorting of detectors useEffect(() => { // Gets all augmented saved objects @@ -167,7 +167,7 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { const detectorsToDisplay = detectors.filter((detector) => savedAugmentDetectorsSet.has(detector.id) ); - console.log('detectorsToDisplay: ' + JSON.stringify(detectorsToDisplay)); + //console.log('detectorsToDisplay: ' + JSON.stringify(detectorsToDisplay)); return detectorsToDisplay; }; @@ -234,18 +234,17 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { }; const getDetectors = async () => { - console.log('inside getDetectors()'); dispatch(getDetectorList(GET_ALL_DETECTORS_QUERY_PARAMS)); }; // This method is only here for development/testing purposes. const getSavedObjects = async () => { const resp = await getSavedFeatureAnywhereLoader().findAll(); - console.log('response: ' + JSON.stringify(resp)); + //console.log('response: ' + JSON.stringify(resp)); }; const onAssociateExistingDetector = async () => { - console.log('inside create anomaly detector'); + console.log('inside create anomaly detector'); }; // This method is only here for development/testing purposes. const createSavedObjects = async () => { @@ -277,7 +276,6 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { }; const handleUnlinkDetectorAction = (detector: DetectorListItem) => { - //console.log('onUnlink: ', detector); setDetectorToUnlink(detector); if (!isEmpty(detector)) { setConfirmModalState({ @@ -300,6 +298,22 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { [handleUnlinkDetectorAction] ); + const renderEmptyMessage = () => { + if (isLoading) { + return 'Loading detectors...' + } else if (!isEmpty(selectedDetectors)) { + return (); + } else { + return (); + } + } + const tableProps = { items: selectedDetectors, columns, @@ -313,17 +327,10 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { hasActions: true, pagination: true, sorting: true, - message: isLoading ? ( - 'Loading detectors...' - ) : ( - - ), + message: renderEmptyMessage(), }; - return ( + //
From eaa330009fae4ad435f6da14002c85dfc80e4597 Mon Sep 17 00:00:00 2001 From: Amit Galitzky Date: Mon, 27 Mar 2023 10:16:44 -0700 Subject: [PATCH 16/16] clean up files Signed-off-by: Amit Galitzky --- .../components/EmptyMessage/EmptyMessage.tsx | 6 +- .../containers/AssociatedDetectors.tsx | 179 +++++++----------- .../AssociatedDetectors/styles.scss | 24 +-- .../AssociatedDetectors/utils/helpers.tsx | 1 - 4 files changed, 84 insertions(+), 126 deletions(-) diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx index 79eaddbc6..aae18dc6e 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/components/EmptyMessage/EmptyMessage.tsx @@ -29,7 +29,11 @@ export const EmptyAssociatedDetectorFlyoutMessage = ( style={{ maxWidth: '45em' }} body={ -

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

+

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

} /> diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx index ba8d8c1eb..55edd1ad7 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/containers/AssociatedDetectors.tsx @@ -13,7 +13,6 @@ import React, { useMemo, useEffect, useState } from 'react'; import { EuiFlyoutHeader, EuiTitle, - EuiText, EuiSpacer, EuiInMemoryTable, EuiFlyoutBody, @@ -42,11 +41,7 @@ import { } from '../../../../../server/utils/helpers'; import { SavedObjectLoader } from '../../../../../../../src/plugins/saved_objects/public'; import { EmptyAssociatedDetectorFlyoutMessage } from '../components/EmptyMessage/EmptyMessage'; -import { - createAugmentVisSavedObject, - ISavedAugmentVis, - VisLayerExpressionFn, -} from '../../../../../../../src/plugins/vis_augmenter/public'; +import { ISavedAugmentVis } from '../../../../../../../src/plugins/vis_augmenter/public'; import { ASSOCIATED_DETECTOR_ACTION } from '../utils/constants'; import { ConfirmUnlinkDetectorModal } from '../components/ConfirmUnlinkDetectorModal/ConfirmUnlinkDetectorModal'; @@ -89,6 +84,8 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { affectedDetector: {} as DetectorListItem, } ); + + // Establish savedObjectLoader for all operations on vis augmented saved objects const savedObjectLoader: SavedObjectLoader = getSavedFeatureAnywhereLoader(); useEffect(() => { @@ -107,7 +104,7 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { } }, [errorGettingDetectors]); - // Update modal state if user decides to close + // Update modal state if user decides to close modal useEffect(() => { if (confirmModalState.isRequestingToClose) { if (isLoading) { @@ -129,8 +126,8 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { useEffect(() => { getDetectors(); }, []); - - // Handle all filtering / sorting of detectors + + // 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) => { @@ -151,23 +148,28 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { }, [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, 'pluginResourceId', '') ) ); + + // 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) ); - //console.log('detectorsToDisplay: ' + JSON.stringify(detectorsToDisplay)); return detectorsToDisplay; }; @@ -182,15 +184,15 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { (savedObj) => get(savedObj, 'visId', '') === embeddable.vis.id ); - const savedAugmentToUnlink = savedAugmentForThisVisualization.filter( + // 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, 'pluginResourceId', '') === detectorToUnlink.id ); - - const savedObjectToUnlinkId = get(savedAugmentToUnlink[0], 'id', ''); + const savedObjectToUnlinkId = get(savedAugmentToUnlink, 'id', ''); await savedObjectLoader .delete(savedObjectToUnlinkId) - .then((resp: any) => {}) .catch((error) => { core.notifications.toasts.addDanger( prettifyErrorMessage( @@ -237,42 +239,10 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { dispatch(getDetectorList(GET_ALL_DETECTORS_QUERY_PARAMS)); }; - // This method is only here for development/testing purposes. - const getSavedObjects = async () => { - const resp = await getSavedFeatureAnywhereLoader().findAll(); - //console.log('response: ' + JSON.stringify(resp)); - }; - + // TODO: this part is incomplete because it is pending on complete the work for associating an existing + // detector which is dependent on changes in the action.tsx code that jackie will merge in const onAssociateExistingDetector = async () => { - console.log('inside create anomaly detector'); - }; - // This method is only here for development/testing purposes. - const createSavedObjects = async () => { - enum VisLayerTypes { - PointInTimeEvents = 'PointInTimeEvents', - } - const fn = { - type: VisLayerTypes.PointInTimeEvents, - name: 'test-fn', - args: { - testArg: selectedDetectors[0].id, - }, - } as VisLayerExpressionFn; - console.log('all Detectors: ' + JSON.stringify(allDetectors)); - const savedObjectToCreate = { - title: 'test-title', - pluginResourceId: 'bNZIp4UB3stq6UHwpWwS', - visId: embeddable.vis.id, - savedObjectType: 'visualization', - visLayerExpressionFn: fn, - } as ISavedAugmentVis; - - const savedObject = await createAugmentVisSavedObject(savedObjectToCreate); - console.log('savedObject: ' + JSON.stringify(savedObject)); - - const response = await savedObject.save({}); - console.log('response: ' + JSON.stringify(response)); - getDetectors(); + console.log('inside create anomaly detector'); }; const handleUnlinkDetectorAction = (detector: DetectorListItem) => { @@ -286,7 +256,6 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { affectedDetector: detector, }); } else { - // might not need this core.notifications.toasts.addWarning( 'Make sure selected detector has not been deleted' ); @@ -300,19 +269,23 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { const renderEmptyMessage = () => { if (isLoading) { - return 'Loading detectors...' + return 'Loading detectors...'; } else if (!isEmpty(selectedDetectors)) { - return (); + return ( + + ); } else { - return (); + return ( + + ); } - } + }; const tableProps = { items: selectedDetectors, @@ -330,57 +303,39 @@ export const AssociatedDetectors = ({ embeddable, closeFlyout }) => { message: renderEmptyMessage(), }; return ( - - //
- - - -

Associated anomaly detectors

-
-
- - {getUnlinkConfirmModal()} - {/* below buttons are just here for development/testing purposes*/} - - - -

{embeddableTitle}

-
-
- - { - onAssociateExistingDetector(); - }} - > - Associate a detector - - -
- - - - { - createSavedObjects(); - }} - > - Create saved objects{' '} - - - - { - getSavedObjects(); - }} - > - Get Saved Objects - - -
-
- //
+
+ + + +

+ Associated anomaly detectors +

+
+
+ + {getUnlinkConfirmModal()} + + + +

{embeddableTitle}

+
+
+ + { + onAssociateExistingDetector(); + }} + > + Associate a detector + + +
+ + +
+
+
); }; diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss index 2243b5f96..e6520e0e6 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/styles.scss @@ -1,16 +1,16 @@ @import '@elastic/eui/src/global_styling/variables/index'; -// .associated-detectors { -// height: 100%; -// display: flex; -// flex-direction: column; +.associated-detectors { + height: 100%; + display: flex; + flex-direction: column; -// .euiFlyoutBody__overflowContent { -// height: 100%; -// padding-bottom: 0; -// } + .euiFlyoutBody__overflowContent { + height: 100%; + padding-bottom: 0; + } -// &__flex-group { -// height: 100%; -// } -// } + &__flex-group { + height: 100%; + } +} diff --git a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx index 6441fa378..b4668a4fd 100644 --- a/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx +++ b/public/components/FeatureAnywhereContextMenu/AssociatedDetectors/utils/helpers.tsx @@ -17,7 +17,6 @@ import { PLUGIN_NAME } from '../../../../utils/constants'; import { Detector } from '../../../../models/interfaces'; export const renderState = (state: DETECTOR_STATE) => { - console.log('detector State: ' + state); return ( //@ts-ignore {state}