From 67f7e441958a2e5269809d09b95a527f4f7186a8 Mon Sep 17 00:00:00 2001
From: gaobinlong <gbinlong@amazon.com>
Date: Tue, 10 Sep 2024 00:54:03 +0800
Subject: [PATCH] Add suggest anomaly detector action to discover page (#849)

* Add generate anomaly detector action to discover page

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Add more test code and rename the file

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Modify flyout header

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Make the detectorName follow the convention

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Truncate the index pattern name if it's too long

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Move entry point to query editor

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Call the node API in dashboard-assistant plugin to generate parameters

Refactor unit test code

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Fix test failure

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Revert the code format

Signed-off-by: gaobinlong <gbinlong@amazon.com>

* Remove some empty lines

Signed-off-by: gaobinlong <gbinlong@amazon.com>

---------

Signed-off-by: gaobinlong <gbinlong@amazon.com>
(cherry picked from commit ec02b63e6edc698f48cc09b2f45b354858c6a91a)
---
 opensearch_dashboards.json                    |   3 +-
 .../SuggestAnomalyDetector.test.tsx           | 449 +++++++++
 .../DiscoverAction/SuggestAnomalyDetector.tsx | 863 ++++++++++++++++++
 .../FeatureAccordion/FeatureAccordion.tsx     |   1 +
 public/plugin.ts                              |  33 +-
 public/redux/reducers/__tests__/ad.test.ts    |  20 +-
 .../reducers/__tests__/opensearch.test.ts     |   4 +-
 public/redux/reducers/ad.ts                   |  39 +-
 public/redux/reducers/opensearch.ts           |   2 +-
 public/services.ts                            |  11 +-
 public/utils/contextMenu/getActions.tsx       |  33 +-
 server/utils/constants.ts                     |   2 +
 12 files changed, 1413 insertions(+), 47 deletions(-)
 create mode 100644 public/components/DiscoverAction/SuggestAnomalyDetector.test.tsx
 create mode 100644 public/components/DiscoverAction/SuggestAnomalyDetector.tsx

diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json
index 2c2df2ba..9a717b83 100644
--- a/opensearch_dashboards.json
+++ b/opensearch_dashboards.json
@@ -7,7 +7,8 @@
   ],
   "optionalPlugins": [
     "dataSource",
-    "dataSourceManagement"
+    "dataSourceManagement",
+    "assistantDashboards"
   ],
   "requiredPlugins": [
     "opensearchDashboardsUtils",
diff --git a/public/components/DiscoverAction/SuggestAnomalyDetector.test.tsx b/public/components/DiscoverAction/SuggestAnomalyDetector.test.tsx
new file mode 100644
index 00000000..4e0de621
--- /dev/null
+++ b/public/components/DiscoverAction/SuggestAnomalyDetector.test.tsx
@@ -0,0 +1,449 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import React from 'react';
+import { render, waitFor } from '@testing-library/react';
+
+import { CoreServicesContext } from '../CoreServices/CoreServices';
+import { coreServicesMock, httpClientMock } from '../../../test/mocks';
+import {
+    HashRouter as Router,
+    RouteComponentProps,
+    Route,
+    Switch,
+} from 'react-router-dom';
+import { Provider } from 'react-redux';
+
+import configureStore from '../../redux/configureStore';
+import SuggestAnomalyDetector from './SuggestAnomalyDetector';
+import userEvent from '@testing-library/user-event';
+import { HttpFetchOptionsWithPath } from '../../../../../src/core/public';
+import { getAssistantClient, getQueryService } from '../../services';
+
+const notifications = {
+    toasts: {
+        addDanger: jest.fn().mockName('addDanger'),
+        addSuccess: jest.fn().mockName('addSuccess'),
+    }
+};
+
+const getNotifications = () => {
+    return notifications;
+}
+
+jest.mock('../../services', () => ({
+    ...jest.requireActual('../../services'),
+    getNotifications: getNotifications,
+    getQueryService: jest.fn().mockReturnValue({
+        queryString: {
+            getQuery: jest.fn(),
+        },
+    }),
+    getAssistantClient: jest.fn().mockReturnValue({
+        executeAgentByName: jest.fn(),
+    })
+}));
+
+const renderWithRouter = () => ({
+    ...render(
+        <Provider store={configureStore(httpClientMock)}>
+            <Router>
+                <Switch>
+                    <Route
+                        render={(props: RouteComponentProps) => (
+                            <CoreServicesContext.Provider value={coreServicesMock}>
+                                <SuggestAnomalyDetector
+                                    closeFlyout={jest.fn()}
+                                />
+                            </CoreServicesContext.Provider>
+                        )}
+                    />
+                </Switch>
+            </Router>
+        </Provider>
+    ),
+});
+
+describe('GenerateAnomalyDetector spec', () => {
+    describe('Renders failed', () => {
+        beforeEach(() => {
+            jest.clearAllMocks();
+        });
+
+        it('renders with invalid dataset type', async () => {
+            const queryService = getQueryService();
+            queryService.queryString.getQuery.mockReturnValue({
+                dataset: {
+                    id: undefined,
+                    title: undefined,
+                    type: 'INDEX'
+                },
+            });
+
+            const { queryByText } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).toBeNull();
+
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Unsupported dataset type'
+                );
+            });
+        });
+
+        it('renders empty component', async () => {
+            const queryService = getQueryService();
+            queryService.queryString.getQuery.mockReturnValue({
+                dataset: {
+                    id: undefined,
+                    title: undefined,
+                    type: 'INDEX_PATTERN'
+                },
+            });
+
+            const { queryByText } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).toBeNull();
+
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Cannot extract complete index info from the context'
+                );
+            });
+        });
+    });
+
+    describe('Renders loading component', () => {
+        beforeEach(() => {
+            jest.clearAllMocks();
+            const queryService = getQueryService();
+            queryService.queryString.getQuery.mockReturnValue({
+                dataset: {
+                    id: 'test-pattern',
+                    title: 'test-pattern',
+                    type: 'INDEX_PATTERN',
+                    timeFieldName: '@timestamp',
+                },
+            });
+
+        });
+
+        it('renders with empty generated parameters', async () => {
+            (getAssistantClient().executeAgentByName as jest.Mock).mockResolvedValueOnce({
+                body: {
+                    inference_results: [
+                        {
+                            output: [
+                                { result: '' }
+                            ]
+                        }
+                    ]
+                }
+            });
+
+            const { queryByText } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Generate parameters for creating anomaly detector failed, reason: Error: Cannot get generated parameters!'
+                );
+            });
+        });
+
+        it('renders with empty parameter', async () => {
+            (getAssistantClient().executeAgentByName as jest.Mock).mockResolvedValueOnce({
+                body: {
+                    inference_results: [
+                        {
+                            output: [
+                                { result: "{\"index\":\"opensearch_dashboards_sample_data_logs\",\"categoryField\":\"ip\",\"aggregationField\":\"\",\"aggregationMethod\":\"\",\"dateFields\":\"utc_time,timestamp\"}" }
+                            ]
+                        }
+                    ]
+                }
+            });
+
+            const { queryByText } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Generate parameters for creating anomaly detector failed, reason: Error: Cannot find aggregation field, aggregation method or data fields!'
+                );
+            });
+        });
+
+        it('renders with empty aggregation field or empty aggregation method', async () => {
+            (getAssistantClient().executeAgentByName as jest.Mock).mockResolvedValueOnce({
+                body: {
+                    inference_results: [
+                        {
+                            output: [
+                                { result: "{\"index\":\"opensearch_dashboards_sample_data_logs\",\"categoryField\":\"ip\",\"aggregationField\":\",\",\"aggregationMethod\":\",\",\"dateFields\":\"utc_time,timestamp\"}" }
+                            ]
+                        }
+                    ]
+                }
+            });
+
+            const { queryByText } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Generate parameters for creating anomaly detector failed, reason: Error: The generated aggregation field or aggregation method is empty!'
+                );
+            });
+        });
+
+        it('renders with different number of aggregation methods and fields', async () => {
+            (getAssistantClient().executeAgentByName as jest.Mock).mockResolvedValueOnce({
+                body: {
+                    inference_results: [
+                        {
+                            output: [
+                                { result: "{\"index\":\"opensearch_dashboards_sample_data_logs\",\"categoryField\":\"ip\",\"aggregationField\":\"a,b\",\"aggregationMethod\":\"avg\",\"dateFields\":\"utc_time,timestamp\"}" }
+                            ]
+                        }
+                    ]
+                }
+            });
+
+            const { queryByText } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Generate parameters for creating anomaly detector failed, reason: Error: The number of aggregation fields and the number of aggregation methods are different!'
+                );
+            });
+        });
+
+        it('renders component completely', async () => {
+            (getAssistantClient().executeAgentByName as jest.Mock).mockResolvedValueOnce({
+                body: {
+                    inference_results: [
+                        {
+                            output: [
+                                { result: "{\"index\":\"opensearch_dashboards_sample_data_logs\",\"categoryField\":\"ip\",\"aggregationField\":\"responseLatency,response\",\"aggregationMethod\":\"avg,sum\",\"dateFields\":\"utc_time,timestamp\"}" }
+                            ]
+                        }
+                    ]
+                }
+            });
+
+            const { queryByText } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+
+            await waitFor(() => {
+                expect(queryByText('Create detector')).not.toBeNull();
+                expect(queryByText('Detector details')).not.toBeNull();
+                expect(queryByText('Advanced configuration')).not.toBeNull();
+                expect(queryByText('Model Features')).not.toBeNull();
+            });
+        });
+
+    });
+
+    describe('Test API calls', () => {
+        beforeEach(() => {
+            jest.clearAllMocks();
+            const queryService = getQueryService();
+            queryService.queryString.getQuery.mockReturnValue({
+                dataset: {
+                    id: 'test-pattern',
+                    title: 'test-pattern',
+                    type: 'INDEX_PATTERN',
+                    timeFieldName: '@timestamp',
+                },
+            });
+        });
+
+        it('All API calls execute successfully', async () => {
+            httpClientMock.post = jest.fn((pathOrOptions: string | HttpFetchOptionsWithPath) => {
+                const url = typeof pathOrOptions === 'string' ? pathOrOptions : pathOrOptions.path;
+                switch (url) {
+                    case '/api/anomaly_detectors/detectors':
+                        return Promise.resolve({
+                            ok: true,
+                            response: {
+                                id: 'test'
+                            }
+                        });
+                    default:
+                        return Promise.resolve({
+                            ok: true
+                        });
+                }
+            });
+            (getAssistantClient().executeAgentByName as jest.Mock).mockResolvedValueOnce({
+                body: {
+                    inference_results: [
+                        {
+                            output: [
+                                { result: "{\"index\":\"test-pattern\",\"categoryField\":\"ip\",\"aggregationField\":\"responseLatency,response\",\"aggregationMethod\":\"avg,sum\",\"dateFields\":\"utc_time,timestamp\"}" }
+                            ]
+                        }
+                    ]
+                }
+            });
+
+
+            const { queryByText, getByTestId } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+            await waitFor(() => {
+                expect(queryByText('Generating parameters...')).toBeNull();
+                expect(queryByText('Create detector')).not.toBeNull();
+                expect(queryByText('Detector details')).not.toBeNull();
+                expect(queryByText('Advanced configuration')).not.toBeNull();
+                expect(queryByText('Model Features')).not.toBeNull();
+            });
+
+            userEvent.click(getByTestId("SuggestAnomalyDetectorCreateButton"));
+
+            await waitFor(() => {
+                expect(httpClientMock.post).toHaveBeenCalledTimes(2);
+                expect(getNotifications().toasts.addSuccess).toHaveBeenCalledTimes(1);
+            });
+        });
+
+        it('Generate parameters failed', async () => {
+            (getAssistantClient().executeAgentByName as jest.Mock).mockRejectedValueOnce('Generate parameters failed');
+
+            const { queryByText } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Generate parameters for creating anomaly detector failed, reason: Generate parameters failed'
+                );
+            });
+        });
+
+        it('Create anomaly detector failed', async () => {
+            httpClientMock.post = jest.fn((pathOrOptions: string | HttpFetchOptionsWithPath) => {
+                const url = typeof pathOrOptions === 'string' ? pathOrOptions : pathOrOptions.path;
+                switch (url) {
+                    case '/api/anomaly_detectors/detectors':
+                        return Promise.resolve({
+                            ok: false,
+                            error: 'Create anomaly detector failed'
+                        });
+                    default:
+                        return Promise.resolve({
+                            ok: true
+                        });
+                }
+            });
+            (getAssistantClient().executeAgentByName as jest.Mock).mockResolvedValueOnce({
+                body: {
+                    inference_results: [
+                        {
+                            output: [
+                                { result: "{\"index\":\"test-pattern\",\"categoryField\":\"ip\",\"aggregationField\":\"responseLatency,response\",\"aggregationMethod\":\"avg,sum\",\"dateFields\":\"utc_time,timestamp\"}" }
+                            ]
+                        }
+                    ]
+                }
+            });
+
+            httpClientMock.get = jest.fn().mockResolvedValue({
+                ok: true,
+                response: {
+                    count: 0
+                },
+            });
+
+            const { queryByText, getByTestId } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+
+            await waitFor(() => {
+                expect(queryByText('Generating parameters...')).toBeNull();
+                expect(queryByText('Create detector')).not.toBeNull();
+                expect(queryByText('Detector details')).not.toBeNull();
+                expect(queryByText('Advanced configuration')).not.toBeNull();
+                expect(queryByText('Model Features')).not.toBeNull();
+            });
+
+            userEvent.click(getByTestId("SuggestAnomalyDetectorCreateButton"));
+
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Create anomaly detector failed'
+                );
+            });
+        });
+
+
+        it('Start anomaly detector failed', async () => {
+            httpClientMock.post = jest.fn((pathOrOptions: string | HttpFetchOptionsWithPath) => {
+                const url = typeof pathOrOptions === 'string' ? pathOrOptions : pathOrOptions.path;
+                switch (url) {
+                    case '/api/anomaly_detectors/detectors':
+                        return Promise.resolve({
+                            ok: true,
+                            response: {
+                                id: 'test'
+                            }
+                        });
+                    case '/api/anomaly_detectors/detectors/test/start':
+                        return Promise.resolve({
+                            ok: false,
+                            error: 'Start anomaly detector failed'
+                        });
+                    default:
+                        return Promise.resolve({
+                            ok: true
+                        });
+                }
+            });
+
+            httpClientMock.get = jest.fn().mockResolvedValue({
+                ok: true,
+                response: {
+                    count: 0
+                },
+            });
+
+            (getAssistantClient().executeAgentByName as jest.Mock).mockResolvedValueOnce({
+                body: {
+                    inference_results: [
+                        {
+                            output: [
+                                { result: "{\"index\":\"test-pattern\",\"categoryField\":\"ip\",\"aggregationField\":\"responseLatency,response\",\"aggregationMethod\":\"avg,sum\",\"dateFields\":\"utc_time,timestamp\"}" }
+                            ]
+                        }
+                    ]
+                }
+            });
+
+
+            const { queryByText, getByTestId } = renderWithRouter();
+            expect(queryByText('Suggested anomaly detector')).not.toBeNull();
+
+            await waitFor(() => {
+                expect(queryByText('Generating parameters...')).toBeNull();
+                expect(queryByText('Create detector')).not.toBeNull();
+                expect(queryByText('Detector details')).not.toBeNull();
+                expect(queryByText('Advanced configuration')).not.toBeNull();
+                expect(queryByText('Model Features')).not.toBeNull();
+            });
+
+            userEvent.click(getByTestId("SuggestAnomalyDetectorCreateButton"));
+
+            await waitFor(() => {
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledTimes(1);
+                expect(getNotifications().toasts.addDanger).toHaveBeenCalledWith(
+                    'Start anomaly detector failed'
+                );
+            });
+        });
+    });
+});
diff --git a/public/components/DiscoverAction/SuggestAnomalyDetector.tsx b/public/components/DiscoverAction/SuggestAnomalyDetector.tsx
new file mode 100644
index 00000000..96e60234
--- /dev/null
+++ b/public/components/DiscoverAction/SuggestAnomalyDetector.tsx
@@ -0,0 +1,863 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useState, useEffect, Fragment } from 'react';
+import {
+    EuiFlyoutHeader,
+    EuiFlyoutBody,
+    EuiFlyoutFooter,
+    EuiTitle,
+    EuiButton,
+    EuiSpacer,
+    EuiText,
+    EuiFormRow,
+    EuiFieldText,
+    EuiCheckbox,
+    EuiFlexItem,
+    EuiFlexGroup,
+    EuiFieldNumber,
+    EuiCallOut,
+    EuiButtonEmpty,
+    EuiPanel,
+    EuiComboBox,
+} from '@elastic/eui';
+import '../FeatureAnywhereContextMenu/CreateAnomalyDetector/styles.scss';
+import { useDispatch, useSelector } from 'react-redux';
+import { isEmpty, get } from 'lodash';
+import {
+    Field,
+    FieldArray,
+    FieldArrayRenderProps,
+    FieldProps,
+    Formik,
+} from 'formik';
+import {
+    createDetector,
+    getDetectorCount,
+    matchDetector,
+    startDetector,
+} from '../../redux/reducers/ad';
+import {
+    getError,
+    getErrorMessage,
+    isInvalid,
+    validateCategoryField,
+    validateDetectorName,
+    validateNonNegativeInteger,
+    validatePositiveInteger,
+} from '../../utils/utils';
+import {
+    CUSTOM_AD_RESULT_INDEX_PREFIX,
+    MAX_DETECTORS,
+    SUGGEST_ANOMALY_DETECTOR_CONFIG_ID,
+} from '../../../server/utils/constants';
+import {
+    focusOnFirstWrongFeature,
+    getCategoryFields,
+    initialFeatureValue,
+    validateFeatures,
+} from '../../pages/ConfigureModel/utils/helpers';
+import { formikToDetector } from '../../pages/ReviewAndCreate/utils/helpers';
+import { FormattedFormRow } from '../FormattedFormRow/FormattedFormRow';
+import { FeatureAccordion } from '../../pages/ConfigureModel/components/FeatureAccordion';
+import { AD_DOCS_LINK, DEFAULT_SHINGLE_SIZE, MAX_FEATURE_NUM, PLUGIN_NAME } from '../../utils/constants';
+import { getAssistantClient, getNotifications, getQueryService } from '../../services';
+import { prettifyErrorMessage } from '../../../server/utils/helpers';
+import EnhancedAccordion from '../FeatureAnywhereContextMenu/EnhancedAccordion';
+import MinimalAccordion from '../FeatureAnywhereContextMenu/MinimalAccordion';
+import { DataFilterList } from '../../pages/DefineDetector/components/DataFilterList/DataFilterList';
+import { FEATURE_TYPE } from '../../models/interfaces';
+import { FeaturesFormikValues } from '../../pages/ConfigureModel/models/interfaces';
+import { getMappings } from '../../redux/reducers/opensearch';
+import { mountReactNode } from '../../../../../src/core/public/utils';
+import { formikToDetectorName } from '../FeatureAnywhereContextMenu/CreateAnomalyDetector/helpers';
+import { DEFAULT_DATA } from '../../../../../src/plugins/data/common';
+import { AppState } from '../../redux/reducers';
+
+export interface GeneratedParameters {
+    categoryField: string;
+    features: FeaturesFormikValues[];
+    dateFields: string[];
+}
+
+function SuggestAnomalyDetector({
+    closeFlyout,
+}: {
+    closeFlyout: any;
+}) {
+    const dispatch = useDispatch();
+    const notifications = getNotifications();
+    const assistantClient = getAssistantClient();
+
+    const queryString = getQueryService().queryString;
+    const dataset = queryString.getQuery().dataset || queryString.getDefaultQuery().dataset;
+    const datasetType = dataset.type;
+    if (datasetType != DEFAULT_DATA.SET_TYPES.INDEX_PATTERN && datasetType != DEFAULT_DATA.SET_TYPES.INDEX) {
+        notifications.toasts.addDanger(
+            'Unsupported dataset type'
+        );
+        return <></>;
+    }
+
+    const indexPatternId = dataset.id;
+    // indexName could be a index pattern or a concrete index
+    const indexName = dataset.title;
+    const timeFieldName = dataset.timeFieldName;
+    if (!indexPatternId || !indexName || !timeFieldName) {
+        notifications.toasts.addDanger(
+            'Cannot extract complete index info from the context'
+        );
+        return <></>;
+    }
+
+    const dataSourceId = dataset.dataSource?.id;
+    const [isLoading, setIsLoading] = useState<boolean>(true);
+    const [buttonName, setButtonName] = useState<string>(
+        'Generating parameters...'
+    );
+    const [categoryFieldEnabled, setCategoryFieldEnabled] =
+        useState<boolean>(false);
+
+    const [accordionsOpen, setAccordionsOpen] = useState<Record<string, boolean>>({ modelFeatures: true });
+    const [intervalValue, setIntervalalue] = useState(10);
+    const [delayValue, setDelayValue] = useState(1);
+    const [enabled, setEnabled] = useState<boolean>(false);
+    const [detectorName, setDetectorName] = useState(
+        formikToDetectorName(indexName.substring(0, 40))
+    );
+    const indexDataTypes = useSelector(
+        (state: AppState) => state.opensearch.dataTypes
+    );
+    const categoricalFields = getCategoryFields(indexDataTypes);
+
+    const dateFields = get(indexDataTypes, 'date', []) as string[];
+    const dateNanoFields = get(indexDataTypes, 'date_nanos', []) as string[];
+    const allDateFields = dateFields.concat(dateNanoFields);
+
+    // let LLM to generate parameters for creating anomaly detector
+    async function getParameters() {
+        try {
+            const executeAgentResponse = await
+                assistantClient.executeAgentByName(SUGGEST_ANOMALY_DETECTOR_CONFIG_ID, { index: indexName }, { dataSourceId }
+                );
+            const rawGeneratedParameters = executeAgentResponse?.body?.inference_results?.[0]?.output?.[0]?.result;
+            if (!rawGeneratedParameters) {
+                throw new Error('Cannot get generated parameters!');
+            }
+
+            const generatedParameters = formatGeneratedParameters(JSON.parse(rawGeneratedParameters));
+            if (generatedParameters.features.length == 0) {
+                throw new Error('Generated parameters have empty model features!');
+            }
+
+            initialDetectorValue.featureList = generatedParameters.features;
+            initialDetectorValue.categoryFieldEnabled = !!generatedParameters.categoryField;
+            initialDetectorValue.categoryField = initialDetectorValue.categoryFieldEnabled ? [generatedParameters.categoryField] : [];
+
+            setIsLoading(false);
+            setButtonName('Create detector');
+            setCategoryFieldEnabled(!!generatedParameters.categoryField);
+        } catch (error) {
+            notifications.toasts.addDanger(
+                'Generate parameters for creating anomaly detector failed, reason: ' + error
+            );
+        }
+    }
+
+    const formatGeneratedParameters = function (rawGeneratedParameters: any): GeneratedParameters {
+        const categoryField = rawGeneratedParameters['categoryField'];
+
+        const rawAggregationFields = rawGeneratedParameters['aggregationField'];
+        const rawAggregationMethods = rawGeneratedParameters['aggregationMethod'];
+        const rawDataFields = rawGeneratedParameters['dateFields'];
+        if (!rawAggregationFields || !rawAggregationMethods || !rawDataFields) {
+            throw new Error('Cannot find aggregation field, aggregation method or data fields!');
+        }
+        const aggregationFields =
+            rawAggregationFields.split(',');
+        const aggregationMethods =
+            rawAggregationMethods.split(',');
+        const dateFields = rawDataFields.split(',');
+
+        if (aggregationFields.length != aggregationMethods.length) {
+            throw new Error('The number of aggregation fields and the number of aggregation methods are different!');
+        }
+
+        const featureList = aggregationFields.map((field: string, index: number) => {
+            const method = aggregationMethods[index];
+            if (!field || !method) {
+                throw new Error('The generated aggregation field or aggregation method is empty!');
+            }
+            const aggregationOption = {
+                label: field,
+            };
+            const feature: FeaturesFormikValues = {
+                featureName: `feature_${field}`,
+                featureType: FEATURE_TYPE.SIMPLE,
+                featureEnabled: true,
+                aggregationQuery: '',
+                aggregationBy: aggregationMethods[index],
+                aggregationOf: [aggregationOption],
+            };
+            return feature;
+        });
+
+        return {
+            categoryField: categoryField,
+            features: featureList,
+            dateFields: dateFields,
+        };
+    };
+
+    useEffect(() => {
+        async function fetchData() {
+            await dispatch(getMappings(indexName, dataSourceId));
+            await getParameters();
+        }
+        fetchData();
+    }, []);
+
+    const onDetectorNameChange = (e: any, field: any) => {
+        field.onChange(e);
+        setDetectorName(e.target.value);
+    };
+
+    const onAccordionToggle = (key: string) => {
+        const newAccordionsOpen = { ...accordionsOpen };
+        newAccordionsOpen[key] = !accordionsOpen[key];
+        setAccordionsOpen(newAccordionsOpen);
+    };
+
+    const onIntervalChange = (e: any, field: any) => {
+        field.onChange(e);
+        setIntervalalue(e.target.value);
+    };
+
+    const onDelayChange = (e: any, field: any) => {
+        field.onChange(e);
+        setDelayValue(e.target.value);
+    };
+
+    const handleValidationAndSubmit = (formikProps: any) => {
+        if (formikProps.values.featureList.length !== 0) {
+            formikProps.setFieldTouched('featureList', true);
+            formikProps.validateForm().then(async (errors: any) => {
+                if (!isEmpty(errors)) {
+                    focusOnFirstWrongFeature(errors, formikProps.setFieldTouched);
+                    notifications.toasts.addDanger(
+                        'One or more input fields is invalid.'
+                    );
+                } else {
+                    handleSubmit(formikProps);
+                }
+            });
+        } else {
+            notifications.toasts.addDanger('One or more features are required.');
+        }
+    };
+
+    const handleSubmit = async (formikProps: any) => {
+        formikProps.setSubmitting(true);
+        try {
+            const detectorToCreate = formikToDetector(formikProps.values);
+            await dispatch(createDetector(detectorToCreate, dataSourceId))
+                .then(async (response: any) => {
+                    const detectorId = response.response.id;
+                    dispatch(startDetector(detectorId, dataSourceId))
+                        .then(() => { })
+                        .catch((err: any) => {
+                            notifications.toasts.addDanger(
+                                prettifyErrorMessage(
+                                    getErrorMessage(
+                                        err,
+                                        'There was a problem starting the real-time detector'
+                                    )
+                                )
+                            );
+                        });
+
+                    const shingleSize = get(
+                        formikProps.values,
+                        'shingleSize',
+                        DEFAULT_SHINGLE_SIZE
+                    );
+                    notifications.toasts.addSuccess({
+                        title: mountReactNode(
+                            <EuiText>
+                                Detector created: <a href="#" onClick={(e) => {
+                                    e.preventDefault();
+                                    const url = `../${PLUGIN_NAME}#/detectors/${detectorId}`;
+                                    window.open(url, '_blank');
+                                }} style={{ textDecoration: 'underline' }}>{formikProps.values.name}</a>
+                            </EuiText >
+                        ),
+                        text: mountReactNode(
+                            <EuiText size="s">
+                                <p>
+                                    Attempting to initialize the detector with historical data. This
+                                    initializing process takes approximately 1 minute if you have data in
+                                    each of the last {32 + shingleSize} consecutive intervals.
+                                </p>
+                            </EuiText>
+                        ),
+                        className: 'createdAndAssociatedSuccessToast',
+                    });
+
+                })
+                .catch((err: any) => {
+                    dispatch(getDetectorCount(dataSourceId)).then((response: any) => {
+                        const totalDetectors = get(response, 'response.count', 0);
+                        if (totalDetectors === MAX_DETECTORS) {
+                            notifications.toasts.addDanger(
+                                'Cannot create detector - limit of ' +
+                                MAX_DETECTORS +
+                                ' detectors reached'
+                            );
+                        } else {
+                            notifications.toasts.addDanger(
+                                prettifyErrorMessage(
+                                    getErrorMessage(
+                                        err,
+                                        'There was a problem creating the detector'
+                                    )
+                                )
+                            );
+                        }
+                    });
+                });
+            closeFlyout();
+        } catch (e) {
+        } finally {
+            formikProps.setSubmitting(false);
+        }
+    };
+
+    const validateAnomalyDetectorName = async (detectorName: string) => {
+        if (isEmpty(detectorName)) {
+            return 'Detector name cannot be empty';
+        } else {
+            const error = validateDetectorName(detectorName);
+            if (error) {
+                return error;
+            }
+            const resp = await dispatch(matchDetector(detectorName, dataSourceId));
+            const match = get(resp, 'response.match', false);
+            if (!match) {
+                return undefined;
+            }
+            //If more than one detectors found, duplicate exists.
+            if (match) {
+                return 'Duplicate detector name';
+            }
+        }
+    };
+
+    let initialDetectorValue = {
+        name: detectorName,
+        index: [{ label: indexName }],
+        timeField: timeFieldName,
+        interval: intervalValue,
+        windowDelay: delayValue,
+        shingleSize: DEFAULT_SHINGLE_SIZE,
+        filterQuery: { match_all: {} },
+        description: 'Created based on the OpenSearch Assistant',
+        resultIndex: undefined,
+        filters: [],
+        featureList: [] as FeaturesFormikValues[],
+        categoryFieldEnabled: false,
+        categoryField: [] as string[],
+        realTime: true,
+        historical: false,
+    };
+
+    return (
+        <div className="add-anomaly-detector">
+            <Formik
+                initialValues={initialDetectorValue}
+                onSubmit={handleSubmit}
+                validateOnChange={true}
+                validate={validateFeatures}
+            >
+                {(formikProps) => (
+                    <>
+                        <EuiFlyoutHeader>
+                            <EuiTitle size='s'>
+                                <h2 id="add-anomaly-detector__title">
+                                    Suggested anomaly detector
+                                </h2>
+                            </EuiTitle>
+                            <EuiSpacer size="m" />
+                            <EuiText size='xs'>
+                                Create an anomaly detector based on the parameters(model features and categorical field) suggested by OpenSearch Assistant.
+                            </EuiText>
+                        </EuiFlyoutHeader>
+                        <EuiFlyoutBody>
+                            <div className="create-new">
+                                <EuiTitle size="s">
+                                    <h3>Detector details</h3>
+                                </EuiTitle>
+                                <EuiSpacer size="m" />
+
+                                <EnhancedAccordion
+                                    id="detectorDetailsAccordion"
+                                    title={detectorName}
+                                    isOpen={accordionsOpen.detectorDetails}
+                                    onToggle={() => onAccordionToggle('detectorDetails')}
+                                    subTitle={
+                                        <EuiText size="m">
+                                            <p>
+                                                Detector interval: {intervalValue} minute(s); Window
+                                                delay: {delayValue} minute(s)
+                                            </p>
+                                        </EuiText>
+                                    }
+                                >
+                                    <Field name="name" validate={validateAnomalyDetectorName}>
+                                        {({ field, form }: FieldProps) => (
+                                            <FormattedFormRow
+                                                title="Name"
+                                                isInvalid={isInvalid(field.name, form)}
+                                                error={getError(field.name, form)}
+                                            >
+                                                <EuiFieldText
+                                                    data-test-subj="detectorNameTextInputFlyout"
+                                                    isInvalid={isInvalid(field.name, form)}
+                                                    {...field}
+                                                    onChange={(e) => onDetectorNameChange(e, field)}
+                                                />
+                                            </FormattedFormRow>
+                                        )}
+                                    </Field>
+
+                                    <EuiSpacer size="s" />
+                                    <Field name="interval" validate={validatePositiveInteger}>
+                                        {({ field, form }: FieldProps) => (
+                                            <EuiFlexGroup>
+                                                <EuiFlexItem style={{ maxWidth: '70%' }}>
+                                                    <FormattedFormRow
+                                                        fullWidth
+                                                        title="Detector interval"
+                                                        isInvalid={isInvalid(field.name, form)}
+                                                        error={getError(field.name, form)}
+                                                    >
+                                                        <EuiFlexGroup gutterSize="s" alignItems="center">
+                                                            <EuiFlexItem grow={false}>
+                                                                <EuiFieldNumber
+                                                                    id="detectionInterval"
+                                                                    placeholder="Detector interval"
+                                                                    data-test-subj="detectionInterval"
+                                                                    min={1}
+                                                                    {...field}
+                                                                    onChange={(e) => onIntervalChange(e, field)}
+                                                                />
+                                                            </EuiFlexItem>
+                                                            <EuiFlexItem>
+                                                                <EuiText>
+                                                                    <p className="minutes">minute(s)</p>
+                                                                </EuiText>
+                                                            </EuiFlexItem>
+                                                        </EuiFlexGroup>
+                                                    </FormattedFormRow>
+                                                </EuiFlexItem>
+                                            </EuiFlexGroup>
+                                        )}
+                                    </Field>
+
+                                    <EuiSpacer size="s" />
+                                    <Field
+                                        name="windowDelay"
+                                        validate={validateNonNegativeInteger}
+                                    >
+                                        {({ field, form }: FieldProps) => (
+                                            <FormattedFormRow
+                                                fullWidth
+                                                title="Window delay"
+                                                isInvalid={isInvalid(field.name, form)}
+                                                error={getError(field.name, form)}
+                                            >
+                                                <EuiFlexGroup gutterSize="s" alignItems="center">
+                                                    <EuiFlexItem grow={false}>
+                                                        <EuiFieldNumber
+                                                            id="windowDelay"
+                                                            placeholder="Window delay"
+                                                            data-test-subj="windowDelay"
+                                                            {...field}
+                                                            onChange={(e) => onDelayChange(e, field)}
+                                                        />
+                                                    </EuiFlexItem>
+                                                    <EuiFlexItem>
+                                                        <EuiText>
+                                                            <p className="minutes">minute(s)</p>
+                                                        </EuiText>
+                                                    </EuiFlexItem>
+                                                </EuiFlexGroup>
+                                            </FormattedFormRow>
+                                        )}
+                                    </Field>
+                                </EnhancedAccordion>
+
+                                <EuiSpacer size="m" />
+
+                                <EnhancedAccordion
+                                    id="advancedConfigurationAccordion"
+                                    title="Advanced configuration"
+                                    isOpen={accordionsOpen.advancedConfiguration}
+                                    onToggle={() => onAccordionToggle('advancedConfiguration')}
+                                    initialIsOpen={false}
+                                >
+                                    <EuiSpacer size="s" />
+                                    <MinimalAccordion
+                                        id="dataFilter"
+                                        title="Data Filter"
+                                        subTitle="Choose a data source subset to focus the data stream and reduce data noise."
+                                    >
+                                        <EuiSpacer size="s" />
+                                        <EuiText size="xs">
+                                            <p>Source: {'test'}</p>
+                                        </EuiText>
+                                        <EuiSpacer size="s" />
+                                        <DataFilterList formikProps={formikProps} />
+                                    </MinimalAccordion>
+
+                                    <MinimalAccordion
+                                        id="shingleSize"
+                                        title="Shingle size"
+                                        subTitle="Set number of intervals in the model's detection window."
+                                        isUsingDivider={true}
+                                    >
+                                        <EuiSpacer size="m" />
+                                        <Field
+                                            name="shingleSize"
+                                            validate={validatePositiveInteger}
+                                        >
+                                            {({ field, form }: FieldProps) => (
+                                                <FormattedFormRow
+                                                    title="Shingle size"
+                                                    hint={[
+                                                        `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.`,
+                                                    ]}
+                                                    hintLink={AD_DOCS_LINK}
+                                                    isInvalid={isInvalid(field.name, form)}
+                                                    error={getError(field.name, form)}
+                                                >
+                                                    <EuiFlexGroup gutterSize="s" alignItems="center">
+                                                        <EuiFlexItem grow={false}>
+                                                            <EuiFieldNumber
+                                                                id="shingleSize"
+                                                                placeholder="Shingle size"
+                                                                data-test-subj="shingleSize"
+                                                                {...field}
+                                                            />
+                                                        </EuiFlexItem>
+                                                        <EuiFlexItem>
+                                                            <EuiText>
+                                                                <p className="minutes">intervals</p>
+                                                            </EuiText>
+                                                        </EuiFlexItem>
+                                                    </EuiFlexGroup>
+                                                </FormattedFormRow>
+                                            )}
+                                        </Field>
+                                    </MinimalAccordion>
+
+                                    <MinimalAccordion
+                                        id="customResultIndex"
+                                        title="Custom result index"
+                                        subTitle="Store detector results to our own index."
+                                        isUsingDivider={true}
+                                    >
+                                        <Field name="resultIndex">
+                                            {({ field, form }: FieldProps) => (
+                                                <EuiFlexGroup direction="column">
+                                                    <EuiFlexItem>
+                                                        <EuiCheckbox
+                                                            id={'resultIndexCheckbox'}
+                                                            label="Enable custom result index"
+                                                            checked={enabled}
+                                                            onChange={() => {
+                                                                if (enabled) {
+                                                                    form.setFieldValue('resultIndex', '');
+                                                                }
+                                                                setEnabled(!enabled);
+                                                            }}
+                                                        />
+                                                    </EuiFlexItem>
+
+                                                    {enabled ? (
+                                                        <EuiFlexItem>
+                                                            <EuiCallOut
+                                                                data-test-subj="cannotEditResultIndexCallout"
+                                                                title="You can't change the custom result index after you create the detector. You can manage the result index with the Index Management plugin."
+                                                                color="warning"
+                                                                iconType="alert"
+                                                                size="s"
+                                                            ></EuiCallOut>
+                                                        </EuiFlexItem>
+                                                    ) : null}
+
+                                                    {enabled ? (
+                                                        <EuiFlexItem>
+                                                            <EuiFormRow
+                                                                label="Field"
+                                                                isInvalid={isInvalid(field.name, form)}
+                                                                helpText={`Custom result index name must contain less than 255 characters including the prefix "opensearch-ad-plugin-result-". Valid characters are a-z, 0-9, -(hyphen) and _(underscore).`}
+                                                            >
+                                                                <EuiFieldText
+                                                                    id="resultIndex"
+                                                                    placeholder="Enter result index name"
+                                                                    prepend={CUSTOM_AD_RESULT_INDEX_PREFIX}
+                                                                    {...field}
+                                                                />
+                                                            </EuiFormRow>
+                                                        </EuiFlexItem>
+                                                    ) : null}
+                                                </EuiFlexGroup>
+                                            )}
+                                        </Field>
+                                    </MinimalAccordion>
+
+                                    <MinimalAccordion
+                                        id="categoricalFields"
+                                        title="Categorical fields"
+                                        subTitle="Split a single time series into multiple time series based on categorical fields."
+                                        isUsingDivider={true}
+                                    >
+                                        <Field
+                                            name="categoryField"
+                                            validate={
+                                                categoryFieldEnabled ? validateCategoryField : null
+                                            }
+                                        >
+                                            {({ field, form }: FieldProps) => (
+                                                <EuiFlexGroup direction="column">
+                                                    <EuiFlexItem>
+                                                        <EuiCheckbox
+                                                            id={'categoryFieldCheckbox'}
+                                                            label="Enable categorical fields"
+                                                            checked={categoryFieldEnabled}
+                                                            onChange={() => {
+                                                                if (categoryFieldEnabled) {
+                                                                    form.setFieldValue('categoryField', []);
+                                                                }
+                                                                setCategoryFieldEnabled(!categoryFieldEnabled);
+                                                            }}
+                                                        />
+                                                    </EuiFlexItem>
+                                                    {categoryFieldEnabled ? (
+                                                        <EuiFlexItem>
+                                                            <EuiCallOut
+                                                                data-test-subj="cannotEditCategoryFieldCallout"
+                                                                title="You can't change the category fields after you create the detector. Make sure that you only select the fields necessary for your use case."
+                                                                color="warning"
+                                                                iconType="alert"
+                                                                size="s"
+                                                            ></EuiCallOut>
+                                                        </EuiFlexItem>
+                                                    ) : null}
+                                                    {categoryFieldEnabled ? (
+                                                        <EuiFlexItem>
+                                                            <EuiFormRow
+                                                                label="Field"
+                                                                isInvalid={isInvalid(field.name, form)}
+                                                                error={getError(field.name, form)}
+                                                                helpText={`You can only apply the categorical fields to the 'ip' and 'keyword' OpenSearch data types, and you can add up to 2 categorical fields.`}
+                                                            >
+                                                                <EuiComboBox
+                                                                    data-test-subj="categoryFieldComboBox"
+                                                                    id="categoryField"
+                                                                    placeholder="Select your categorical fields"
+                                                                    selectedOptions={
+                                                                        field.value.map((value: any) => {
+                                                                            return {
+                                                                                label: value,
+                                                                            };
+                                                                        })
+                                                                    }
+                                                                    options={categoricalFields?.map((field) => {
+                                                                        return {
+                                                                            label: field,
+                                                                        };
+                                                                    })}
+                                                                    onBlur={() => {
+                                                                        form.setFieldTouched('categoryField', true);
+                                                                    }}
+                                                                    onChange={(options) => {
+                                                                        const selection = options.map(
+                                                                            (option) => option.label
+                                                                        );
+                                                                        if (!isEmpty(selection)) {
+                                                                            if (selection.length <= 2) {
+                                                                                form.setFieldValue(
+                                                                                    'categoryField',
+                                                                                    selection
+                                                                                );
+                                                                            }
+                                                                        } else {
+                                                                            form.setFieldValue('categoryField', []);
+                                                                        }
+                                                                    }}
+                                                                    singleSelection={false}
+                                                                    isClearable={true}
+                                                                />
+                                                            </EuiFormRow>
+                                                        </EuiFlexItem>
+                                                    ) : null}
+                                                </EuiFlexGroup>
+                                            )}
+                                        </Field>
+                                    </MinimalAccordion>
+
+                                    <MinimalAccordion
+                                        id="categoricalFields"
+                                        title="Timestamp field"
+                                        subTitle="Choose the time field you want to use for time filter."
+                                        isUsingDivider={true}
+                                    >
+                                        <Field name="timeField">
+                                            {({ field, form }: FieldProps) => (
+                                                <FormattedFormRow
+                                                    title="Timestamp field"
+                                                    isInvalid={isInvalid(field.name, form)}
+                                                    error={getError(field.name, form)}
+                                                >
+                                                    <EuiComboBox
+                                                        data-test-subj="timestampFilter"
+                                                        id="timeField"
+                                                        placeholder="Find timestamp"
+                                                        options={allDateFields.map((field) => {
+                                                            return {
+                                                                label: field,
+                                                            };
+                                                        })}
+                                                        onBlur={() => {
+                                                            form.setFieldTouched('timeField', true);
+                                                        }}
+                                                        onChange={(options) => {
+                                                            form.setFieldValue(
+                                                                'timeField',
+                                                                get(options, '0.label')
+                                                            );
+                                                        }}
+                                                        selectedOptions={
+                                                            field.value
+                                                                ? [
+                                                                    {
+                                                                        label: field.value,
+                                                                    },
+                                                                ]
+                                                                : [{ label: timeFieldName }]
+                                                        }
+                                                        singleSelection={{ asPlainText: true }}
+                                                        isClearable={false}
+                                                    />
+                                                </FormattedFormRow>
+                                            )}
+                                        </Field>
+                                    </MinimalAccordion>
+                                </EnhancedAccordion>
+
+                                <EuiSpacer size="l" />
+                                <EuiTitle size="s">
+                                    <h3>Model Features</h3>
+                                </EuiTitle>
+                                <EuiSpacer size="m" />
+
+                                <EnhancedAccordion
+                                    id="modelFeaturesAccordion"
+                                    title="Features"
+                                    isOpen={accordionsOpen.modelFeatures}
+                                    onToggle={() => onAccordionToggle('modelFeatures')}
+                                >
+                                    <FieldArray name="featureList">
+                                        {({
+                                            push,
+                                            remove,
+                                            form: { values },
+                                        }: FieldArrayRenderProps) => {
+                                            return (
+                                                <Fragment>
+                                                    {values.featureList.map(
+                                                        (feature: any, index: number) => (
+                                                            <FeatureAccordion
+                                                                onDelete={() => {
+                                                                    remove(index);
+                                                                }}
+                                                                index={index}
+                                                                feature={feature}
+                                                                handleChange={formikProps.handleChange}
+                                                                displayMode="flyout"
+                                                                key={index}
+                                                            />
+                                                        )
+                                                    )}
+
+                                                    <EuiSpacer size="m" />
+                                                    <EuiPanel paddingSize="none">
+                                                        <EuiButton
+                                                            className="featureButton"
+                                                            data-test-subj="addFeature"
+                                                            isDisabled={
+                                                                values.featureList.length >= MAX_FEATURE_NUM
+                                                            }
+                                                            onClick={() => {
+                                                                push(initialFeatureValue());
+                                                            }}
+                                                            disabled={isLoading}
+                                                        >
+                                                            Add another feature
+                                                        </EuiButton>
+                                                    </EuiPanel>
+                                                    <EuiSpacer size="s" />
+                                                    <EuiText className="content-panel-subTitle">
+                                                        <p>
+                                                            You can add up to{' '}
+                                                            {Math.max(
+                                                                MAX_FEATURE_NUM - values.featureList.length,
+                                                                0
+                                                            )}{' '}
+                                                            more features.
+                                                        </p>
+                                                    </EuiText>
+                                                </Fragment>
+                                            );
+                                        }}
+                                    </FieldArray>
+                                </EnhancedAccordion>
+                                <EuiSpacer size="m" />
+                            </div>
+                        </EuiFlyoutBody>
+                        <EuiFlyoutFooter>
+                            <EuiFlexGroup justifyContent="spaceBetween">
+                                <EuiFlexItem grow={false}>
+                                    <EuiButtonEmpty onClick={closeFlyout}>Cancel</EuiButtonEmpty>
+                                </EuiFlexItem>
+                                <EuiFlexItem grow={false}>
+                                    <EuiButton
+                                        fill={true}
+                                        data-test-subj="SuggestAnomalyDetectorCreateButton"
+                                        isLoading={isLoading}
+                                        onClick={() => {
+                                            handleValidationAndSubmit(formikProps);
+                                        }}
+                                    >
+                                        {buttonName}
+                                    </EuiButton>
+                                </EuiFlexItem>
+                            </EuiFlexGroup>
+                        </EuiFlyoutFooter>
+                    </>
+                )}
+            </Formik>
+        </div>
+    );
+}
+
+export default SuggestAnomalyDetector;
diff --git a/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx b/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx
index 545dda63..53fd616c 100644
--- a/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx
+++ b/public/pages/ConfigureModel/components/FeatureAccordion/FeatureAccordion.tsx
@@ -113,6 +113,7 @@ export const FeatureAccordion = (props: FeatureAccordionProps) => {
     if (props.displayMode === 'flyout') {
       return (
         <EuiButtonIcon
+          aria-label='trash'
           size="s"
           onClick={onClick}
           disabled={false}
diff --git a/public/plugin.ts b/public/plugin.ts
index 95274204..d00c5074 100644
--- a/public/plugin.ts
+++ b/public/plugin.ts
@@ -27,7 +27,7 @@ import {
 } from '../../../src/plugins/embeddable/public';
 import { ACTION_AD } from './action/ad_dashboard_action';
 import { APP_PATH, DASHBOARD_PAGE_NAV_ID, DETECTORS_PAGE_NAV_ID, OVERVIEW_PAGE_NAV_ID, PLUGIN_NAME } from './utils/constants';
-import { getActions } from './utils/contextMenu/getActions';
+import { ACTION_SUGGEST_AD, getActions, getSuggestAnomalyDetectorAction } from './utils/contextMenu/getActions';
 import { overlayAnomaliesFunction } from './expressions/overlay_anomalies';
 import {
   setClient,
@@ -42,7 +42,8 @@ import {
   setDataSourceManagementPlugin,
   setDataSourceEnabled,
   setNavigationUI,
-  setApplication
+  setApplication,
+  setAssistantClient,
 } from './services';
 import { AnomalyDetectionOpenSearchDashboardsPluginStart } from 'public';
 import {
@@ -50,14 +51,16 @@ import {
   VisAugmenterStart,
 } from '../../../src/plugins/vis_augmenter/public';
 import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
-import { DataPublicPluginStart } from '../../../src/plugins/data/public';
+import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../src/plugins/data/public';
 import { DataSourceManagementPluginSetup } from '../../../src/plugins/data_source_management/public';
 import { DataSourcePluginSetup } from '../../../src/plugins/data_source/public';
 import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public';
+import { AssistantPublicPluginStart } from '../../dashboards-assistant/public';
 
 declare module '../../../src/plugins/ui_actions/public' {
   export interface ActionContextMapping {
     [ACTION_AD]: {};
+    [ACTION_SUGGEST_AD]: {}
   }
 }
 
@@ -68,6 +71,7 @@ export interface AnomalyDetectionSetupDeps {
   visAugmenter: VisAugmenterSetup;
   dataSourceManagement: DataSourceManagementPluginSetup;
   dataSource: DataSourcePluginSetup;
+  data: DataPublicPluginSetup;
 }
 
 export interface AnomalyDetectionStartDeps {
@@ -77,6 +81,7 @@ export interface AnomalyDetectionStartDeps {
   uiActions: UiActionsStart;
   data: DataPublicPluginStart;
   navigation: NavigationPublicPluginStart;
+  assistantDashboards: AssistantPublicPluginStart;
 }
 
 export class AnomalyDetectionOpenSearchDashboardsPlugin
@@ -102,7 +107,7 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin
     });
 
     // register applications with category and use case information
-    core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.observability,[
+    core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.observability, [
       {
         id: PLUGIN_NAME,
         category: DEFAULT_APP_CATEGORIES.detect,
@@ -121,7 +126,7 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin
           const [coreStart] = await core.getStartServices();
           return renderApp(coreStart, params, APP_PATH.OVERVIEW, hideInAppSideNavBar);
         },
-      }); 
+      });
     }
 
     if (core.chrome.navGroup.getNavGroupEnabled()) {
@@ -135,7 +140,7 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin
           const [coreStart] = await core.getStartServices();
           return renderApp(coreStart, params, APP_PATH.DASHBOARD, hideInAppSideNavBar);
         },
-      }); 
+      });
     }
 
     if (core.chrome.navGroup.getNavGroupEnabled()) {
@@ -149,15 +154,15 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin
           const [coreStart] = await core.getStartServices();
           return renderApp(coreStart, params, APP_PATH.LIST_DETECTORS, hideInAppSideNavBar);
         },
