diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json
new file mode 100644
index 000000000..5124ea41e
--- /dev/null
+++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json
@@ -0,0 +1,27 @@
+[
+ {
+ "value1": 1,
+ "value2": 10,
+ "value3": 5
+ },
+ {
+ "value1": 5,
+ "value2": 1,
+ "value3": 3
+ },
+ {
+ "value1": 9,
+ "value2": 6,
+ "value3": 2
+ },
+ {
+ "value1": 2,
+ "value2": 1,
+ "value3": 1
+ },
+ {
+ "value1": 12,
+ "value2": 5,
+ "value3": 4
+ }
+]
diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt
new file mode 100644
index 000000000..2e010deba
--- /dev/null
+++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt
@@ -0,0 +1 @@
+[{"count":0,"name":"@timestamp","type":"date","esTypes":["date"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"_id","type":"string","esTypes":["_id"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_index","type":"string","esTypes":["_index"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_score","type":"number","scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_source","type":"_source","esTypes":["_source"],"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_type","type":"string","scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"value1","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"value2","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"value3","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false}]
\ No newline at end of file
diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt
new file mode 100644
index 000000000..d4d78055d
--- /dev/null
+++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt
@@ -0,0 +1 @@
+{"mappings":{"properties":{"value1":{"type":"integer"},"value2":{"type":"integer"},"value3":{"type":"integer"},"@timestamp":{"type":"date", "format":"epoch_millis"}}},"settings":{"index":{"number_of_shards":"1","number_of_replicas":"1"}}}
\ No newline at end of file
diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js
new file mode 100644
index 000000000..03f547f75
--- /dev/null
+++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js
@@ -0,0 +1,130 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ INDEX_PATTERN_FILEPATH_SIMPLE,
+ INDEX_SETTINGS_FILEPATH_SIMPLE,
+ SAMPLE_DATA_FILEPATH_SIMPLE,
+} from '../../../../../utils/constants';
+import {
+ deleteVisAugmenterData,
+ bootstrapDashboard,
+} from '../../../../../utils/dashboards/vis-augmenter/helpers';
+
+describe('Vis augmenter - existing dashboards work as expected', () => {
+ describe('dashboard with ineligible, eligible, and vega visualizations', () => {
+ const indexName = 'vis-augmenter-sample-index';
+ const indexPatternName = 'vis-augmenter-sample-*';
+ const dashboardName = 'Vis Augmenter Dashboard';
+ const visualizationSpecs = [
+ {
+ name: 'count-agg-vis',
+ type: 'line',
+ indexPattern: indexPatternName,
+ metrics: [],
+ },
+ {
+ name: 'single-metric-vis',
+ type: 'line',
+ indexPattern: indexPatternName,
+ metrics: [
+ {
+ aggregation: 'Average',
+ field: 'value1',
+ },
+ ],
+ },
+ {
+ name: 'multi-metric-vis',
+ type: 'line',
+ indexPattern: indexPatternName,
+ metrics: [
+ {
+ aggregation: 'Average',
+ field: 'value1',
+ },
+ {
+ aggregation: 'Average',
+ field: 'value2',
+ },
+ {
+ aggregation: 'Max',
+ field: 'value3',
+ },
+ ],
+ },
+ {
+ name: 'area-vis',
+ type: 'area',
+ indexPattern: indexPatternName,
+ metrics: [
+ {
+ aggregation: 'Max',
+ field: 'value2',
+ },
+ ],
+ },
+ {
+ name: 'vega-vis',
+ type: 'vega',
+ indexPattern: indexPatternName,
+ metrics: [],
+ },
+ ];
+
+ const visualizationNames = visualizationSpecs.map(
+ (visualizationSpec) => visualizationSpec.name
+ );
+
+ before(() => {
+ // Create a dashboard and add some visualizations
+ bootstrapDashboard(
+ INDEX_SETTINGS_FILEPATH_SIMPLE,
+ INDEX_PATTERN_FILEPATH_SIMPLE,
+ SAMPLE_DATA_FILEPATH_SIMPLE,
+ indexName,
+ indexPatternName,
+ dashboardName,
+ visualizationSpecs
+ );
+ });
+
+ beforeEach(() => {
+ cy.visitDashboard(dashboardName);
+ cy.wait(5000);
+ });
+
+ after(() => {
+ deleteVisAugmenterData(
+ indexName,
+ indexPatternName,
+ visualizationNames,
+ dashboardName
+ );
+ });
+
+ it('View events option does not exist for any visualization', () => {
+ visualizationNames.forEach((visualizationName) => {
+ cy.getVisPanelByTitle(visualizationName)
+ .openVisContextMenu()
+ .getMenuItems()
+ .contains('View Events')
+ .should('not.exist');
+ });
+ });
+
+ it('Validate non-vega visualizations are not rendered with vega under the hood', () => {
+ visualizationSpecs.forEach((visualizationSpec) => {
+ cy.getVisPanelByTitle(visualizationSpec.name).within(() => {
+ if (visualizationSpec.type === 'vega') {
+ cy.get('.vgaVis__view').should('exist');
+ } else {
+ cy.get('.vgaVis__view').should('not.exist');
+ }
+ });
+ });
+ });
+ });
+});
diff --git a/cypress/utils/dashboards/commands.js b/cypress/utils/dashboards/commands.js
index e7a4e39b1..54615c999 100644
--- a/cypress/utils/dashboards/commands.js
+++ b/cypress/utils/dashboards/commands.js
@@ -5,6 +5,7 @@
import './vis_builder/commands';
import './vis_type_table/commands';
+import './vis-augmenter/commands';
Cypress.Commands.add('waitForLoader', () => {
const opts = { log: false };
diff --git a/cypress/utils/dashboards/constants.js b/cypress/utils/dashboards/constants.js
index e16741d1e..c2c797c85 100644
--- a/cypress/utils/dashboards/constants.js
+++ b/cypress/utils/dashboards/constants.js
@@ -17,3 +17,4 @@ export const SAVED_OBJECTS_PATH =
export * from './vis_builder/constants';
export * from './vis_type_table/constants';
+export * from './vis-augmenter/constants';
diff --git a/cypress/utils/dashboards/vis-augmenter/commands.js b/cypress/utils/dashboards/vis-augmenter/commands.js
new file mode 100644
index 000000000..18b18767b
--- /dev/null
+++ b/cypress/utils/dashboards/vis-augmenter/commands.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import _ from 'lodash';
+import { BASE_PATH } from '../../constants';
+
+Cypress.Commands.add('getVisPanelByTitle', (title) =>
+ cy.get(`[data-title="${title}"]`).parents('.embPanel').should('be.visible')
+);
+
+Cypress.Commands.add('openVisContextMenu', { prevSubject: true }, (panel) =>
+ cy
+ .wrap(panel)
+ .find(`[data-test-subj="embeddablePanelContextMenuClosed"]`)
+ .click()
+ .then(() => cy.get('.euiContextMenu'))
+);
+
+Cypress.Commands.add(
+ 'clickVisPanelMenuItem',
+ { prevSubject: 'optional' },
+ (menu, text) =>
+ (menu ? cy.wrap(menu) : cy.get('.euiContextMenu'))
+ .find('button')
+ .contains(text)
+ .click()
+ .then(() => cy.get('.euiContextMenu'))
+);
+
+Cypress.Commands.add('getMenuItems', { prevSubject: 'optional' }, (menu) =>
+ (menu ? cy.wrap(menu) : cy.get('.euiContextMenu')).find('button')
+);
+
+Cypress.Commands.add('visitDashboard', (dashboardName) => {
+ cy.visit(`${BASE_PATH}/app/dashboards`);
+ cy.get('.euiFieldSearch').type(dashboardName);
+ cy.wait(1000);
+ cy.get('[data-test-subj="itemsInMemTable"]').contains(dashboardName).click({
+ force: true,
+ });
+});
diff --git a/cypress/utils/dashboards/vis-augmenter/constants.js b/cypress/utils/dashboards/vis-augmenter/constants.js
new file mode 100644
index 000000000..6c7bfa365
--- /dev/null
+++ b/cypress/utils/dashboards/vis-augmenter/constants.js
@@ -0,0 +1,15 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+const SAMPLE_DATA_DIR_SIMPLE =
+ 'dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/';
+
+export const SAMPLE_DATA_FILEPATH_SIMPLE = SAMPLE_DATA_DIR_SIMPLE + 'data.json';
+
+export const INDEX_PATTERN_FILEPATH_SIMPLE =
+ SAMPLE_DATA_DIR_SIMPLE + 'index-pattern-fields.txt';
+
+export const INDEX_SETTINGS_FILEPATH_SIMPLE =
+ SAMPLE_DATA_DIR_SIMPLE + 'index-settings.txt';
diff --git a/cypress/utils/dashboards/vis-augmenter/helpers.js b/cypress/utils/dashboards/vis-augmenter/helpers.js
new file mode 100644
index 000000000..e8a555e92
--- /dev/null
+++ b/cypress/utils/dashboards/vis-augmenter/helpers.js
@@ -0,0 +1,316 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { isEmpty } from 'lodash';
+import { MiscUtils } from '@opensearch-dashboards-test/opensearch-dashboards-test-library';
+
+const apiRequest = (url, method = 'POST', body = undefined, qs = undefined) =>
+ cy.request({
+ method: method,
+ failOnStatusCode: false,
+ url: url,
+ headers: {
+ 'content-type': 'application/json',
+ 'osd-xsrf': true,
+ },
+ body: body,
+ qs: qs,
+ });
+
+const devToolsRequest = (
+ url,
+ method = 'POST',
+ body = undefined,
+ qs = undefined
+) =>
+ cy.request({
+ method: 'POST',
+ form: false,
+ failOnStatusCode: false,
+ url: encodeURI(`api/console/proxy?path=${url}&method=${method}`),
+ headers: {
+ 'content-type': 'application/json;charset=UTF-8',
+ 'osd-xsrf': true,
+ },
+ body: body,
+ qs: qs,
+ });
+
+/**
+ * Cleans up the index & all associated saved objects (index pattern, visualizations,
+ * dashboards, etc.) created during the test run
+ */
+export const deleteVisAugmenterData = (
+ indexName,
+ indexPatternName,
+ visualizationNames,
+ dashboardName
+) => {
+ devToolsRequest(indexName, 'DELETE').then(() => {
+ apiRequest(
+ `api/saved_objects/index-pattern/${indexPatternName}`,
+ 'DELETE'
+ ).then(() => {
+ apiRequest(
+ `
+api/opensearch-dashboards/management/saved_objects/_find?perPage=5000&page=1&fields=id&type=config&type=url&type=index-pattern&type=query&type=dashboard&type=visualization&type=visualization-visbuilder&type=augment-vis&type=search&sortField=type`,
+ 'GET'
+ ).then((response) => {
+ response.body.saved_objects.forEach((obj) => {
+ if (
+ obj.type !== 'config' &&
+ [
+ indexName,
+ indexPatternName,
+ ...visualizationNames,
+ dashboardName,
+ ].indexOf(obj.meta.title) !== -1
+ ) {
+ apiRequest(
+ `api/saved_objects/${obj.type}/${obj.id}?force=true`,
+ 'DELETE'
+ );
+ }
+ });
+ });
+ });
+ });
+};
+
+/**
+ * Fetch the fixtures to create and configure an index, index pattern,
+ * and ingesting sample data to the index
+ */
+export const ingestVisAugmenterData = (
+ indexName,
+ indexPatternName,
+ indexSettingsFilepath,
+ indexPatternFieldsFilepath,
+ sampleDataFilepath
+) => {
+ cy.fixture(indexSettingsFilepath).then((indexSettings) =>
+ devToolsRequest(indexName, 'PUT', indexSettings)
+ );
+
+ cy.fixture(indexPatternFieldsFilepath).then((fields) => {
+ apiRequest(
+ `api/saved_objects/index-pattern/${indexPatternName}`,
+ 'POST',
+ JSON.stringify({
+ attributes: {
+ fields: fields,
+ title: indexPatternName,
+ timeFieldName: '@timestamp',
+ },
+ })
+ );
+ });
+
+ cy.fixture(sampleDataFilepath).then((indexData) => {
+ indexData.forEach((item, idx) => {
+ let date = new Date();
+ item['@timestamp'] = date.setMinutes(date.getMinutes() - 1);
+ devToolsRequest(`${indexName}/_doc/${idx}`, 'POST', JSON.stringify(item));
+ });
+ });
+};
+
+/**
+ * Creating a new visualization from a dashboard, and finishing at the
+ * vis edit page
+ */
+const bootstrapCreateFromDashboard = (visType, indexPatternName) => {
+ cy.getElementByTestId('dashboardAddNewPanelButton')
+ .should('be.visible')
+ .click();
+
+ cy.getElementByTestId(`visType-${visType}`).click();
+
+ // vega charts don't have a secondary modal to configure the
+ // index pattern / saved search. Skip those steps here
+ if (visType !== 'vega') {
+ cy.getElementByTestId('savedObjectFinderSearchInput').type(
+ `${indexPatternName}{enter}`
+ );
+ cy.get(`[title="${indexPatternName}"]`).click();
+ }
+};
+
+const setXAxisDateHistogram = () => {
+ cy.get('.euiTitle')
+ .contains('Buckets')
+ .parent()
+ .find('[data-test-subj="visEditorAdd_buckets"]')
+ .click();
+
+ cy.getElementByTestId('visEditorAdd_buckets_X-axis').click();
+
+ cy.get('.euiTitle')
+ .contains('Buckets')
+ .parent()
+ .within(() => {
+ cy.wait(1000);
+ cy.getElementByTestId('comboBoxInput')
+ .find('input')
+ .type('Date Histogram{enter}', { force: true });
+ });
+};
+
+/**
+ * From the vis edit view, return to the dashboard
+ */
+const saveVisualizationAndReturn = (visualizationName) => {
+ cy.getElementByTestId('visualizeEditorRenderButton').click({
+ force: true,
+ });
+ cy.getElementByTestId('visualizeSaveButton').click({
+ force: true,
+ });
+ cy.getElementByTestId('savedObjectTitle').type(visualizationName);
+ cy.getElementByTestId('confirmSaveSavedObjectButton').click({
+ force: true,
+ });
+};
+
+/**
+ * Creates a list of specified metrics under the y-axis section
+ * in the vis editor page
+ */
+const setYAxis = (metrics) => {
+ metrics.forEach((metric, index) => {
+ // There is always a default count metric populated. So, for the first
+ // added metric, we need to overwrite it. For additional metrics, we
+ // can just click the "Add" button
+ if (index === 0) {
+ cy.getElementByTestId('metricsAggGroup')
+ .find('.euiAccordion__button')
+ .click({ force: true });
+ } else {
+ cy.getElementByTestId('visEditorAdd_metrics').click();
+ cy.getElementByTestId('visEditorAdd_metrics_Y-axis').click();
+ }
+ addMetric(metric, index);
+ });
+};
+
+/**
+ * Adds a metric to the specified index in the vis editor page
+ */
+const addMetric = (metric, index) => {
+ cy.getElementByTestId('metricsAggGroup')
+ .find(`[data-test-subj="visEditorAggAccordion${index + 1}"]`)
+ .within(() => {
+ cy.wait(1000);
+ cy.getElementByTestId('comboBoxSearchInput').type(
+ `${metric.aggregation}{downarrow}{enter}`,
+ {
+ force: true,
+ }
+ );
+ cy.contains(`${metric.aggregation}`).click({ force: true });
+ });
+
+ // non-count aggregations will have an additional field value to set
+ if (metric.aggregation !== 'Count' && metric.aggregation !== 'count') {
+ cy.getElementByTestId('metricsAggGroup')
+ .find(`[data-test-subj="visEditorAggAccordion${index + 1}"]`)
+ .find('[data-test-subj="visDefaultEditorField"]')
+ .within(() => {
+ cy.wait(1000);
+ cy.getElementByTestId('comboBoxSearchInput').type(
+ `${metric.field}{downarrow}{enter}`,
+ {
+ force: true,
+ }
+ );
+ });
+ }
+
+ // re-collapse the accordion
+ cy.getElementByTestId(`visEditorAggAccordion${index + 1}`)
+ .find('.euiAccordion__button')
+ .first()
+ .click({ force: true });
+};
+
+/**
+ * Creates an individual visualization, assuming runner is
+ * starting from dashboard edit view.
+ */
+export const createVisualizationFromDashboard = (
+ visType,
+ indexPatternName,
+ visualizationName,
+ metrics
+) => {
+ bootstrapCreateFromDashboard(visType, indexPatternName);
+
+ // Vega visualizations don't configure axes the same way,
+ // so ignore those here. Note we still want to support the vega type,
+ // but don't support any custom specs, as the default spec may be
+ // sufficient for now
+ if (visType !== 'vega') {
+ if (!isEmpty(metrics)) {
+ setYAxis(metrics);
+ }
+ setXAxisDateHistogram();
+ }
+ saveVisualizationAndReturn(visualizationName);
+};
+
+/**
+ * Ingests the specified sample data, creates and saves a specified
+ * list of visualizations, and saves them all to a new dashboard
+ */
+export const bootstrapDashboard = (
+ indexSettingsFilepath,
+ indexPatternFieldsFilepath,
+ sampleDataFilepath,
+ indexName,
+ indexPatternName,
+ dashboardName,
+ visualizationSpecs
+) => {
+ const miscUtils = new MiscUtils(cy);
+ deleteVisAugmenterData(
+ indexName,
+ indexPatternName,
+ visualizationSpecs.map((visualizationSpec) => visualizationSpec.name),
+ dashboardName
+ );
+ ingestVisAugmenterData(
+ indexName,
+ indexPatternName,
+ indexSettingsFilepath,
+ indexPatternFieldsFilepath,
+ sampleDataFilepath
+ );
+
+ miscUtils.visitPage('app/dashboards');
+
+ cy.getElementByTestId('createDashboardPromptButton')
+ .should('be.visible')
+ .click();
+
+ // Create several different visualizations
+ visualizationSpecs.forEach((visualizationSpec) => {
+ createVisualizationFromDashboard(
+ visualizationSpec.type,
+ visualizationSpec.indexPattern,
+ visualizationSpec.name,
+ visualizationSpec.metrics
+ );
+ });
+
+ cy.getElementByTestId('dashboardSaveMenuItem').click({
+ force: true,
+ });
+
+ cy.getElementByTestId('savedObjectTitle').type(dashboardName);
+
+ cy.getElementByTestId('confirmSaveSavedObjectButton').click({
+ force: true,
+ });
+};
diff --git a/cypress/utils/dashboards/vis-augmenter/index.d.ts b/cypress/utils/dashboards/vis-augmenter/index.d.ts
new file mode 100644
index 000000000..ded2d27d3
--- /dev/null
+++ b/cypress/utils/dashboards/vis-augmenter/index.d.ts
@@ -0,0 +1,45 @@
+// type definitions for custom commands like "createDefaultTodos"
+///
+
+declare namespace Cypress {
+ interface Chainable {
+ /**
+ * Returns visualization panel by title
+ * @example
+ * cy.getVisPanelByTitle('[Logs] Visitors by OS')
+ */
+ getVisPanelByTitle(title: string): Chainable;
+
+ /**
+ * Opens vis panel context menu
+ * @example
+ * cy.get('visPanel').openVisContextMenu()
+ */
+ openVisContextMenu(): Chainable;
+
+ /**
+ * Clicks vis panel context menu item
+ * @example
+ * cy.clickVisPanelMenuItem('Alerting')
+ */
+ clickVisPanelMenuItem(text: string): Chainable;
+
+ /**
+ * Gets all items in the context menu
+ * @example
+ * cy.getVisPanelByTitle('my-visualization')
+ .openVisContextMenu()
+ .getMenuItems()
+ .contains('View Events')
+ .should('exist');
+ */
+ getMenuItems(): Chainable;
+
+ /**
+ * Visits a dashboard
+ * @example
+ * cy.visitDashboard('My-Dashboard')
+ */
+ visitDashboard(dashboardName: string): Chainable;
+ }
+}
diff --git a/cypress/utils/helpers.js b/cypress/utils/helpers.js
index a64e431fa..bb67116f6 100644
--- a/cypress/utils/helpers.js
+++ b/cypress/utils/helpers.js
@@ -4,3 +4,4 @@
*/
export * from './plugins/anomaly-detection-dashboards-plugin/helpers';
+export * from './dashboards/vis-augmenter/helpers';