diff --git a/.github/workflows/cypress-workflow.yml b/.github/workflows/cypress-workflow.yml
new file mode 100644
index 000000000..67d2e3c46
--- /dev/null
+++ b/.github/workflows/cypress-workflow.yml
@@ -0,0 +1,95 @@
+name: Cypress integration tests workflow
+on:
+ pull_request:
+ branches:
+ - "*"
+ push:
+ branches:
+ - "*"
+env:
+ OPENSEARCH_DASHBOARDS_VERSION: '2.4'
+ OPENSEARCH_VERSION: '2.4.0-SNAPSHOT'
+jobs:
+ tests:
+ name: Run Cypress E2E tests
+ runs-on: ubuntu-latest
+ env:
+ # prevents extra Cypress installation progress messages
+ CI: 1
+ # avoid warnings like "tput: No value for $TERM and no -T specified"
+ TERM: xterm
+ steps:
+ - name: Set up JDK
+ uses: actions/setup-java@v1
+ with:
+ # TODO: Parse this from security analytics plugin
+ java-version: 11
+ - name: Checkout index management
+ uses: actions/checkout@v2
+ with:
+ path: security-analytics
+ repository: opensearch-project/security-analytics
+ ref: '2.4'
+ - name: Run opensearch with plugin
+ run: |
+ cd security-analytics
+ ./gradlew run -Dopensearch.version=${{ env.OPENSEARCH_VERSION }} &
+ sleep 300
+ # timeout 300 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:9200)" != "200" ]]; do sleep 5; done'
+ - name: Checkout Security Analytics Dashboards plugin
+ uses: actions/checkout@v2
+ with:
+ path: security-analytics-dashboards-plugin
+ - name: Checkout OpenSearch-Dashboards
+ uses: actions/checkout@v2
+ with:
+ repository: opensearch-project/OpenSearch-Dashboards
+ path: OpenSearch-Dashboards
+ ref: ${{ env.OPENSEARCH_DASHBOARDS_VERSION }}
+ - name: Get node and yarn versions
+ id: versions
+ run: |
+ echo "::set-output name=node_version::$(node -p "(require('./OpenSearch-Dashboards/package.json').engines.node).match(/[.0-9]+/)[0]")"
+ echo "::set-output name=yarn_version::$(node -p "(require('./OpenSearch-Dashboards/package.json').engines.yarn).match(/[.0-9]+/)[0]")"
+ - name: Setup node
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ steps.versions.outputs.node_version }}
+ registry-url: 'https://registry.npmjs.org'
+ - name: Install correct yarn version for OpenSearch-Dashboards
+ run: |
+ npm uninstall -g yarn
+ echo "Installing yarn ${{ steps.versions_step.outputs.yarn_version }}"
+ npm i -g yarn@${{ steps.versions.outputs.yarn_version }}
+ - name: Bootstrap plugin/OpenSearch-Dashboards
+ run: |
+ mkdir -p OpenSearch-Dashboards/plugins
+ mv security-analytics-dashboards-plugin OpenSearch-Dashboards/plugins
+ cd OpenSearch-Dashboards/plugins/security-analytics-dashboards-plugin
+ yarn osd bootstrap
+ - name: Run OpenSearch-Dashboards server
+ run: |
+ cd OpenSearch-Dashboards
+ yarn start --no-base-path --no-watch &
+ sleep 300
+ # timeout 300 bash -c 'while [[ "$(curl -s localhost:5601/api/status | jq -r '.status.overall.state')" != "green" ]]; do sleep 5; done'
+ # for now just chrome, use matrix to do all browsers later
+ - name: Cypress tests
+ uses: cypress-io/github-action@v2
+ with:
+ working-directory: OpenSearch-Dashboards/plugins/security-analytics-dashboards-plugin
+ command: yarn run cypress run
+ wait-on: 'http://localhost:5601'
+ browser: chrome
+ # Screenshots are only captured on failure, will change this once we do visual regression tests
+ - uses: actions/upload-artifact@v1
+ if: failure()
+ with:
+ name: cypress-screenshots
+ path: OpenSearch-Dashboards/plugins/security-analytics-dashboards-plugin/cypress/screenshots
+ # Test run video was always captured, so this action uses "always()" condition
+ - uses: actions/upload-artifact@v1
+ if: always()
+ with:
+ name: cypress-videos
+ path: OpenSearch-Dashboards/plugins/security-analytics-dashboards-plugin/cypress/videos
diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts
new file mode 100644
index 000000000..eee870220
--- /dev/null
+++ b/cypress/plugins/index.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+// TODO: yarn osd bootstrap fails when trying to add below package as a dependency..
+// const wp = require("@cypress/webpack-preprocessor");
+//
+/**
+ * @type {Cypress.PluginConfig}
+ */
+module.exports = (on) => {
+ // const options = {
+ // webpackOptions: {
+ // resolve: {
+ // extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
+ // },
+ // module: {
+ // rules: [
+ // {
+ // test: /\.tsx?$/,
+ // loader: "ts-loader",
+ // options: { transpileOnly: true },
+ // },
+ // ],
+ // },
+ // },
+ // };
+ //
+ // on("file:preprocessor", wp(options));
+};
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
new file mode 100644
index 000000000..11d1938b8
--- /dev/null
+++ b/cypress/support/commands.js
@@ -0,0 +1,115 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+const { ADMIN_AUTH, INDICES, NODE_API, PLUGIN_NAME } = require('./constants');
+
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add("login", (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
+
+Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
+ // Add the basic auth header when security enabled in the Opensearch cluster
+ // https://github.com/cypress-io/cypress/issues/1288
+ if (Cypress.env('security_enabled')) {
+ const ADMIN_AUTH = {
+ username: Cypress.env('username'),
+ password: Cypress.env('password'),
+ };
+ if (options) {
+ options.auth = ADMIN_AUTH;
+ } else {
+ options = { auth: ADMIN_AUTH };
+ }
+ // Add query parameters - select the default OSD tenant
+ options.qs = { security_tenant: 'private' };
+ return originalFn(url, options);
+ } else {
+ return originalFn(url, options);
+ }
+});
+
+// Be able to add default options to cy.request(), https://github.com/cypress-io/cypress/issues/726
+Cypress.Commands.overwrite('request', (originalFn, ...args) => {
+ let defaults = {};
+ // Add the basic authentication header when security enabled in the Opensearch cluster
+ const ADMIN_AUTH = {
+ username: Cypress.env('username'),
+ password: Cypress.env('password'),
+ };
+ if (Cypress.env('security_enabled')) {
+ defaults.auth = ADMIN_AUTH;
+ }
+
+ let options = {};
+ if (typeof args[0] === 'object' && args[0] !== null) {
+ options = Object.assign({}, args[0]);
+ } else if (args.length === 1) {
+ [options.url] = args;
+ } else if (args.length === 2) {
+ [options.method, options.url] = args;
+ } else if (args.length === 3) {
+ [options.method, options.url, options.body] = args;
+ }
+
+ return originalFn(Object.assign({}, defaults, options));
+});
+
+Cypress.Commands.add('deleteAllIndices', () => {
+ cy.request('DELETE', `${Cypress.env('opensearch')}/index*,sample*,opensearch_dashboards*,test*`);
+});
+
+Cypress.Commands.add('createDetector', (detectorJSON) => {
+ cy.request('POST', `${Cypress.env('opensearch')}${NODE_API.DETECTORS_BASE}`, detectorJSON);
+});
+
+Cypress.Commands.add('updateDetector', (detectorId, detectorJSON) => {
+ cy.request(
+ 'PUT',
+ `${Cypress.env('opensearch')}/${NODE_API.DETECTORS_BASE}/${detectorId}`,
+ detectorJSON
+ );
+});
+
+Cypress.Commands.add('createRule', (ruleJSON) => {
+ cy.request('POST', `${Cypress.env('opensearch')}${NODE_API.RULES_BASE}`, ruleJSON);
+});
+
+Cypress.Commands.add('updateRule', (ruleId, ruleJSON) => {
+ cy.request('PUT', `${Cypress.env('opensearch')}/${NODE_API.RULES_BASE}/${ruleId}`, ruleJSON);
+});
+
+Cypress.Commands.add('createIndex', (index, settings = {}) => {
+ cy.request('PUT', `${Cypress.env('opensearch')}/${index}`, settings);
+});
+
+Cypress.Commands.add('createIndexTemplate', (name, template) => {
+ cy.request(
+ 'PUT',
+ `${Cypress.env('opensearch')}${NODE_API.INDEX_TEMPLATE_BASE}/${name}`,
+ template
+ );
+});
diff --git a/cypress/support/constants.js b/cypress/support/constants.js
new file mode 100644
index 000000000..d6f34163a
--- /dev/null
+++ b/cypress/support/constants.js
@@ -0,0 +1,21 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { API } from '../../server/utils/constants';
+
+export const TWENTY_SECONDS = 20000;
+
+export const INDICES = {
+ DETECTORS_INDEX: '.opensearch-detectors-config',
+ PRE_PACKAGED_RULES_INDEX: '.opensearch-pre-packaged-rules-config',
+ CUSTOM_RULES_INDEX: '.opensearch-custom-rules-config',
+};
+
+export const PLUGIN_NAME = 'opensearch_index_management_dashboards';
+
+export const NODE_API = {
+ ...API,
+ INDEX_TEMPLATE_BASE: '/_index_template',
+};
diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts
new file mode 100644
index 000000000..16182a154
--- /dev/null
+++ b/cypress/support/index.d.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+///
+
+declare namespace Cypress {
+ interface Chainable {
+ /**
+ * Deletes all indices in cluster
+ * @example
+ * cy.deleteAllIndices()
+ */
+ deleteAllIndices(): Chainable;
+
+ /**
+ * Creates a detector
+ * @example
+ * cy.createPolicy({ "detector_type": ... })
+ */
+ createDetector(detectorJSON: object): Chainable;
+
+ /**
+ * Updates settings for index
+ * @example
+ * cy.updateIndexSettings("some_index", settings)
+ */
+ updateDetector(detectorId: string, detectorJSON: object): Chainable;
+
+ /**
+ * Creates index with policy
+ * @example
+ * cy.createIndex("some_index", "some_policy")
+ */
+ createIndex(index: string, settings?: object): Chainable;
+
+ /**
+ * Creates an index template.
+ * @example
+ * cy.createIndexTemplate("some_index_template", { "index_patterns": "abc", "properties": { ... } })
+ */
+ createIndexTemplate(name: string, template: object): Chainable;
+ }
+}
diff --git a/cypress/support/index.js b/cypress/support/index.js
new file mode 100644
index 000000000..cf15f85bb
--- /dev/null
+++ b/cypress/support/index.js
@@ -0,0 +1,33 @@
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands';
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
+
+const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/;
+Cypress.on('uncaught:exception', (err) => {
+ /* returning false here prevents Cypress from failing the test */
+ if (resizeObserverLoopErrRe.test(err.message)) {
+ return false;
+ }
+});
+
+// Switch the base URL of Opensearch when security enabled in the cluster
+// Not doing this for Dashboards because it can still use http when security enabled
+if (Cypress.env('security_enabled')) {
+ Cypress.env('opensearch', `https://${Cypress.env('opensearch_url')}`);
+} else {
+ Cypress.env('opensearch', `http://${Cypress.env('opensearch_url')}`);
+}