-      }); 
+      });
     }
 
     // link the sub applications to the parent application
     core.chrome.navGroup.addNavLinksToGroup(
       DEFAULT_NAV_GROUPS.observability,
       [{
-          id: OVERVIEW_PAGE_NAV_ID,
-          parentNavLinkId: PLUGIN_NAME
+        id: OVERVIEW_PAGE_NAV_ID,
+        parentNavLinkId: PLUGIN_NAME
       },
       {
         id: DASHBOARD_PAGE_NAV_ID,
@@ -189,6 +194,11 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin
       plugins.uiActions.addTriggerAction(CONTEXT_MENU_TRIGGER, action);
     });
 
+    // Add suggest anomaly detector action to the uiActions in Discover
+    if (plugins.assistantDashboards?.assistantTriggers?.AI_ASSISTANT_TRIGGER) {
+      const suggestAnomalyDetectorAction = getSuggestAnomalyDetectorAction();
+      plugins.uiActions.addTriggerAction(plugins.assistantDashboards.assistantTriggers.AI_ASSISTANT_TRIGGER, suggestAnomalyDetectorAction);
+    }
     // registers the expression function used to render anomalies on an Augmented Visualization
     plugins.expressions.registerFunction(overlayAnomaliesFunction);
     return {};
@@ -196,7 +206,7 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin
 
   public start(
     core: CoreStart,
-    { embeddable, visAugmenter, uiActions, data, navigation }: AnomalyDetectionStartDeps
+    { embeddable, visAugmenter, uiActions, data, navigation, assistantDashboards }: AnomalyDetectionStartDeps
   ): AnomalyDetectionOpenSearchDashboardsPluginStart {
     setUISettings(core.uiSettings);
     setEmbeddable(embeddable);
@@ -207,6 +217,9 @@ export class AnomalyDetectionOpenSearchDashboardsPlugin
     setQueryService(data.query);
     setSavedObjectsClient(core.savedObjects.client);
     setNavigationUI(navigation.ui);
+    if (assistantDashboards) {
+      setAssistantClient(assistantDashboards.assistantClient);
+    }
     setApplication(core.application);
     return {};
   }
diff --git a/public/redux/reducers/__tests__/ad.test.ts b/public/redux/reducers/__tests__/ad.test.ts
index 79bd8f48..b99b82ba 100644
--- a/public/redux/reducers/__tests__/ad.test.ts
+++ b/public/redux/reducers/__tests__/ad.test.ts
@@ -54,7 +54,7 @@ describe('detector reducer actions', () => {
         },
       });
       expect(httpMockedClient.get).toHaveBeenCalledWith(
-        `..${BASE_NODE_API_PATH}/detectors/${detectorId}`
+        `${BASE_NODE_API_PATH}/detectors/${detectorId}`
       );
     });
     test('should invoke [REQUEST, FAILURE]', async () => {
@@ -76,7 +76,7 @@ describe('detector reducer actions', () => {
           errorMessage: 'Not found',
         });
         expect(httpMockedClient.get).toHaveBeenCalledWith(
-          `..${BASE_NODE_API_PATH}/detectors/${detectorId}`
+          `${BASE_NODE_API_PATH}/detectors/${detectorId}`
         );
       }
     });
