From 25afb705b4d24413a330cde51f1e74803555ecc7 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Mon, 7 Mar 2022 10:26:41 -0800 Subject: [PATCH] Add AD cypress tests for uncovered workflows and pages (#95) * Add test coverage for AD plugin Signed-off-by: Tyler Ohlsen * Add helpers file Signed-off-by: Tyler Ohlsen * Remove unused constants/helpers/fixtures Signed-off-by: Tyler Ohlsen * Add tests for checking flyouts Signed-off-by: Tyler Ohlsen * Make sample detector WF more efficient; set up config page test suite Signed-off-by: Tyler Ohlsen * Add detector config page tests Signed-off-by: Tyler Ohlsen * Add tests for real-time results page Signed-off-by: Tyler Ohlsen * Add historical results tests Signed-off-by: Tyler Ohlsen * Add back missing import Signed-off-by: Tyler Ohlsen * Reorganize tests; add historical analysis test Signed-off-by: Tyler Ohlsen * Update test data Signed-off-by: Tyler Ohlsen * Rename historical analysis test suite Signed-off-by: Tyler Ohlsen * Add more historical analysis tests Signed-off-by: Tyler Ohlsen --- .../delete_detector_response.json | 25 +- .../multiple_detectors_index_response.json | 11 + .../multiple_detectors_response.json | 8 +- .../post_detector_response.json | 23 -- .../sample_test_data.txt | 6 + .../search_index_response.json | 4 - ...son => single_index_mapping_response.json} | 2 +- .../single_index_response.json | 6 + .../single_running_detector_response.json | 4 +- .../single_stopped_detector_response.json | 4 +- .../ad_dashboard_sanity_test_spec.js | 113 -------- .../create_detector_spec.js | 113 +++++--- .../dashboard_spec.js | 144 +++++++++++ .../detector_configuration_spec.js | 115 +++++++++ .../detector_list_spec.js | 241 ++++++++++-------- .../historical_analysis_spec.js | 139 ++++++++++ .../overview_spec.js | 79 ++++++ .../real_time_results_spec.js | 68 +++++ .../sample_detector_spec.js | 68 +++++ cypress/utils/commands.js | 9 + cypress/utils/helpers.js | 6 + .../commands.js | 68 +++-- .../constants.js | 81 ++++-- .../helpers.js | 23 ++ 24 files changed, 1013 insertions(+), 347 deletions(-) create mode 100644 cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/multiple_detectors_index_response.json delete mode 100644 cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/post_detector_response.json create mode 100644 cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/sample_test_data.txt delete mode 100644 cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/search_index_response.json rename cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/{index_mapping_response.json => single_index_mapping_response.json} (92%) create mode 100644 cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_index_response.json delete mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/ad_dashboard_sanity_test_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/dashboard_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/detector_configuration_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/historical_analysis_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/overview_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/real_time_results_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/sample_detector_spec.js create mode 100644 cypress/utils/helpers.js create mode 100644 cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/delete_detector_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/delete_detector_response.json index 0b764f699..5cde039ce 100644 --- a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/delete_detector_response.json +++ b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/delete_detector_response.json @@ -1,15 +1,14 @@ { - "_index": ".opendistro-anomaly-detectors", - "_type": "_doc", - "_id": "ulgqpXEBqtadYz9j2MHG", - "_version": 2, - "result": "deleted", - "forced_refresh": true, - "_shards": { - "total": 2, - "successful": 2, - "failed": 0 - }, - "_seq_no": 6, - "_primary_term": 1 + "ok": true, + "response": { + "_index": ".opendistro-anomaly-detectors", + "_type": "_doc", + "_id": "nKDQ-n4BQogdTzUQtWpL", + "_version": 2, + "result": "deleted", + "forced_refresh": true, + "_shards": { "total": 2, "successful": 1, "failed": 0 }, + "_seq_no": 16, + "_primary_term": 15 + } } diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/multiple_detectors_index_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/multiple_detectors_index_response.json new file mode 100644 index 000000000..c564319cb --- /dev/null +++ b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/multiple_detectors_index_response.json @@ -0,0 +1,11 @@ +{ + "ok": true, + "response": { + "indices": [ + { "health": "green", "index": "feature-required-detector-index" }, + { "health": "green", "index": "initializing-detector-index" }, + { "health": "green", "index": "running-detector-index" }, + { "health": "green", "index": "stopped-detector-index" } + ] + } +} diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/multiple_detectors_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/multiple_detectors_response.json index 3fbbc39a6..8a8f7ff26 100644 --- a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/multiple_detectors_response.json +++ b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/multiple_detectors_response.json @@ -6,7 +6,7 @@ { "id": "ulgqpXEBqtadYz7j2MHG", "description": "test", - "indices": ["feature-required-index"], + "indices": ["feature-required-detector-index"], "lastUpdateTime": 1587614056945, "name": "feature-required-detector", "timeField": "timestamp", @@ -28,7 +28,7 @@ { "id": "ulgqpXEBqtadYz6j2MHG", "description": "test", - "indices": ["initializing-index"], + "indices": ["initializing-detector-index"], "lastUpdateTime": 1587614056945, "name": "initializing-detector", "timeField": "timestamp", @@ -63,7 +63,7 @@ { "id": "ulgqpXEBqtadYz8j2MHG", "description": "test", - "indices": ["running-index"], + "indices": ["running-detector-index"], "lastUpdateTime": 1587614056945, "name": "running-detector", "timeField": "timestamp", @@ -98,7 +98,7 @@ { "id": "ulgqpXEBqtadYz9j2MHG", "description": "test", - "indices": ["stopped-index"], + "indices": ["stopped-detector-index"], "lastUpdateTime": 1587614056945, "name": "stopped-detector", "timeField": "timestamp", diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/post_detector_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/post_detector_response.json deleted file mode 100644 index 1db0dd0b8..000000000 --- a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/post_detector_response.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "ok": true, - "response": { - "name": "detector-name", - "description": "", - "timeField": "timestamp", - "indices": ["e2e-test-index"], - "detectionInterval": { "period": { "interval": 10, "unit": "Minutes" } }, - "windowDelay": { "period": { "interval": 1, "unit": "Minutes" } }, - "schemaVersion": 0, - "id": "MiSon3IBq0Ub5wDcgBII", - "primaryTerm": 3, - "seqNo": 41, - "filterQuery": { "match_all": { "boost": 1 } }, - "featureAttributes": [], - "uiMetadata": { - "features": {}, - "filters": [], - "filterType": "simple_filter" - }, - "enabled": false - } -} diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/sample_test_data.txt b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/sample_test_data.txt new file mode 100644 index 000000000..b205b8cb2 --- /dev/null +++ b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/sample_test_data.txt @@ -0,0 +1,6 @@ +{"index":{"_id": "id-1"}} +{ "value": 1, "timestamp": "2020-01-01"} +{"index":{"_id": "id-2"}} +{ "value": 2, "timestamp": "2020-01-02"} +{"index":{"_id": "id-3"}} +{ "value": 3, "timestamp": "2020-01-03"} diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/search_index_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/search_index_response.json deleted file mode 100644 index e3758032e..000000000 --- a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/search_index_response.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ok": true, - "response": { "indices": [{ "health": "green", "index": "e2e-test-index" }] } -} diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/index_mapping_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_index_mapping_response.json similarity index 92% rename from cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/index_mapping_response.json rename to cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_index_mapping_response.json index 483d3f6ba..6a69d91d7 100644 --- a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/index_mapping_response.json +++ b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_index_mapping_response.json @@ -2,7 +2,7 @@ "ok": true, "response": { "mappings": { - "e2e-test-index": { + "test-index": { "mappings": { "properties": { "timestamp": { diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_index_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_index_response.json new file mode 100644 index 000000000..3bad051ea --- /dev/null +++ b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_index_response.json @@ -0,0 +1,6 @@ +{ + "ok": true, + "response": { + "indices": [{ "health": "green", "index": "test-index" }] + } +} diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_running_detector_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_running_detector_response.json index 2a358163f..1c246f329 100644 --- a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_running_detector_response.json +++ b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_running_detector_response.json @@ -5,8 +5,8 @@ "detectorList": [ { "id": "ulgqpXEBqtadYz9j2MHG", - "description": "test", - "indices": ["test-index"], + "description": "A running detector.", + "indices": ["running-detector-index"], "lastUpdateTime": 1587614056945, "name": "running-detector", "timeField": "timestamp", diff --git a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_stopped_detector_response.json b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_stopped_detector_response.json index 49c77e94f..422c6bd1e 100644 --- a/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_stopped_detector_response.json +++ b/cypress/fixtures/plugins/anomaly-detection-dashboards-plugin/single_stopped_detector_response.json @@ -5,8 +5,8 @@ "detectorList": [ { "id": "ulgqpXEBqtadYz9j2MHG", - "description": "test", - "indices": ["test-index"], + "description": "A stopped detector.", + "indices": ["stopped-detector-index"], "lastUpdateTime": 1587614056945, "name": "stopped-detector", "timeField": "timestamp", diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/ad_dashboard_sanity_test_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/ad_dashboard_sanity_test_spec.js deleted file mode 100644 index 4a1819b8f..000000000 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/ad_dashboard_sanity_test_spec.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - AD_FIXTURE_BASE_PATH, - BASE_AD_DASHBOARDS_PATH, -} from '../../../utils/constants'; - -// Contains basic sanity tests on AD Dashboards page -describe('AD Dashboard page', () => { - // start a server so that server responses can be mocked via fixtures - // in all of the below test cases - before(() => { - cy.server(); - }); - - it('empty - no detector index', () => { - cy.mockGetDetectorOnAction( - AD_FIXTURE_BASE_PATH + 'no_detector_index_response.json', - () => { - cy.visit(BASE_AD_DASHBOARDS_PATH); - } - ); - cy.contains('h2', 'You have no detectors'); - }); - - it('empty - empty detector index', () => { - cy.mockGetDetectorOnAction( - AD_FIXTURE_BASE_PATH + 'empty_detector_index_response.json', - () => { - cy.visit(BASE_AD_DASHBOARDS_PATH); - } - ); - cy.contains('h2', 'You have no detectors'); - }); - - it('non-empty - single running detector', () => { - cy.mockGetDetectorOnAction( - AD_FIXTURE_BASE_PATH + 'single_running_detector_response.json', - () => { - cy.visit(BASE_AD_DASHBOARDS_PATH); - } - ); - - cy.contains('h3', 'Live anomalies'); - cy.contains('a', 'running-detector'); - }); - - it('redirect to create detector page', () => { - cy.mockGetDetectorOnAction( - AD_FIXTURE_BASE_PATH + 'no_detector_index_response.json', - () => { - cy.visit(BASE_AD_DASHBOARDS_PATH); - } - ); - - cy.mockSearchIndexOnAction( - AD_FIXTURE_BASE_PATH + 'search_index_response.json', - () => { - cy.get('a[data-test-subj="createDetectorButton"]').click({ - force: true, - }); - } - ); - - cy.contains('span', 'Create detector'); - }); - - it('filter by detector', () => { - cy.mockGetDetectorOnAction( - AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', - () => { - cy.visit(BASE_AD_DASHBOARDS_PATH); - } - ); - - cy.contains('stopped-detector'); - cy.contains('running-detector'); - - cy.get('[data-test-subj=comboBoxToggleListButton]') - .first() - .click({ force: true }); - cy.get('.euiFilterSelectItem').first().click({ force: true }); - cy.get('.euiPageSideBar').click({ force: true }); - - cy.contains('feature-required-detector'); // first one in the list returned by multiple_detectors_response.json - cy.contains('stopped-detector').should('not.be.visible'); - cy.contains('running-detector').should('not.be.visible'); - }); - - it('filter by detector state', () => { - cy.mockGetDetectorOnAction( - AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', - () => { - cy.visit(BASE_AD_DASHBOARDS_PATH); - } - ); - - cy.contains('stopped-detector'); - cy.contains('running-detector'); - - cy.get('[data-test-subj=comboBoxToggleListButton]') - .eq(1) - .click({ force: true }); - cy.get('.euiFilterSelectItem').first().click({ force: true }); - cy.get('.euiPageSideBar').click({ force: true }); - - cy.contains('stopped-detector'); // because stopped is the first item in the detector state dropdown - cy.contains('running-detector').should('not.be.visible'); - }); -}); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/create_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/create_detector_spec.js index 61408c0ac..f2a2284cd 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/create_detector_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/create_detector_spec.js @@ -3,48 +3,91 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - AD_FIXTURE_BASE_PATH, - BASE_AD_CREATE_AD_PATH, -} from '../../../utils/constants'; - -context('Create detector', () => { - it.skip('Create detector - from dashboard', () => { - cy.mockSearchIndexOnAction( - AD_FIXTURE_BASE_PATH + 'search_index_response.json', - () => { - cy.visit(BASE_AD_CREATE_AD_PATH); - } - ); +import { AD_FIXTURE_BASE_PATH, AD_URL } from '../../../utils/constants'; +import { selectTopItemFromFilter } from '../../../utils/helpers'; - cy.contains('h1', 'Create detector'); +context('Create detector workflow', () => { + const TEST_DETECTOR_NAME = 'test-detector'; + const TEST_DETECTOR_DESCRIPTION = 'Some test detector description.'; + const TEST_FEATURE_NAME = 'test-feature'; + const TEST_TIMESTAMP_NAME = 'timestamp'; // coming from single_index_response.json fixture + const TEST_INDEX_NAME = 'ad-cypress-test-index'; - const detectorName = 'detector-name'; - cy.get('input[name="detectorName"]').type(detectorName, { force: true }); + // Index some sample data first + before(() => { + cy.server(); + cy.fixture(AD_FIXTURE_BASE_PATH + 'sample_test_data.txt').then((data) => { + cy.request({ + method: 'POST', + form: true, + url: 'api/console/proxy', + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + qs: { + path: `${TEST_INDEX_NAME}/_bulk`, + method: 'POST', + }, + body: data, + }); + }); + }); - cy.mockGetIndexMappingsOnAction( - AD_FIXTURE_BASE_PATH + 'index_mapping_response.json', - () => { - cy.get('input[role="textbox"]').first().type('e2e-test-index{enter}', { - force: true, - }); - } + // Clean up created resources + after(() => { + cy.log('Deleting index with name: ' + TEST_INDEX_NAME); + cy.deleteIndex(TEST_INDEX_NAME); + }); + + it('Full creation - based on real index', () => { + // Define detector step + cy.visit(AD_URL.CREATE_AD); + cy.getElementByTestId('defineOrEditDetectorTitle').should('exist'); + cy.getElementByTestId('detectorNameTextInput').type(TEST_DETECTOR_NAME); + cy.getElementByTestId('detectorDescriptionTextInput').type( + TEST_DETECTOR_DESCRIPTION + ); + cy.getElementByTestId('indicesFilter').type(`${TEST_INDEX_NAME}{enter}`); + selectTopItemFromFilter('timestampFilter', false); + cy.getElementByTestId('defineDetectorNextButton').click(); + cy.getElementByTestId('defineOrEditDetectorTitle').should('not.exist'); + cy.getElementByTestId('configureOrEditModelConfigurationTitle').should( + 'exist' ); - cy.get('input[role="textbox"]').last().type('timestamp{enter}', { - force: true, - }); + // Configure model step + cy.getElementByTestId('featureNameTextInput-0').type(TEST_FEATURE_NAME); + selectTopItemFromFilter('featureFieldTextInput-0', false); + cy.getElementByTestId('configureModelNextButton').click(); + cy.getElementByTestId('configureOrEditModelConfigurationTitle').should( + 'not.exist' + ); + cy.getElementByTestId('detectorJobsTitle').should('exist'); - cy.mockCreateDetectorOnAction( - AD_FIXTURE_BASE_PATH + 'post_detector_response.json', - () => { - cy.get('[data-test-subj=createOrSaveDetectorButton]').click({ - force: true, - }); - } + // Set up detector jobs step + cy.getElementByTestId('detectorJobsNextButton').click(); + cy.getElementByTestId('detectorJobsTitle').should('not.exist'); + cy.getElementByTestId('reviewAndCreateTitle').should('exist'); + + // Review and create step + cy.getElementByTestId('detectorNameCell').contains(TEST_DETECTOR_NAME); + cy.getElementByTestId('detectorDescriptionCell').contains( + TEST_DETECTOR_DESCRIPTION ); + cy.getElementByTestId('indexNameCell').contains(TEST_INDEX_NAME); + cy.getElementByTestId('timestampNameCell').contains(TEST_TIMESTAMP_NAME); + cy.getElementByTestId('featureTable').contains(TEST_FEATURE_NAME); + cy.getElementByTestId('createDetectorButton').click(); - cy.contains('h1', detectorName); - cy.contains('h3', 'Detector configuration'); + // Clean up the created detector. Extract detector ID from the detector configuration page + cy.getElementByTestId('detectorIdCell').within(() => { + cy.get('.euiText--medium') + .invoke('text') + .then((detectorId) => { + cy.log('Deleting detector with ID: ' + detectorId); + cy.deleteDetector(detectorId); + }); + }); }); }); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/dashboard_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/dashboard_spec.js new file mode 100644 index 000000000..e53b4a5a5 --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/dashboard_spec.js @@ -0,0 +1,144 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AD_FIXTURE_BASE_PATH, AD_URL } from '../../../utils/constants'; +import { selectTopItemFromFilter } from '../../../utils/helpers'; + +// Contains basic sanity tests on AD Dashboards page +describe('AD Dashboard page', () => { + before(() => { + cy.server(); + }); + + it('Empty - no detector index', () => { + cy.mockGetDetectorOnAction( + AD_FIXTURE_BASE_PATH + 'no_detector_index_response.json', + () => { + cy.visit(AD_URL.DASHBOARD); + } + ); + cy.getElementByTestId('emptyDashboardHeader').should('exist'); + cy.getElementByTestId('dashboardLiveAnomaliesHeader').should('not.exist'); + }); + + it('Empty - empty detector index', () => { + cy.mockGetDetectorOnAction( + AD_FIXTURE_BASE_PATH + 'empty_detector_index_response.json', + () => { + cy.visit(AD_URL.DASHBOARD); + } + ); + cy.getElementByTestId('emptyDashboardHeader').should('exist'); + cy.getElementByTestId('dashboardLiveAnomaliesHeader').should('not.exist'); + }); + + it('Non-empty - single running detector', () => { + cy.mockGetDetectorOnAction( + AD_FIXTURE_BASE_PATH + 'single_running_detector_response.json', + () => { + cy.visit(AD_URL.DASHBOARD); + } + ); + + cy.getElementByTestId('emptyDashboardHeader').should('not.exist'); + cy.getElementByTestId('dashboardLiveAnomaliesHeader').should('exist'); + cy.getElementByTestId('dashboardDetectorTable').should('exist'); + cy.getElementByTestId('dashboardSunburstChartHeader').should('exist'); + cy.getElementByTestId('dashboardDetectorTable').contains( + 'running-detector' + ); + }); + + it('Redirect to create detector page', () => { + cy.mockGetDetectorOnAction( + AD_FIXTURE_BASE_PATH + 'no_detector_index_response.json', + () => { + cy.visit(AD_URL.DASHBOARD); + } + ); + + cy.mockSearchIndexOnAction( + AD_FIXTURE_BASE_PATH + 'multiple_detectors_index_response.json', + () => { + cy.getElementByTestId('createDetectorButton').click(); + } + ); + + cy.getElementByTestId('defineOrEditDetectorTitle').should('exist'); + }); + + it('Filter by detector', () => { + cy.mockGetDetectorOnAction( + AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', + () => { + cy.visit(AD_URL.DASHBOARD); + } + ); + + cy.contains('stopped-detector'); + cy.contains('running-detector'); + + selectTopItemFromFilter('detectorFilter'); + + cy.contains('feature-required-detector'); // first one in the list returned by multiple_detectors_response.json + cy.contains('stopped-detector').should('not.be.visible'); + cy.contains('running-detector').should('not.be.visible'); + }); + + it('Filter by detector state', () => { + cy.mockGetDetectorOnAction( + AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', + () => { + cy.visit(AD_URL.DASHBOARD); + } + ); + + cy.contains('stopped-detector'); + cy.contains('running-detector'); + + selectTopItemFromFilter('detectorStateFilter'); + + cy.contains('stopped-detector'); // because stopped is the first item in the detector state dropdown + cy.contains('running-detector').should('not.be.visible'); + }); + + it('Filter by index', () => { + cy.mockGetDetectorsAndIndicesOnAction( + AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', + AD_FIXTURE_BASE_PATH + 'multiple_detectors_index_response.json', + () => { + cy.visit(AD_URL.DASHBOARD); + } + ); + + cy.contains('feature-required-detector'); + cy.contains('stopped-detector'); + cy.contains('running-detector'); + + selectTopItemFromFilter('indicesFilter'); + + cy.contains('feature-required-detector'); // because feature-required is the first index returned in the fixture + cy.contains('running-detector').should('not.be.visible'); + cy.contains('stopped-detector').should('not.be.visible'); + }); + + it('Enter and exit full screen', () => { + cy.mockGetDetectorOnAction( + AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', + () => { + cy.visit(AD_URL.DASHBOARD); + } + ); + + cy.contains('View full screen'); + cy.contains('Exit full screen').should('not.be.visible'); + cy.getElementByTestId('dashboardFullScreenButton').click(); + cy.contains('View full screen').should('not.be.visible'); + cy.contains('Exit full screen'); + cy.getElementByTestId('dashboardFullScreenButton').click(); + cy.contains('View full screen'); + cy.contains('Exit full screen').should('not.be.visible'); + }); +}); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/detector_configuration_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/detector_configuration_spec.js new file mode 100644 index 000000000..2e4b71d7d --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/detector_configuration_spec.js @@ -0,0 +1,115 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AD_URL } from '../../../utils/constants'; + +context('Detector configuration page', () => { + // Creating a sample detector and visiting the config page. Stopping the detector + // for easier checks when editing/deleting detector + before(() => { + cy.server(); + cy.visit(AD_URL.OVERVIEW); + cy.getElementByTestId('createHttpSampleDetectorButton').click(); + cy.visit(AD_URL.OVERVIEW); + cy.getElementByTestId('viewSampleDetectorLink').click(); + cy.getElementByTestId('resultsTab').click(); + cy.getElementByTestId('stopAndStartDetectorButton').click(); + cy.getElementByTestId('detectorStateStopped').should('exist'); + cy.getElementByTestId('configurationsTab').click(); + }); + + // Clean up created sample index + after(() => { + cy.deleteAllIndices(); + }); + + it('Redirect to edit detector settings from button', () => { + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + + cy.getElementByTestId('editDetectorSettingsButton').click(); + + cy.getElementByTestId('defineOrEditDetectorTitle').should('exist'); + cy.go('back'); + + cy.getElementByTestId('defineOrEditDetectorTitle').should('not.exist'); + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + }); + + it('Redirect to edit model configuration from button', () => { + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + + cy.getElementByTestId('editModelConfigurationButton').click(); + + cy.getElementByTestId('configureOrEditModelConfigurationTitle').should( + 'exist' + ); + cy.go('back'); + + cy.getElementByTestId('configureOrEditModelConfigurationTitle').should( + 'not.exist' + ); + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + }); + + it('Redirect to edit detector settings from dropdown', () => { + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + + cy.getElementByTestId('actionsButton').click(); + cy.getElementByTestId('editDetectorSettingsItem').click(); + + cy.getElementByTestId('defineOrEditDetectorTitle').should('exist'); + cy.go('back'); + + cy.getElementByTestId('defineOrEditDetectorTitle').should('not.exist'); + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + }); + + it('Redirect to edit model configuration from dropdown', () => { + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + + cy.getElementByTestId('actionsButton').click(); + cy.getElementByTestId('editModelConfigurationItem').click(); + + cy.getElementByTestId('configureOrEditModelConfigurationTitle').should( + 'exist' + ); + cy.go('back'); + + cy.getElementByTestId('configureOrEditModelConfigurationTitle').should( + 'not.exist' + ); + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + }); + + it('Delete detector from dropdown, redirects to detector list page', () => { + cy.getElementByTestId('detectorSettingsHeader').should('exist'); + cy.getElementByTestId('modelConfigurationHeader').should('exist'); + cy.getElementByTestId('detectorJobsHeader').should('exist'); + + cy.getElementByTestId('actionsButton').click(); + cy.getElementByTestId('deleteDetectorItem').click(); + cy.getElementByTestId('typeDeleteField').type('delete', { force: true }); + cy.getElementByTestId('confirmButton').click(); + + cy.getElementByTestId('detectorListHeader').should('exist'); + cy.getElementByTestId('detectorListHeader').contains('(0)'); + }); +}); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/detector_list_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/detector_list_spec.js index 1ed7b9446..c5cbcfae3 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/detector_list_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/detector_list_spec.js @@ -5,129 +5,140 @@ import { AD_FIXTURE_BASE_PATH, - BASE_AD_DETECTOR_LIST_PATH, + AD_URL, TEST_DETECTOR_ID, - DETECTOR_STATE_DISABLED, - DETECTOR_STATE_INIT, - DETECTOR_STATE_RUNNING, - DETECTOR_STATE_FEATURE_REQUIRED, + DETECTOR_STATE, } from '../../../utils/constants'; +import { selectTopItemFromFilter } from '../../../utils/helpers'; -describe('Detector list', () => { - const EMPTY_MESSAGE = - 'A detector is an individual anomaly detection task. You can create multiple detectors, ' + - 'and all the detectors can run simultaneously, with each analyzing data from different sources. ' + - 'Create an anomaly detector to get started.'; +describe('Detector list page', () => { + before(() => { + cy.server(); + }); - it('Empty detectors - no detector index', () => { + it('Empty - no detector index', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'no_detector_index_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.contains('p', '(0)'); - cy.contains('p', EMPTY_MESSAGE); - cy.get('.euiButton--primary.euiButton--fill').should( - 'have.length.at.least', - 2 - ); + cy.getElementByTestId('detectorListHeader').contains('(0)'); + cy.getElementByTestId('emptyDetectorListMessage').should('exist'); + cy.getElementByTestId('sampleDetectorButton').should('exist'); + cy.getElementByTestId('createDetectorButton').should('exist'); }); - it('Empty detectors - empty detector index', () => { + it('Empty - empty detector index', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'empty_detector_index_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.contains('p', '(0)'); - cy.contains('p', EMPTY_MESSAGE); - cy.get('.euiButton--primary.euiButton--fill').should( - 'have.length.at.least', - 2 - ); + cy.getElementByTestId('detectorListHeader').contains('(0)'); + cy.getElementByTestId('emptyDetectorListMessage').should('exist'); + cy.getElementByTestId('sampleDetectorButton').should('exist'); + cy.getElementByTestId('createDetectorButton').should('exist'); }); - it('One detector - single stopped detector index', () => { + it('Non-empty - single stopped detector', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'single_stopped_detector_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.contains('p', '(1)'); - cy.contains('stopped-detector'); - cy.contains('Stopped'); - cy.contains('test-index'); - cy.get('.euiButton--primary.euiButton--fill').should( - 'have.length.at.least', - 1 + cy.getElementByTestId('detectorListHeader').contains('(1)'); + cy.getElementByTestId('detectorListTable').contains('stopped-detector'); + cy.getElementByTestId('detectorListTable').contains( + DETECTOR_STATE.DISABLED + ); + cy.getElementByTestId('detectorListTable').contains( + 'stopped-detector-index' ); + cy.getElementByTestId('createDetectorButton').should('exist'); + cy.getElementByTestId('sampleDetectorButton').should('not.exist'); + cy.getElementByTestId('emptyDetectorListMessage').should('not.exist'); }); - it('Multiple detectors - multiple detectors index', () => { + it('Non-empty - multiple detectors', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.contains('p', '(4)'); - cy.contains('stopped-detector'); - cy.contains('initializing-detector'); - cy.contains('running-detector'); - cy.contains('feature-required-detector'); - cy.contains('stopped-index'); - cy.contains('initializing-index'); - cy.contains('running-index'); - cy.contains('feature-required-index'); - cy.contains(DETECTOR_STATE_DISABLED); - cy.contains(DETECTOR_STATE_INIT); - cy.contains(DETECTOR_STATE_RUNNING); - cy.contains(DETECTOR_STATE_FEATURE_REQUIRED); - cy.get('.euiButton--primary.euiButton--fill').should( - 'have.length.at.least', - 1 + cy.getElementByTestId('detectorListHeader').contains('(4)'); + cy.getElementByTestId('detectorListTable').contains('stopped-detector'); + cy.getElementByTestId('detectorListTable').contains( + 'initializing-detector' + ); + cy.getElementByTestId('detectorListTable').contains('running-detector'); + cy.getElementByTestId('detectorListTable').contains( + 'feature-required-detector' + ); + cy.getElementByTestId('detectorListTable').contains( + 'stopped-detector-index' ); + cy.getElementByTestId('detectorListTable').contains( + 'initializing-detector-index' + ); + cy.getElementByTestId('detectorListTable').contains( + 'running-detector-index' + ); + cy.getElementByTestId('detectorListTable').contains( + 'feature-required-detector-index' + ); + cy.getElementByTestId('detectorListTable').contains( + DETECTOR_STATE.DISABLED + ); + cy.getElementByTestId('detectorListTable').contains(DETECTOR_STATE.INIT); + cy.getElementByTestId('detectorListTable').contains(DETECTOR_STATE.RUNNING); + cy.getElementByTestId('detectorListTable').contains( + DETECTOR_STATE.FEATURE_REQUIRED + ); + cy.getElementByTestId('createDetectorButton').should('exist'); + cy.getElementByTestId('sampleDetectorButton').should('not.exist'); + cy.getElementByTestId('emptyDetectorListMessage').should('not.exist'); }); it('Redirect to create detector', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'single_stopped_detector_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.get('[data-test-subj=createDetectorButton]').click({ force: true }); - cy.contains('span', 'Create detector'); + cy.getElementByTestId('createDetectorButton').click(); + cy.getElementByTestId('defineOrEditDetectorTitle').should('exist'); }); it('Start single detector', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'single_stopped_detector_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.get('.euiTableRowCellCheckbox').within(() => - cy.get('.euiCheckbox__input').click({ force: true }) - ); - cy.get('[data-test-subj=listActionsButton]').click({ force: true }); - cy.get('[data-test-subj=startDetectors]').click({ force: true }); - cy.contains('The following detectors will begin initializing.'); + cy.getElementByTestId('startDetectorsModal').should('not.exist'); + cy.get('.euiTableRowCellCheckbox').find('.euiCheckbox__input').click(); + cy.getElementByTestId('listActionsButton').click(); + cy.getElementByTestId('startDetectors').click(); + cy.getElementByTestId('startDetectorsModal').should('exist'); cy.contains('stopped-detector'); cy.mockStartDetectorOnAction( AD_FIXTURE_BASE_PATH + 'start_detector_response.json', TEST_DETECTOR_ID, () => { - cy.get('[data-test-subj=confirmButton]').click({ force: true }); + cy.getElementByTestId('confirmButton').click(); } ); + cy.getElementByTestId('startDetectorsModal').should('not.exist'); cy.contains('Successfully started all selected detectors'); }); @@ -135,54 +146,50 @@ describe('Detector list', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'single_running_detector_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.get('.euiTableRowCellCheckbox').within(() => - cy.get('.euiCheckbox__input').click({ force: true }) - ); - cy.get('[data-test-subj=listActionsButton]').click({ force: true }); - cy.get('[data-test-subj=stopDetectors]').click({ force: true }); - cy.contains('The following detectors will be stopped.'); + cy.getElementByTestId('stopDetectorsModal').should('not.exist'); + cy.get('.euiTableRowCellCheckbox').find('.euiCheckbox__input').click(); + cy.getElementByTestId('listActionsButton').click(); + cy.getElementByTestId('stopDetectors').click(); + cy.getElementByTestId('stopDetectorsModal').should('exist'); cy.contains('running-detector'); cy.mockStopDetectorOnAction( AD_FIXTURE_BASE_PATH + 'stop_detector_response.json', TEST_DETECTOR_ID, () => { - cy.get('[data-test-subj=confirmButton]').click({ force: true }); + cy.getElementByTestId('confirmButton').click(); } ); + cy.getElementByTestId('stopDetectorsModal').should('not.exist'); cy.contains('Successfully stopped all selected detectors'); }); - it.skip('Delete single detector', () => { + it('Delete single detector', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'single_stopped_detector_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.get('.euiTableRowCellCheckbox').within(() => - cy.get('.euiCheckbox__input').click({ force: true }) - ); - cy.get('[data-test-subj=listActionsButton]').click({ force: true }); - cy.get('[data-test-subj=deleteDetectors]').click({ force: true }); - cy.contains( - 'The following detectors and feature configurations will be permanently removed. This action is irreversible.' - ); + cy.getElementByTestId('deleteDetectorsModal').should('not.exist'); + cy.get('.euiTableRowCellCheckbox').find('.euiCheckbox__input').click(); + cy.getElementByTestId('listActionsButton').click(); + cy.getElementByTestId('deleteDetectors').click(); + cy.getElementByTestId('deleteDetectorsModal').should('exist'); cy.contains('stopped-detector'); cy.contains('Running'); cy.contains('No'); - cy.get('[data-test-subj=typeDeleteField]') - .click({ force: true }) - .type('delete'); + cy.getElementByTestId('typeDeleteField').click().type('delete'); cy.mockDeleteDetectorOnAction( AD_FIXTURE_BASE_PATH + 'delete_detector_response.json', TEST_DETECTOR_ID, () => { - cy.get('[data-test-subj=confirmButton]').click({ force: true }); + cy.getElementByTestId('confirmButton').click(); } ); + cy.getElementByTestId('deleteDetectorsModal').should('not.exist'); cy.contains('Successfully deleted all selected detectors'); }); @@ -190,40 +197,68 @@ describe('Detector list', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.contains('stopped-detector'); - cy.contains('running-detector'); + cy.getElementByTestId('detectorListTable').contains('stopped-detector'); + cy.getElementByTestId('detectorListTable').contains('running-detector'); - cy.get('[data-test-subj=detectorListSearch]') + cy.getElementByTestId('detectorListSearch') .first() - .click({ force: true }) + .click() .type('stopped-detector'); - cy.contains('stopped-detector'); - cy.contains('running-detector').should('not.be.visible'); + cy.getElementByTestId('detectorListTable').contains('stopped-detector'); + cy.getElementByTestId('detectorListTable') + .contains('running-detector') + .should('not.be.visible'); }); it('Filter by detector state', () => { cy.mockGetDetectorOnAction( AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', () => { - cy.visit(BASE_AD_DETECTOR_LIST_PATH); + cy.visit(AD_URL.DETECTOR_LIST); } ); - cy.contains('stopped-detector'); - cy.contains('running-detector'); + cy.getElementByTestId('detectorListTable').contains('stopped-detector'); + cy.getElementByTestId('detectorListTable').contains('running-detector'); - cy.get('[data-test-subj=comboBoxToggleListButton]') - .first() - .click({ force: true }); - cy.get('.euiFilterSelectItem').first().click({ force: true }); - cy.get('.euiPageSideBar').click({ force: true }); + selectTopItemFromFilter('detectorStateFilter'); + + cy.getElementByTestId('detectorListTable').contains('stopped-detector'); // because stopped is the first item in the detector state dropdown + cy.getElementByTestId('detectorListTable') + .contains('running-detector') + .should('not.be.visible'); + }); + + it('Filter by index', () => { + cy.mockGetDetectorsAndIndicesOnAction( + AD_FIXTURE_BASE_PATH + 'multiple_detectors_response.json', + AD_FIXTURE_BASE_PATH + 'multiple_detectors_index_response.json', + () => { + cy.visit(AD_URL.DETECTOR_LIST); + } + ); + + cy.getElementByTestId('detectorListTable').contains( + 'feature-required-detector' + ); + cy.getElementByTestId('detectorListTable').contains('stopped-detector'); + cy.getElementByTestId('detectorListTable').contains('running-detector'); + + selectTopItemFromFilter('indicesFilter'); - cy.contains('stopped-detector'); // because stopped is the first item in the detector state dropdown - cy.contains('running-detector').should('not.be.visible'); + cy.getElementByTestId('detectorListTable').contains( + 'feature-required-detector' + ); // because feature-required is the first index returned in the fixture + cy.getElementByTestId('detectorListTable') + .contains('running-detector') + .should('not.be.visible'); + cy.getElementByTestId('detectorListTable') + .contains('stopped-detector') + .should('not.be.visible'); }); }); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/historical_analysis_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/historical_analysis_spec.js new file mode 100644 index 000000000..c7ef4d80f --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/historical_analysis_spec.js @@ -0,0 +1,139 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AD_URL } from '../../../utils/constants'; + +context('Historical results page', () => { + function verifyAnomaliesInCharts() { + // Wait for any kicked off historical analysis to finish. Relying on default + // timeout (60s) to find an element that only shows up when the analysis is finished + cy.getElementByTestId('detectorStateFinished').should('exist'); + + // Let results load separately, since they load asynchronously. Should take <1s + cy.wait(5000); + + cy.getElementByTestId('anomalyOccurrenceStat').within(() => { + cy.get('.euiTitle--small') + .invoke('text') + .then((anomalyOccurrenceCount) => { + cy.log('Num anomaly occurrences: ' + anomalyOccurrenceCount); + expect(parseInt(anomalyOccurrenceCount)).to.be.gte(1); + }); + }); + cy.getElementByTestId('anomalyOccurrencesHeader').should( + 'not.contain', + '(0)' + ); + } + + function verifyNoAnomaliesInCharts() { + // Wait for any kicked off historical analysis to finish. Relying on default + // timeout (60s) to find an element that only shows up when the analysis is finished + cy.getElementByTestId('detectorStateFinished').should('exist'); + + // Let results load separately, since they load asynchronously. Should take <1s + cy.wait(5000); + + cy.getElementByTestId('anomalyOccurrenceStat').within(() => { + cy.get('.euiTitle--small') + .invoke('text') + .then((anomalyOccurrenceCount) => { + cy.log('Num anomaly occurrences: ' + anomalyOccurrenceCount); + expect(parseInt(anomalyOccurrenceCount)).to.equal(0); + }); + }); + cy.getElementByTestId('anomalyOccurrencesHeader').should('contain', '(0)'); + } + + // Creating a sample detector and visiting the config page + before(() => { + cy.server(); + cy.visit(AD_URL.OVERVIEW); + cy.getElementByTestId('createHttpSampleDetectorButton').click(); + cy.visit(AD_URL.OVERVIEW); + cy.getElementByTestId('viewSampleDetectorLink').click(); + cy.getElementByTestId('historicalTab').click(); + }); + + // Clean up resources + after(() => { + cy.getElementByTestId('actionsButton').click(); + cy.getElementByTestId('deleteDetectorItem').click(); + cy.getElementByTestId('typeDeleteField').type('delete', { force: true }); + cy.getElementByTestId('confirmButton').click(); + cy.deleteAllIndices(); + }); + + context('Sample detector', () => { + it('Empty message with modal', () => { + cy.getElementByTestId('emptyHistoricalAnalysisMessage').should('exist'); + cy.getElementByTestId('runHistoricalAnalysisButton').click(); + cy.getElementByTestId('historicalAnalysisModalHeader').should('exist'); + cy.getElementByTestId('cancelButton').click(); + cy.getElementByTestId('historicalAnalysisModalHeader').should( + 'not.exist' + ); + cy.getElementByTestId('emptyHistoricalAnalysisMessage').should('exist'); + cy.getElementByTestId('historicalAnalysisHeader').should('not.exist'); + }); + + it('Start first historical analysis', () => { + cy.getElementByTestId('runHistoricalAnalysisButton').click(); + cy.getElementByTestId('historicalAnalysisModalHeader').should('exist'); + cy.getElementByTestId('confirmButton').click(); + + cy.getElementByTestId('emptyHistoricalAnalysisMessage').should( + 'not.exist' + ); + cy.getElementByTestId('historicalAnalysisModalHeader').should( + 'not.exist' + ); + cy.getElementByTestId('detectorStateInitializing').should('exist'); + cy.getElementByTestId('historicalAnalysisTitle').should('exist'); + }); + + // Choosing the default of 30 days with the sample detector data (which contains 7 days historical data) + // should produce 4+ anomalies + it('Produces anomaly results by default', () => { + cy.wait(10000); + verifyAnomaliesInCharts(); + }); + + it('Filtering by date range', () => { + cy.getElementByTestId('superDatePickerToggleQuickMenuButton').click(); + cy.get(`[aria-label="Next time window"]`).click(); + cy.contains('Refresh').click(); + verifyNoAnomaliesInCharts(); + + cy.getElementByTestId('superDatePickerToggleQuickMenuButton').click(); + cy.get(`[aria-label="Previous time window"]`).click(); + cy.contains('Refresh').click(); + verifyAnomaliesInCharts(); + }); + + it('Aggregations render anomalies', () => { + cy.get(`[aria-label="Daily max"]`).click(); + verifyAnomaliesInCharts(); + cy.get(`[aria-label="Weekly max"]`).click(); + verifyAnomaliesInCharts(); + cy.get(`[aria-label="Monthly max"]`).click(); + verifyAnomaliesInCharts(); + }); + + it('Run subsequent historical analysis', () => { + cy.getElementByTestId('modifyHistoricalAnalysisButton').click(); + cy.getElementByTestId('historicalAnalysisModalHeader').should('exist'); + cy.getElementByTestId('confirmButton').click(); + cy.getElementByTestId('historicalAnalysisModalHeader').should( + 'not.exist' + ); + cy.getElementByTestId('detectorStateInitializing').should('exist'); + cy.getElementByTestId('historicalAnalysisTitle').should('exist'); + + cy.wait(10000); + verifyAnomaliesInCharts(); + }); + }); +}); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/overview_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/overview_spec.js new file mode 100644 index 000000000..0d0b142d0 --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/overview_spec.js @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AD_URL } from '../../../utils/constants'; + +context('Overview page', () => { + function validatePageElements() { + cy.getElementByTestId('overviewTitle').should('exist'); + cy.getElementByTestId('createHttpSampleDetectorButton').should('exist'); + cy.getElementByTestId('createECommerceSampleDetectorButton').should( + 'exist' + ); + cy.getElementByTestId('createHostHealthSampleDetectorButton').should( + 'exist' + ); + cy.getElementByTestId('flyoutInfoButton').should('have.length', 3); + } + + // Takes an index as an argument, to click on the n'th found flyout button + function openAndCloseFlyout(flyoutButtonIndex) { + cy.getElementByTestId('detectorDetailsHeader').should('not.exist'); + cy.getElementByTestId('indexDetailsHeader').should('not.exist'); + + cy.getElementByTestId('flyoutInfoButton').eq(flyoutButtonIndex).click(); + + cy.getElementByTestId('detectorDetailsHeader').should('exist'); + cy.getElementByTestId('indexDetailsHeader').should('exist'); + + cy.getElementByTestId('euiFlyoutCloseButton').click(); + + cy.getElementByTestId('detectorDetailsHeader').should('not.exist'); + cy.getElementByTestId('indexDetailsHeader').should('not.exist'); + } + + before(() => { + cy.server(); + }); + + it('Empty dashboard redirects to overview page', () => { + cy.visit(AD_URL.DASHBOARD); + cy.getElementByTestId('sampleDetectorButton').click(); + validatePageElements(); + }); + + it('Empty detector list redirects to overview page', () => { + cy.visit(AD_URL.DETECTOR_LIST); + cy.getElementByTestId('sampleDetectorButton').click(); + validatePageElements(); + }); + + it('Side nav AD button redirects to overview page', () => { + cy.visit(AD_URL.DETECTOR_LIST); + + cy.get('.euiSideNav').contains('Anomaly detection').click(); + validatePageElements(); + }); + + context('Flyouts open and close', () => { + it('HTTP responses sample detector', () => { + cy.visit(AD_URL.OVERVIEW); + validatePageElements(); + openAndCloseFlyout(0); + }); + + it('eCommerce sample detector', () => { + cy.visit(AD_URL.OVERVIEW); + validatePageElements(); + openAndCloseFlyout(1); + }); + + it('Host health sample detector', () => { + cy.visit(AD_URL.OVERVIEW); + validatePageElements(); + openAndCloseFlyout(2); + }); + }); +}); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/real_time_results_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/real_time_results_spec.js new file mode 100644 index 000000000..641c24771 --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/real_time_results_spec.js @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AD_URL } from '../../../utils/constants'; + +context('Real-time results page', () => { + // Creating a sample detector and visiting the config page + before(() => { + cy.server(); + cy.visit(AD_URL.OVERVIEW); + cy.getElementByTestId('createHttpSampleDetectorButton').click(); + cy.visit(AD_URL.OVERVIEW); + cy.getElementByTestId('viewSampleDetectorLink').click(); + cy.getElementByTestId('resultsTab').click(); + }); + + // Clean up resources + after(() => { + cy.getElementByTestId('actionsButton').click(); + cy.getElementByTestId('deleteDetectorItem').click(); + cy.getElementByTestId('typeDeleteField').type('delete', { force: true }); + cy.getElementByTestId('confirmButton').click(); + cy.deleteAllIndices(); + }); + + context('Sample detector', () => { + it('Start and stop detector from button', () => { + // Sample detector will default to initializing upon creation + cy.getElementByTestId('detectorStateInitializing').should('exist'); + cy.getElementByTestId('stopAndStartDetectorButton').click(); + cy.getElementByTestId('detectorStateStopped').should('exist'); + cy.getElementByTestId('stopAndStartDetectorButton').click(); + cy.getElementByTestId('detectorStateInitializing').should('exist'); + }); + + it('Renders no anomalies', () => { + cy.getElementByTestId('anomalyOccurrenceTab').click(); + cy.getElementByTestId('noAnomaliesMessage').should('exist'); + cy.getElementByTestId('anomalyOccurrencesHeader').should( + 'contain', + '(0)' + ); + cy.getElementByTestId('featureNameHeader').should('not.exist'); + + cy.getElementByTestId('featureBreakdownTab').click(); + + cy.getElementByTestId('noAnomaliesMessage').should('not.exist'); + cy.getElementByTestId('featureNameHeader').should('exist'); + cy.getElementByTestId('featureNameHeader').should( + 'have.length.be.gte', + 1 + ); + }); + + it('Enter and exit full screen', () => { + cy.contains('View full screen'); + cy.contains('Exit full screen').should('not.be.visible'); + cy.getElementByTestId('anomalyResultsFullScreenButton').click(); + cy.contains('View full screen').should('not.be.visible'); + cy.contains('Exit full screen'); + cy.getElementByTestId('anomalyResultsFullScreenButton').click(); + cy.contains('View full screen'); + cy.contains('Exit full screen').should('not.be.visible'); + }); + }); +}); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/sample_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/sample_detector_spec.js new file mode 100644 index 000000000..2dbb15adc --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/sample_detector_spec.js @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + AD_URL, +} from '../../../utils/constants'; + +context('Sample detectors', () => { + // Helper fn used in many of the below tests. Takes in a button test ID to determine + // the sample detector to create/delete from the overview page + const createAndDeleteSampleDetector = (createButtonDataTestSubj) => { + cy.visit(AD_URL.OVERVIEW); + + cy.getElementByTestId('overviewTitle').should('exist'); + cy.getElementByTestId('viewSampleDetectorLink').should('not.exist'); + cy.getElementByTestId(createButtonDataTestSubj).click(); + cy.visit(AD_URL.OVERVIEW); + + // Check that the details page defaults to real-time, and shows detector is initializing + cy.getElementByTestId('viewSampleDetectorLink').click(); + cy.getElementByTestId('detectorNameHeader').should('exist'); + cy.getElementByTestId('sampleIndexDetailsCallout').should('exist'); + cy.getElementByTestId('realTimeResultsHeader').should('exist'); + cy.getElementByTestId('detectorStateInitializing').should('exist'); + + // Stop the detector so it can be deleted + cy.getElementByTestId('stopAndStartDetectorButton').click(); + cy.getElementByTestId('detectorStateStopped').should('exist'); + + // Visit configuration page to get the info to clean up detector and index + cy.getElementByTestId('configurationsTab').click(); + cy.getElementByTestId('detectorIdCell').within(() => { + cy.get('.euiText--medium') + .invoke('text') + .then((detectorId) => { + cy.log('Deleting detector with ID: ' + detectorId); + cy.deleteDetector(detectorId); + }); + }); + + cy.getElementByTestId('indexNameCell').within(() => { + cy.get('.euiText--medium') + .invoke('text') + .then((indexName) => { + cy.log('Deleting index with name: ' + indexName); + cy.deleteIndex(indexName); + }); + }); + }; + + before(() => { + cy.server(); + }); + + it('HTTP response sample detector - create and delete', () => { + createAndDeleteSampleDetector('createHttpSampleDetectorButton'); + }); + + it('eCommerce sample detector - create and delete', () => { + createAndDeleteSampleDetector('createECommerceSampleDetectorButton'); + }); + + it('Host health sample detector - create and delete', () => { + createAndDeleteSampleDetector('createHostHealthSampleDetectorButton'); + }); +}); diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index df1728677..2480a5798 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -67,7 +67,12 @@ Cypress.Commands.add('login', () => { }); }); +Cypress.Commands.add('getElementByTestId', (testId) => { + return cy.get(`[data-test-subj=${testId}]`); +}); + Cypress.Commands.add('deleteAllIndices', () => { + cy.log('Deleting all indices'); cy.request( 'DELETE', `${Cypress.env('openSearchUrl')}/index*,sample*,opensearch_dashboards*` @@ -98,6 +103,10 @@ Cypress.Commands.add('createIndex', (index, policyID = null, settings = {}) => { } }); +Cypress.Commands.add('deleteIndex', (indexName) => { + cy.request('DELETE', `${Cypress.env('openSearchUrl')}/${indexName}`); +}); + Cypress.Commands.add('createIndexTemplate', (name, template) => { cy.request( 'PUT', diff --git a/cypress/utils/helpers.js b/cypress/utils/helpers.js new file mode 100644 index 000000000..a64e431fa --- /dev/null +++ b/cypress/utils/helpers.js @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './plugins/anomaly-detection-dashboards-plugin/helpers'; diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/commands.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/commands.js index 5e1b1de7f..8e3525c6b 100644 --- a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/commands.js +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/commands.js @@ -4,17 +4,18 @@ */ import { - AD_GET_DETECTORS_NODE_API_PATH, - AD_GET_INDICES_NODE_API_PATH, - AD_GET_MAPPINGS_NODE_API_PATH, + AD_NODE_API_PATH, getADStartDetectorNodeApiPath, getADStopDetectorNodeApiPath, + getADDeleteDetectorNodeApiPath, + getADStopDetectorApiPath, + getADGetDetectorApiPath, } from '../../constants'; Cypress.Commands.add( 'mockGetDetectorOnAction', function (fixtureFileName, funcMockedOn) { - cy.route2(AD_GET_DETECTORS_NODE_API_PATH, { + cy.route2(AD_NODE_API_PATH.GET_DETECTORS, { fixture: fixtureFileName, }).as('getDetectors'); @@ -24,10 +25,28 @@ Cypress.Commands.add( } ); +Cypress.Commands.add( + 'mockGetDetectorsAndIndicesOnAction', + function (detectorsFixtureFileName, indexFixtureFileName, funcMockedOn) { + cy.route2(AD_NODE_API_PATH.GET_DETECTORS, { + fixture: detectorsFixtureFileName, + }).as('getDetectors'); + + cy.route2(AD_NODE_API_PATH.GET_INDICES, { + fixture: indexFixtureFileName, + }).as('getIndices'); + + funcMockedOn(); + + cy.wait('@getDetectors'); + cy.wait('@getIndices'); + } +); + Cypress.Commands.add( 'mockSearchIndexOnAction', function (fixtureFileName, funcMockedOn) { - cy.route2(AD_GET_INDICES_NODE_API_PATH, { + cy.route2(AD_NODE_API_PATH.GET_INDICES, { fixture: fixtureFileName, }).as('getIndices'); @@ -40,7 +59,6 @@ Cypress.Commands.add( Cypress.Commands.add( 'mockStartDetectorOnAction', function (fixtureFileName, detectorId, funcMockedOn) { - cy.server(); cy.route2(getADStartDetectorNodeApiPath(detectorId), { fixture: fixtureFileName, }).as('startDetector'); @@ -54,7 +72,6 @@ Cypress.Commands.add( Cypress.Commands.add( 'mockStopDetectorOnAction', function (fixtureFileName, detectorId, funcMockedOn) { - cy.server(); cy.route2(getADStopDetectorNodeApiPath(detectorId), { fixture: fixtureFileName, }).as('stopDetector'); @@ -66,29 +83,28 @@ Cypress.Commands.add( ); Cypress.Commands.add( - 'mockGetIndexMappingsOnAction', - function (fixtureFileName, funcMockedOn) { - cy.server(); - cy.route2(AD_GET_MAPPINGS_NODE_API_PATH, { + 'mockDeleteDetectorOnAction', + function (fixtureFileName, detectorId, funcMockedOn) { + cy.route2(getADDeleteDetectorNodeApiPath(detectorId), { fixture: fixtureFileName, - }).as('getMappings'); + }).as('deleteDetector'); funcMockedOn(); - cy.wait('@getMappings'); + cy.wait('@deleteDetector'); } ); -Cypress.Commands.add( - 'mockCreateDetectorOnAction', - function (fixtureFileName, funcMockedOn) { - cy.server(); - cy.route2(AD_GET_DETECTORS_NODE_API_PATH, { fixture: fixtureFileName }).as( - 'createDetector' - ); - - funcMockedOn(); - - cy.wait('@createDetector'); - } -); +Cypress.Commands.add('deleteDetector', (detectorId) => { + cy.request( + 'DELETE', + `${Cypress.env('openSearchUrl')}/${getADGetDetectorApiPath(detectorId)}` + ); +}); + +Cypress.Commands.add('stopDetector', (detectorId) => { + cy.request( + 'POST', + `${Cypress.env('openSearchUrl')}/${getADStopDetectorApiPath(detectorId)}` + ); +}); diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/constants.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/constants.js index 9f7014da8..8c23ed905 100644 --- a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/constants.js +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/constants.js @@ -5,43 +5,82 @@ import { BASE_PATH } from '../../base_constants'; -export const AD_FIXTURE_BASE_PATH = - 'plugins/anomaly-detection-dashboards-plugin/'; +/** + ***************************** + URL CONSTANTS + ***************************** + */ + +const BASE_AD_PATH = BASE_PATH + '/app/anomaly-detection-dashboards#'; -export const BASE_AD_PATH = BASE_PATH + '/app/anomaly-detection-dashboards#'; +export const AD_URL = { + OVERVIEW: BASE_AD_PATH + '/overview', + DASHBOARD: BASE_AD_PATH + '/dashboard', + DETECTOR_LIST: BASE_AD_PATH + '/detectors', + CREATE_AD: BASE_AD_PATH + '/create-ad', +}; -export const BASE_AD_DASHBOARDS_PATH = BASE_AD_PATH + '/dashboard'; +/** + ***************************** + PUBLIC API CONSTANTS + ***************************** + */ + +const AD_BASE_API_PATH = '_plugins/_anomaly_detection/detectors'; -export const BASE_AD_DETECTOR_LIST_PATH = BASE_AD_PATH + '/detectors'; +export function getADGetDetectorApiPath(detectorId) { + return AD_BASE_API_PATH + '/' + detectorId; +} -export const BASE_AD_CREATE_AD_PATH = BASE_AD_PATH + '/create-ad'; +export function getADStopDetectorApiPath(detectorId) { + return AD_BASE_API_PATH + '/' + detectorId + '/_stop'; +} -export const BASE_AD_NODE_API_PATH = BASE_PATH + '/api/anomaly_detectors'; +/** + ***************************** + NODE API / SERVER CONSTANTS + ***************************** + */ -export const AD_GET_DETECTORS_NODE_API_PATH = - BASE_AD_NODE_API_PATH + '/detectors*'; +const BASE_AD_NODE_API_PATH = BASE_PATH + '/api/anomaly_detectors'; -export const AD_GET_INDICES_NODE_API_PATH = - BASE_AD_NODE_API_PATH + '/_indices*'; +export const AD_NODE_API_PATH = { + GET_DETECTORS: BASE_AD_NODE_API_PATH + '/detectors*', + GET_INDICES: BASE_AD_NODE_API_PATH + '/_indices*', + GET_MAPPINGS: BASE_AD_NODE_API_PATH + '/_mappings*', + VALIDATE: BASE_AD_NODE_API_PATH + '/detectors/_validate', +}; -export const AD_GET_MAPPINGS_NODE_API_PATH = - BASE_AD_NODE_API_PATH + '/_mappings*'; +function getBaseNodeApiPath(detectorId) { + return BASE_AD_NODE_API_PATH + '/detectors/' + detectorId; +} export function getADStartDetectorNodeApiPath(detectorId) { - return BASE_AD_NODE_API_PATH + '/detectors/' + detectorId + '/start'; + return getBaseNodeApiPath(detectorId) + '/start'; } export function getADStopDetectorNodeApiPath(detectorId) { - return BASE_AD_NODE_API_PATH + '/detectors/' + detectorId + '/stop'; + return getBaseNodeApiPath(detectorId) + '/stop'; } -export const TEST_DETECTOR_ID = 'ulgqpXEBqtadYz9j2MHG'; +export function getADDeleteDetectorNodeApiPath(detectorId) { + return getBaseNodeApiPath(detectorId); +} -// TODO: when repo is onboarded to typescript we can convert these detector states into an enum -export const DETECTOR_STATE_DISABLED = 'Stopped'; +/** + ***************************** + MISC CONSTANTS + ***************************** + */ -export const DETECTOR_STATE_INIT = 'Initializing'; +export const AD_FIXTURE_BASE_PATH = + 'plugins/anomaly-detection-dashboards-plugin/'; -export const DETECTOR_STATE_RUNNING = 'Running'; +export const TEST_DETECTOR_ID = 'ulgqpXEBqtadYz9j2MHG'; -export const DETECTOR_STATE_FEATURE_REQUIRED = 'Feature required'; +export const DETECTOR_STATE = { + DISABLED: 'Stopped', + INIT: 'Initializing', + RUNNING: 'Running', + FEATURE_REQUIRED: 'Feature required', +}; diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js new file mode 100644 index 000000000..c21f98fd8 --- /dev/null +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const selectTopItemFromFilter = ( + dataTestSubjectName, + allowMultipleSelections = true +) => { + cy.getElementByTestId(dataTestSubjectName) + .find('[data-test-subj=comboBoxToggleListButton]') + .click({ force: true }); + cy.get('.euiFilterSelectItem').first().click(); + + // If multiple options can be selected, the combo box doesn't close after selecting an option. + // We manually close in this case, so the unselected items aren't visible on the page. + // This way, we can test whether or not filtering has worked as expected. + if (allowMultipleSelections) { + cy.getElementByTestId(dataTestSubjectName) + .find('[data-test-subj=comboBoxToggleListButton]') + .click(); + } +};