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/action/ad_dashboard_action.tsx b/public/action/ad_dashboard_action.tsx
new file mode 100644
index 000000000..0d8c43894
--- /dev/null
+++ b/public/action/ad_dashboard_action.tsx
@@ -0,0 +1,74 @@
+import { IEmbeddable } from '../../../../src/plugins/dashboard/public/embeddable_plugin';
+import {
+ DASHBOARD_CONTAINER_TYPE,
+ DashboardContainer,
+} from '../../../../src/plugins/dashboard/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 {
+ return embeddable.type === DASHBOARD_CONTAINER_TYPE;
+}
+
+export interface ActionContext {
+ embeddable: IEmbeddable;
+}
+
+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 title;
+ },
+ getIconType: () => icon,
+ type: ACTION_AD,
+ grouping,
+ 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();
+ }
+
+ onClick({ embeddable });
+ },
+ });
\ No newline at end of file
diff --git a/public/components/ContextMenu/CreateAnomalyDetector/index.tsx b/public/components/ContextMenu/CreateAnomalyDetector/index.tsx
new file mode 100644
index 000000000..f84c2ba37
--- /dev/null
+++ b/public/components/ContextMenu/CreateAnomalyDetector/index.tsx
@@ -0,0 +1,91 @@
+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/FeatureAnywhereContextMenu/Container/Container.js b/public/components/FeatureAnywhereContextMenu/Container/Container.js
new file mode 100644
index 000000000..61c0abef7
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/Container/Container.js
@@ -0,0 +1,28 @@
+import React, { useState } from 'react';
+import CreateAnomalyDetector from '../CreateAnomalyDetector';
+import { useIndex } from '../../../utils/contextMenu/indexes';
+import './styles.scss';
+
+const Container = ({ startingFlyout, ...props }) => {
+ const { embeddable } = props;
+ console.log({ embeddable });
+ const index = useIndex(embeddable);
+ const [mode, setMode] = useState(startingFlyout);
+
+ const Flyout = {
+ create: CreateAnomalyDetector,
+ }[mode];
+
+ return (
+
+ );
+};
+
+export default Container;
diff --git a/public/components/FeatureAnywhereContextMenu/Container/index.js b/public/components/FeatureAnywhereContextMenu/Container/index.js
new file mode 100644
index 000000000..6a0f64ace
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/Container/index.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import Container from './Container';
+
+export default Container;
diff --git a/public/components/FeatureAnywhereContextMenu/Container/styles.scss b/public/components/FeatureAnywhereContextMenu/Container/styles.scss
new file mode 100644
index 000000000..376c85572
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/Container/styles.scss
@@ -0,0 +1,7 @@
+.context-menu {
+ &__flyout {
+ &.euiFlyout--medium {
+ width: 740px;
+ }
+ }
+}
diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx
new file mode 100644
index 000000000..5f3a7b14f
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/AddAnomalyDetector.tsx
@@ -0,0 +1,104 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import {
+ EuiFlexItem,
+ EuiFlexGroup,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiTitle,
+ EuiSpacer,
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFormFieldset,
+ EuiCheckableCard,
+} from '@elastic/eui';
+import './styles.scss';
+import CreateNew from './CreateNew';
+
+function AddAnomalyDetector({
+ embeddable,
+ closeFlyout,
+ core,
+ services,
+ mode,
+ setMode,
+ index,
+}) {
+ const onCreate = () => {
+ console.log(`Current mode: ${mode}`);
+ const event = new Event('createDetector');
+ document.dispatchEvent(event);
+ closeFlyout();
+ };
+
+ return (
+
+
+
+ Add anomaly detector
+
+
+
+
+
+ Options to create a new detector or associate an existing detector
+
+ ),
+ }}
+ className="add-anomaly-detector__modes"
+ >
+ {[
+ {
+ id: 'add-anomaly-detector__create',
+ label: 'Create new detector',
+ value: 'create',
+ },
+ {
+ id: 'add-anomaly-detector__existing',
+ label: 'Associate existing detector',
+ value: 'existing',
+ },
+ ].map((option) => (
+ setMode(option.value),
+ }}
+ />
+ ))}
+
+
+ {mode === 'create' && (
+
+ )}
+
+
+
+
+
+ Cancel
+
+
+
+ {mode === 'existing' ? 'Associate' : 'Create'} detector
+
+
+
+
+
+ );
+}
+
+export default AddAnomalyDetector;
diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/CreateNew.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/CreateNew.js
new file mode 100644
index 000000000..b626ebe22
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/CreateNew.js
@@ -0,0 +1,474 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useState } from 'react';
+import {
+ EuiTitle,
+ EuiSpacer,
+ EuiIcon,
+ EuiText,
+ EuiSwitch,
+ EuiLoadingSpinner,
+ EuiPanel,
+ EuiAccordion,
+ EuiFormRow,
+ EuiFieldText,
+ EuiCheckbox,
+ EuiSelect,
+ EuiFlexItem,
+ EuiFlexGroup,
+ EuiFieldNumber,
+ EuiCallOut
+ } from '@elastic/eui';
+import { EmbeddablePanel } from '../../../../../../../src/plugins/embeddable/public';
+import './styles.scss';
+import EnhancedAccordion from '../../EnhancedAccordion';
+
+function CreateNew({ embeddable, closeFlyout, core, services, index }) {
+ const [isShowVis, setIsShowVis] = useState(false);
+ const title = embeddable.getTitle();
+ const history = {
+ location: { pathname: '/create-detector', search: '', hash: '', state: undefined },
+ push: (value) => console.log('pushed', value),
+ goBack: closeFlyout,
+ };
+ const createMonitorProps = {
+ ...history,
+ history,
+ httpClient: core.http,
+ // This is not expected to be used
+ setFlyout: () => null,
+ notifications: core.notifications,
+ isDarkMode: core.isDarkMode,
+ notificationService: services.notificationService,
+ edit: false,
+ updateMonitor: () => null,
+ staticContext: undefined,
+ isMinimal: true,
+ defaultName: `${title} anomaly detector 1`,
+ defaultIndex: index,
+ defaultTimeField: embeddable.vis.params.time_field,
+ isDefaultTriggerEnabled: true,
+ };
+
+ 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 detectorNameFromVis = embeddable.vis.title + ' anomaly detector 1';
+ const featureList = embeddable.vis.params.series;
+ console.log("feature list: " + featureList)
+ console.log("feature name: " + featureList[0].label)
+
+ const anomaliesOptions = [
+ { value: 'option_one', text: 'Field value' },
+ { value: 'option_two', text: 'Custom expression' },
+ ];
+ const [anomaliesValue, setAnomaliesValue] = useState(anomaliesOptions[0].value);
+ const anomaliesOnChange = (e) => {
+ setAnomaliesValue(e.target.value);
+ };
+
+ const aggMethodOptions = [
+ { value: 'avg', text: 'AVG' },
+ { value: 'sum', text: 'SUM' },
+ ];
+ const [aggMethodValue, setAggMethodValue] = useState(
+ featureList[0].metrics[0].type
+ );
+ const aggMethodOnChange = (e) => {
+ setAggMethodValue(e.target.value);
+ };
+
+ const [shingleSizeValue, setShingleSizeValue] = useState('');
+
+ const shingleSizeOnChange = (e) => {
+ setShingleSizeValue(e.target.value);
+ };
+
+ const [checked, setChecked] = useState(false);
+ const onCustomerResultIndexCheckboxChange = (e) => {
+ setChecked(e.target.checked);
+ };
+
+ const detectorDetailsOnToggle = (isOpen) => {
+ setIsOpen(isOpen ? 'open' : 'closed');
+ };
+
+ const [isOpen, setIsOpen] = useState('open');
+
+ return (
+
+
+
+ Create and configure an anomaly detector to automatically detect anomalies in your data and
+ to view real-time results on the visualization. {' '}
+
+ Learn more
+
+
+
+
+
+
+
+
+ {title}
+
+
+ setIsShowVis(!isShowVis)}
+ />
+
+
+
+ Promise.resolve([])}
+ inspector={{ isAvailable: () => false }}
+ hideHeader
+ isRetained
+ isBorderless
+ />
+
+
+
+ Detector details
+
+
+ {/* {!index &&
} */}
+ {/* Do not initialize until index is available */}
+
+ {/*
+
+ Detector interval: 10 minutes; Window delay: 1 minutesss
+
+
+ }>
+
+
+
+
+
+ intervalOnChange(e)}
+ />
+
+
+ delayOnChange(e)}
+ />
+
+
+ */}
+
+
+
+
+
+ Detector interval: 10 minutes; Window delay: 1 minute
+
+
+
+
+
+
+
+
+ intervalOnChange(e)}
+ />
+
+
+ delayOnChange(e)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Set number of intervals in the model's detection window.
+
+
+
+
+
+ The anomaly detector expects the single size to be between 1 and 60. The default shingle size
+ is 8. We recommend that you don't choose 1 unless you have 2 or more features. Smaller values
+ might increase recall but also false positives. Larger values might be useful for ignoring
+ noise in a signal.
+
+ Learn more
+
+
+
+
+
+ shingleSizeOnChange(e)}
+ aria-label="intervals"
+ />
+
+
+
+
+
+
+
+
+
+ Store detector results to our own index.
+
+
+
+
+
+ onCustomerResultIndexCheckboxChange(e)}
+ />
+
+
+
+
+
+
+
+
+ Split a single time series into multiple time series based on categorical fields.
+
+
+
+
+
+
+ The dashboard does not support high-cardinality detectors.
+
+ Learn more
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Model Features
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ anomaliesOnChange(e)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ aggMethodOnChange(e)}
+ />
+
+
+
+
+
+
+
+
+ Field: {featureList[0].metrics[0].field}, Aggregation method: {featureList[0].metrics[0].type}
+
+
+
+
+
+
+
+
+
+
+
+ anomaliesOnChange(e)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ aggMethodOnChange(e)}
+ />
+
+
+
+
+
+
+
+
+
+ Field: {featureList[1].metrics[0].field}, Aggregation method: {featureList[1].metrics[0].type}
+
+
+
+
+
+
+
+
+
+
+
+ anomaliesOnChange(e)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ aggMethodOnChange(e)}
+ />
+
+
+
+
+
+
+
+
+
+ Field: {featureList[2].metrics[0].field}, Aggregation method: {featureList[2].metrics[0].type}
+
+
+
+
+
+
+
+ );
+}
+
+export default CreateNew;
diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/index.js b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/index.js
new file mode 100644
index 000000000..95b553cce
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/index.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import CreateNew from './CreateNew';
+
+export default CreateNew;
diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/styles.scss b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/styles.scss
new file mode 100644
index 000000000..5e0d1c20f
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/CreateNew/styles.scss
@@ -0,0 +1,29 @@
+.create-new {
+ &__vis {
+ height: 400px;
+
+ &--hidden {
+ display: none;
+ }
+ }
+
+ &__title-and-toggle {
+ display: flex;
+ justify-content: space-between;
+ }
+
+ &__title-icon {
+ margin-right: 10px;
+ vertical-align: middle;
+ }
+
+ .visualization {
+ padding: 0;
+ }
+
+ &__frequency {
+ span {
+ text-transform: lowercase;
+ }
+ }
+}
diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.tsx b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.tsx
new file mode 100644
index 000000000..cacc501e0
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/index.tsx
@@ -0,0 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import AddAnomalyDetector from './AddAnomalyDetector';
+
+export default AddAnomalyDetector;
diff --git a/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss
new file mode 100644
index 000000000..a5cf2471a
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss
@@ -0,0 +1,24 @@
+.add-anomaly-detector {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .euiFlyoutBody__overflowContent {
+ height: 100%;
+ padding-bottom: 0;
+ }
+
+ .euiFlexItem.add-anomaly-detector__scroll {
+ overflow-y: auto;
+ }
+
+ &__flex-group {
+ height: 100%;
+ }
+
+ &__modes {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
+ }
+}
diff --git a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/DocumentationTitle.js b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/DocumentationTitle.js
new file mode 100644
index 000000000..3edbb24a5
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/DocumentationTitle.js
@@ -0,0 +1,25 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { EuiIcon, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
+import { i18n } from '@osd/i18n';
+
+const DocumentationTitle = () => (
+
+
+
+ {i18n.translate('dashboard.actions.adMenuItem.documentation.displayName', {
+ defaultMessage: 'Documentation',
+ })}
+
+
+
+
+
+
+);
+
+export default DocumentationTitle;
diff --git a/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.js b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.js
new file mode 100644
index 000000000..a60ab0b46
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/DocumentationTitle/index.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import DocumentationTitle from './DocumentationTitle';
+
+export default DocumentationTitle;
diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js
new file mode 100644
index 000000000..b745d3310
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import {
+ EuiTitle,
+ EuiSpacer,
+ EuiButtonIcon,
+ EuiButtonEmpty,
+ EuiAccordion,
+ EuiPanel,
+} from '@elastic/eui';
+import './styles.scss';
+
+const EnhancedAccordion = ({
+ id,
+ title,
+ subTitle,
+ isOpen,
+ onToggle,
+ children,
+ isButton,
+ iconType,
+ extraAction,
+}) => (
+
+
+
+
+
+ {!isButton && (
+ {extraAction}
}
+ forceState={isOpen ? 'open' : 'closed'}
+ onToggle={onToggle}
+ buttonContent={
+
+
+ {title}
+
+
+ {subTitle && (
+ <>
+
+ {subTitle}
+ >
+ )}
+
+ }
+ >
+
+ {children}
+
+
+ )}
+ {isButton && (
+
+
+ )}
+
+
+);
+
+export default EnhancedAccordion;
\ No newline at end of file
diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js
new file mode 100644
index 000000000..23d43a83c
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/EnhancedAccordion.test.js
@@ -0,0 +1,15 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import EnhancedAccordion from './EnhancedAccordion';
+
+describe('EnhancedAccordion', () => {
+ test('renders', () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+ });
+});
\ No newline at end of file
diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js
new file mode 100644
index 000000000..3226d34c2
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/index.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import EnhancedAccordion from './EnhancedAccordion';
+
+export default EnhancedAccordion;
\ No newline at end of file
diff --git a/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/styles.scss b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/styles.scss
new file mode 100644
index 000000000..88ca19a17
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/EnhancedAccordion/styles.scss
@@ -0,0 +1,28 @@
+.enhanced-accordion {
+ &__arrow {
+ transition: rotate .3s;
+ rotate: 0deg;
+
+ &--open {
+ rotate: 90deg;
+ }
+
+ &--hidden {
+ visibility: hidden;
+ }
+ }
+
+ &__title {
+ padding: 12px 16px;
+ }
+
+ &__extra {
+ padding-right: 16px;
+ }
+
+ &__button {
+ width: 100%;
+ height: 100%;
+ min-height: 50px;
+ }
+}
\ No newline at end of file
diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js
new file mode 100644
index 000000000..f2b5902ae
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import {
+ EuiHorizontalRule,
+ EuiTitle,
+ EuiAccordion,
+ EuiSpacer,
+ EuiPanel,
+ EuiTextColor,
+ EuiText,
+} from '@elastic/eui';
+import './styles.scss';
+
+function MinimalAccordion({ id, isOpen, onToggle, title, subTitle, children, isUsingDivider }) {
+ return (
+
+ {isUsingDivider && (
+ <>
+
+
+ >
+ )}
+
+
+ {title}
+
+ {subTitle && (
+
+ {subTitle}
+
+ )}
+ >
+ }
+ forceState={isOpen ? 'open' : 'closed'}
+ onToggle={onToggle}
+ >
+
+ {children}
+
+
+
+ );
+}
+
+export default MinimalAccordion;
\ No newline at end of file
diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js
new file mode 100644
index 000000000..3da83605c
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/MinimalAccordion.test.js
@@ -0,0 +1,15 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { shallow } from 'enzyme';
+import MinimalAccordion from './MinimalAccordion';
+
+describe('MinimalAccordion', () => {
+ test('renders', () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js
new file mode 100644
index 000000000..b4e1d5392
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/index.js
@@ -0,0 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import MinimalAccordion from './MinimalAccordion';
+
+export default MinimalAccordion;
\ No newline at end of file
diff --git a/public/components/FeatureAnywhereContextMenu/MinimalAccordion/styles.scss b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/styles.scss
new file mode 100644
index 000000000..0afb10cf7
--- /dev/null
+++ b/public/components/FeatureAnywhereContextMenu/MinimalAccordion/styles.scss
@@ -0,0 +1,24 @@
+.minimal-accordion {
+ .euiAccordion__button {
+ align-items: flex-start;
+
+ &:hover,
+ &:focus {
+ text-decoration: none;
+
+ .minimal-accordion__title {
+ text-decoration: underline;
+ }
+ }
+ }
+
+ &__title {
+ margin-top: -5px;
+ font-weight: 400;
+ }
+
+ &__panel {
+ padding-left: 28px;
+ padding-bottom: 0;
+ }
+}
\ No newline at end of file
diff --git a/public/plugin.ts b/public/plugin.ts
deleted file mode 100644
index 7ee985bff..000000000
--- a/public/plugin.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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 {
- AppMountParameters,
- CoreSetup,
- CoreStart,
- Plugin,
- PluginInitializerContext,
-} from '../../../src/core/public';
-import {
- AnomalyDetectionOpenSearchDashboardsPluginSetup,
- AnomalyDetectionOpenSearchDashboardsPluginStart,
-} from '.';
-
-export class AnomalyDetectionOpenSearchDashboardsPlugin
- implements
- Plugin<
- AnomalyDetectionOpenSearchDashboardsPluginSetup,
- AnomalyDetectionOpenSearchDashboardsPluginStart
- >
-{
- constructor(private readonly initializerContext: PluginInitializerContext) {
- // can retrieve config from initializerContext
- }
-
- public setup(
- core: CoreSetup
- ): AnomalyDetectionOpenSearchDashboardsPluginSetup {
- core.application.register({
- id: 'anomaly-detection-dashboards',
- title: 'Anomaly Detection',
- category: {
- id: 'opensearch',
- label: 'OpenSearch Plugins',
- order: 2000,
- },
- order: 5000,
- mount: async (params: AppMountParameters) => {
- const { renderApp } = await import('./anomaly_detection_app');
- const [coreStart, depsStart] = await core.getStartServices();
- return renderApp(coreStart, params);
- },
- });
- return {};
- }
-
- public start(
- core: CoreStart
- ): AnomalyDetectionOpenSearchDashboardsPluginStart {
- return {};
- }
-}
diff --git a/public/plugin.tsx b/public/plugin.tsx
new file mode 100644
index 000000000..d192a5f09
--- /dev/null
+++ b/public/plugin.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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 {
+ AppMountParameters,
+ CoreSetup,
+ Plugin,
+ PluginInitializerContext,
+} from '../../../src/core/public';
+import { CONTEXT_MENU_TRIGGER } from '../../../src/plugins/embeddable/public';
+import { ACTION_AD } from './action/ad_dashboard_action';
+import { PLUGIN_NAME } from './utils/constants';
+import { getActions } from './utils/contextMenu/getActions';
+
+declare module '../../../src/plugins/ui_actions/public' {
+ export interface ActionContextMapping {
+ [ACTION_AD]: {};
+ }
+}
+
+export class AnomalyDetectionOpenSearchDashboardsPlugin implements Plugin {
+ public setup(core: CoreSetup, plugins) {
+ core.application.register({
+ id: PLUGIN_NAME,
+ title: 'Anomaly Detection',
+ category: {
+ id: 'opensearch',
+ label: 'OpenSearch Plugins',
+ order: 2000,
+ },
+ order: 5000,
+ mount: async (params: AppMountParameters) => {
+ const { renderApp } = await import('./anomaly_detection_app');
+ const [coreStart] = await core.getStartServices();
+ return renderApp(coreStart, params);
+ },
+ });
+
+ // 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() {}
+
+ public stop() {}
+}
\ No newline at end of file
diff --git a/public/utils/contextMenu/getActions.tsx b/public/utils/contextMenu/getActions.tsx
new file mode 100644
index 000000000..fc659f186
--- /dev/null
+++ b/public/utils/contextMenu/getActions.tsx
@@ -0,0 +1,76 @@
+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 '../../action/ad_dashboard_action';
+import Container from '../../components/FeatureAnywhereContextMenu/Container';
+import DocumentationTitle from '../../components/FeatureAnywhereContextMenu/DocumentationTitle';
+
+// 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',
+ },
+];
+
+export const getActions = ({ core, plugins }) => {
+ const getOnClick =
+ (startingFlyout) =>
+ async ({ embeddable }) => {
+ const services = await core.getStartServices();
+ const openFlyout = services[0].overlays.openFlyout;
+ const overlay = openFlyout(
+ toMountPoint(
+ overlay.close(),
+ core,
+ services,
+ }}
+ />
+ ),
+ { size: 'm', className: 'context-menu__flyout' }
+ );
+ };
+
+ return [
+ {
+ grouping,
+ id: 'createAnomalyDetector',
+ title: i18n.translate('dashboard.actions.adMenuItem.createAnomalyDetector.displayName', {
+ defaultMessage: 'Create anomaly detector',
+ }),
+ icon: 'plusInCircle' as EuiIconType,
+ order: 100,
+ onClick: getOnClick('create'),
+ },
+ {
+ grouping,
+ id: 'associatedAnomalyDetector',
+ title: i18n.translate('dashboard.actions.adMenuItem.associatedAnomalyDetector.displayName', {
+ defaultMessage: 'Associated anomaly detector',
+ }),
+ icon: 'gear' as EuiIconType,
+ order: 99,
+ onClick: getOnClick('associated'),
+ },
+ {
+ id: 'documentation',
+ title: ,
+ icon: 'documentation' as EuiIconType,
+ order: 98,
+ onClick: () => {
+ window.open(
+ 'https://opensearch.org/docs/latest/monitoring-plugins/anomaly-detection/index/',
+ '_blank'
+ );
+ },
+ },
+ ].map((options) => createADAction({ ...options, grouping }));
+}
\ No newline at end of file
diff --git a/public/utils/contextMenu/indexes.ts b/public/utils/contextMenu/indexes.ts
new file mode 100644
index 000000000..e8476a6e1
--- /dev/null
+++ b/public/utils/contextMenu/indexes.ts
@@ -0,0 +1,23 @@
+import { useState, useEffect } from 'react';
+
+export const useIndex = (embeddable) => {
+ const [index, setIndex] = useState();
+
+ useEffect(() => {
+ const getIndex = async () => {
+ await new Promise((resolve) => {
+ setTimeout(resolve, 4000);
+ });
+
+ const newIndex = [
+ { health: 'green', label: 'opensearch_dashboards_sample_data_logs', status: 'open' },
+ ];
+
+ setIndex(newIndex);
+ };
+
+ getIndex();
+ }, [embeddable]);
+
+ return index;
+};
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