@@ -104,7 +104,7 @@ describe('detector reducer actions', () => {
         },
       });
       expect(httpMockedClient.delete).toHaveBeenCalledWith(
-        `..${BASE_NODE_API_PATH}/detectors/${expectedDetector.id}`
+        `${BASE_NODE_API_PATH}/detectors/${expectedDetector.id}`
       );
     });
     test('should invoke [REQUEST, FAILURE]', async () => {
@@ -129,7 +129,7 @@ describe('detector reducer actions', () => {
           errorMessage: 'Detector is consumed by Monitor',
         });
         expect(httpMockedClient.delete).toHaveBeenCalledWith(
-          `..${BASE_NODE_API_PATH}/detectors/${expectedDetector.id}`
+          `${BASE_NODE_API_PATH}/detectors/${expectedDetector.id}`
         );
       }
     });
@@ -162,7 +162,7 @@ describe('detector reducer actions', () => {
         },
       });
       expect(httpMockedClient.post).toHaveBeenCalledWith(
-        `..${BASE_NODE_API_PATH}/detectors`,
+        `${BASE_NODE_API_PATH}/detectors`,
         {
           body: JSON.stringify(expectedDetector),
         }
@@ -190,7 +190,7 @@ describe('detector reducer actions', () => {
           errorMessage: 'Internal server error',
         });
         expect(httpMockedClient.post).toHaveBeenCalledWith(
-          `..${BASE_NODE_API_PATH}/detectors`,
+          `${BASE_NODE_API_PATH}/detectors`,
           {
             body: JSON.stringify(expectedDetector),
           }
@@ -230,7 +230,7 @@ describe('detector reducer actions', () => {
         },
       });
       expect(httpMockedClient.put).toHaveBeenCalledWith(
-        `..${BASE_NODE_API_PATH}/detectors/${detectorId}`,
+        `${BASE_NODE_API_PATH}/detectors/${detectorId}`,
         { body: JSON.stringify(randomDetector) }
       );
     });
@@ -258,7 +258,7 @@ describe('detector reducer actions', () => {
           errorMessage: 'Internal server error',
         });
         expect(httpMockedClient.post).toHaveBeenCalledWith(
-          `..${BASE_NODE_API_PATH}/detectors`,
+          `${BASE_NODE_API_PATH}/detectors`,
           randomDetector,
           {
             params: {
@@ -298,7 +298,7 @@ describe('detector reducer actions', () => {
         ),
       });
       expect(httpMockedClient.post).toHaveBeenCalledWith(
-        `..${BASE_NODE_API_PATH}/detectors/_search`,
+        `${BASE_NODE_API_PATH}/detectors/_search`,
         {
           body: JSON.stringify(query),
         }
@@ -328,7 +328,7 @@ describe('detector reducer actions', () => {
           errorMessage: 'Internal server error',
         });
         expect(httpMockedClient.post).toHaveBeenCalledWith(
-          `..${BASE_NODE_API_PATH}/detectors`,
+          `${BASE_NODE_API_PATH}/detectors`,
           randomDetector
         );
       }
diff --git a/public/redux/reducers/__tests__/opensearch.test.ts b/public/redux/reducers/__tests__/opensearch.test.ts
index 18d75ae2..ee3e5fc0 100644
--- a/public/redux/reducers/__tests__/opensearch.test.ts
+++ b/public/redux/reducers/__tests__/opensearch.test.ts
@@ -175,7 +175,7 @@ describe('opensearch reducer actions', () => {
         },
       });
       expect(httpMockedClient.get).toHaveBeenCalledWith(
-        `..${BASE_NODE_API_PATH}/_mappings`,
+        `${BASE_NODE_API_PATH}/_mappings`,
         {
           query: { indices: [] },
         }
@@ -202,7 +202,7 @@ describe('opensearch reducer actions', () => {
           errorMessage: 'Something went wrong',
         });
         expect(httpMockedClient.get).toHaveBeenCalledWith(
-          `..${BASE_NODE_API_PATH}/_mappings`,
+          `${BASE_NODE_API_PATH}/_mappings`,
           {
             query: { indices: [] },
           }
diff --git a/public/redux/reducers/ad.ts b/public/redux/reducers/ad.ts
index 3fa06ad3..a1a689d1 100644
--- a/public/redux/reducers/ad.ts
+++ b/public/redux/reducers/ad.ts
@@ -374,9 +374,8 @@ export const createDetector = (
   dataSourceId: string = ''
 ): APIAction => {
   const url = dataSourceId
-    ? `..${AD_NODE_API.DETECTOR}/${dataSourceId}`
-    : `..${AD_NODE_API.DETECTOR}`;
-
+    ? `${AD_NODE_API.DETECTOR}/${dataSourceId}`
+    : `${AD_NODE_API.DETECTOR}`;
   return {
     type: CREATE_DETECTOR,
     request: (client: HttpSetup) =>
@@ -391,7 +390,7 @@ export const validateDetector = (
   validationType: string,
   dataSourceId: string = ''
 ): APIAction => {
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/_validate/${validationType}`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/_validate/${validationType}`;
   const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
 
   return {
@@ -407,7 +406,7 @@ export const getDetector = (
   detectorId: string,
   dataSourceId: string = ''
 ): APIAction => {
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/${detectorId}`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/${detectorId}`;
   const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
 
   return {
@@ -422,7 +421,7 @@ export const getDetectorList = (
 ): APIAction => {
   const dataSourceId = queryParams.dataSourceId || '';
 
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/_list`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/_list`;
   const url = dataSourceId
     ? `${baseUrl}/${dataSourceId}`
     : baseUrl;
@@ -436,7 +435,7 @@ export const getDetectorList = (
 export const searchDetector = (requestBody: any): APIAction => ({
   type: SEARCH_DETECTOR,
   request: (client: HttpSetup) =>
-    client.post(`..${AD_NODE_API.DETECTOR}/_search`, {
+    client.post(`${AD_NODE_API.DETECTOR}/_search`, {
       body: JSON.stringify(requestBody),
     }),
 });
@@ -446,7 +445,7 @@ export const updateDetector = (
   requestBody: Detector,
   dataSourceId: string = ''
 ): APIAction => {
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/${detectorId}`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/${detectorId}`;
   const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
 
   return {
@@ -463,7 +462,7 @@ export const deleteDetector = (
   detectorId: string,
   dataSourceId: string = ''
 ): APIAction => {
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/${detectorId}`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/${detectorId}`;
   const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
 
   return {
@@ -477,7 +476,7 @@ export const startDetector = (
   detectorId: string,
   dataSourceId: string = ''
 ): APIAction => {
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/${detectorId}/start`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/${detectorId}/start`;
   const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
 
   return {
@@ -493,7 +492,7 @@ export const startHistoricalDetector = (
   startTime: number,
   endTime: number
 ): APIAction => {
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/${detectorId}`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/${detectorId}`;
   const url = dataSourceId
     ? `${baseUrl}/${dataSourceId}/start`
     : `${baseUrl}/start`;
@@ -517,7 +516,7 @@ export const stopDetector = (
   detectorId: string,
   dataSourceId: string = ''
 ): APIAction => {
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/${detectorId}/stop/${false}`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/${detectorId}/stop/${false}`;
   const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
 
   return {
@@ -531,7 +530,7 @@ export const stopHistoricalDetector = (
   detectorId: string,
   dataSourceId: string = ''
 ): APIAction => {
-  const baseUrl = `..${AD_NODE_API.DETECTOR}/${detectorId}/stop/${true}`;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/${detectorId}/stop/${true}`;
   const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
 
   return {
@@ -544,16 +543,16 @@ export const stopHistoricalDetector = (
 export const getDetectorProfile = (detectorId: string): APIAction => ({
   type: GET_DETECTOR_PROFILE,
   request: (client: HttpSetup) =>
-    client.get(`..${AD_NODE_API.DETECTOR}/${detectorId}/_profile`),
+    client.get(`${AD_NODE_API.DETECTOR}/${detectorId}/_profile`),
   detectorId,
 });
 
 export const matchDetector = (
-  detectorName: string, 
+  detectorName: string,
   dataSourceId: string = ''
 ): APIAction => {
-    const baseUrl = `..${AD_NODE_API.DETECTOR}/${detectorName}/_match`;
-    const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
+  const baseUrl = `${AD_NODE_API.DETECTOR}/${detectorName}/_match`;
+  const url = dataSourceId ? `${baseUrl}/${dataSourceId}` : baseUrl;
 
   return {
     type: MATCH_DETECTOR,
@@ -562,9 +561,9 @@ export const matchDetector = (
 };
 
 export const getDetectorCount = (dataSourceId: string = ''): APIAction => {
-  const url = dataSourceId ? 
-    `..${AD_NODE_API.DETECTOR}/_count/${dataSourceId}` : 
-    `..${AD_NODE_API.DETECTOR}/_count`;
+  const url = dataSourceId ?
+    `${AD_NODE_API.DETECTOR}/_count/${dataSourceId}` :
+    `${AD_NODE_API.DETECTOR}/_count`;
 
   return {
     type: GET_DETECTOR_COUNT,
diff --git a/public/redux/reducers/opensearch.ts b/public/redux/reducers/opensearch.ts
index 77667b74..046b2c19 100644
--- a/public/redux/reducers/opensearch.ts
+++ b/public/redux/reducers/opensearch.ts
@@ -371,7 +371,7 @@ export const getMappings = (
   return {
     type: GET_MAPPINGS,
     request: (client: HttpSetup) =>
-      client.get(`..${url}`, {
+      client.get(`${url}`, {
         query: { indices: searchKey },
       }),
   };
diff --git a/public/services.ts b/public/services.ts
index 0c3d45dd..d582ac59 100644
--- a/public/services.ts
+++ b/public/services.ts
@@ -16,6 +16,7 @@ import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_u
 import { UiActionsStart } from '../../../src/plugins/ui_actions/public';
 import { SavedAugmentVisLoader } from '../../../src/plugins/vis_augmenter/public';
 import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public';
+import { AssistantPublicPluginStart } from '../../../plugins/dashboards-assistant/public/';
 
 export interface DataSourceEnabled {
   enabled: boolean;
@@ -45,6 +46,12 @@ export const [getUISettings, setUISettings] =
 export const [getQueryService, setQueryService] =
   createGetterSetter<DataPublicPluginStart['query']>('Query');
 
+export const [getAssistantEnabled, setAssistantEnabled] =
+  createGetterSetter<AssistantPublicPluginStart>('AssistantClient');
+
+export const [getAssistantClient, setAssistantClient] =
+  createGetterSetter<AssistantPublicPluginStart['assistantClient']>('AssistantClient');
+
 export const [getSavedObjectsClient, setSavedObjectsClient] =
   createGetterSetter<CoreStart['savedObjects']['client']>('SavedObjectsClient');
 
@@ -54,10 +61,10 @@ export const [getDataSourceManagementPlugin, setDataSourceManagementPlugin] =
 export const [getDataSourceEnabled, setDataSourceEnabled] =
   createGetterSetter<DataSourceEnabled>('DataSourceEnabled');
 
-export const [getNavigationUI, setNavigationUI] = 
+export const [getNavigationUI, setNavigationUI] =
   createGetterSetter<NavigationPublicPluginStart['ui']>('navigation');
 
-export const [getApplication, setApplication] = 
+export const [getApplication, setApplication] =
   createGetterSetter<CoreStart['application']>('application');
 
 // This is primarily used for mocking this module and each of its fns in tests.
diff --git a/public/utils/contextMenu/getActions.tsx b/public/utils/contextMenu/getActions.tsx
index f58a7a9e..6bb6bf3e 100644
--- a/public/utils/contextMenu/getActions.tsx
+++ b/public/utils/contextMenu/getActions.tsx
@@ -7,7 +7,7 @@ import React from 'react';
 import { i18n } from '@osd/i18n';
 import { EuiIconType } from '@elastic/eui';
 import { toMountPoint } from '../../../../../src/plugins/opensearch_dashboards_react/public';
-import { Action } from '../../../../../src/plugins/ui_actions/public';
+import { Action, createAction } from '../../../../../src/plugins/ui_actions/public';
 import { createADAction } from '../../action/ad_dashboard_action';
 import AnywhereParentFlyout from '../../components/FeatureAnywhereContextMenu/AnywhereParentFlyout';
 import { Provider } from 'react-redux';
@@ -16,6 +16,9 @@ import DocumentationTitle from '../../components/FeatureAnywhereContextMenu/Docu
 import { AD_FEATURE_ANYWHERE_LINK, ANOMALY_DETECTION_ICON } from '../constants';
 import { getClient, getOverlays } from '../../../public/services';
 import { FLYOUT_MODES } from '../../../public/components/FeatureAnywhereContextMenu/AnywhereParentFlyout/constants';
+import SuggestAnomalyDetector from '../../../public/components/DiscoverAction/SuggestAnomalyDetector';
+
+export const ACTION_SUGGEST_AD = 'suggestAnomalyDetector';
 
 // This is used to create all actions in the same context menu
 const grouping: Action['grouping'] = [
@@ -87,3 +90,31 @@ export const getActions = () => {
     },
   ].map((options) => createADAction({ ...options, grouping }));
 };
+
+export const getSuggestAnomalyDetectorAction = () => {
+  const onClick = async function () {
+    const overlayService = getOverlays();
+    const openFlyout = overlayService.openFlyout;
+    const store = configureStore(getClient());
+    const overlay = openFlyout(
+      toMountPoint(
+        <Provider store={store}>
+          <SuggestAnomalyDetector
+            closeFlyout={() => overlay.close()}
+          />
+        </Provider>
+      )
+    );
+  }
+
+  return createAction({
+    id: 'suggestAnomalyDetector',
+    order: 100,
+    type: ACTION_SUGGEST_AD,
+    getDisplayName: () => 'Suggest anomaly detector',
+    getIconType: () => ANOMALY_DETECTION_ICON,
+    execute: async () => {
+      onClick();
+    },
+  });
+}
diff --git a/server/utils/constants.ts b/server/utils/constants.ts
index ac3c887a..1a756187 100644
--- a/server/utils/constants.ts
+++ b/server/utils/constants.ts
@@ -132,3 +132,5 @@ export const HISTORICAL_TASK_TYPES = [
 ];
 
 export const CUSTOM_AD_RESULT_INDEX_PREFIX = 'opensearch-ad-plugin-result-';
+
+export const SUGGEST_ANOMALY_DETECTOR_CONFIG_ID = 'os_suggest_ad';