From 0cca956d314c50fde5797c365b3aea3a95e3aa36 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Fri, 26 May 2023 14:59:48 +0200 Subject: [PATCH 01/12] feature anywhere initial tests Signed-off-by: Jovan Cvetkovic --- .../feature-anywhere-sample.json | 10 ++ .../feature-anywhere/index-pattern-fields.txt | 1 + .../index-settings-sample.txt | 1 + .../dashboard_feature_anywhere_spec.js | 139 ++++++++++++++++++ .../dashboards/feature-anywhere/commands.js | 137 +++++++++++++++++ .../dashboards/feature-anywhere/helpers.js | 123 ++++++++++++++++ .../dashboards/feature-anywhere/index.d.ts | 91 ++++++++++++ 7 files changed, 502 insertions(+) create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/feature-anywhere/dashboard_feature_anywhere_spec.js create mode 100644 cypress/utils/dashboards/feature-anywhere/commands.js create mode 100644 cypress/utils/dashboards/feature-anywhere/helpers.js create mode 100644 cypress/utils/dashboards/feature-anywhere/index.d.ts diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json new file mode 100644 index 000000000..434db2bc8 --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json @@ -0,0 +1,10 @@ +[ + { + "name": "John", + "lastname": "Doe" + }, + { + "name": "Jane", + "lastname": "Doe" + } +] \ No newline at end of file diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt new file mode 100644 index 000000000..3102f056d --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/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":"lastname","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"name","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false}] \ No newline at end of file diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt new file mode 100644 index 000000000..ac120bb3d --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt @@ -0,0 +1 @@ +{"mappings":{"properties":{"name":{"type":"text"},"lastname":{"type":"text"},"@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/feature-anywhere/dashboard_feature_anywhere_spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/feature-anywhere/dashboard_feature_anywhere_spec.js new file mode 100644 index 000000000..0a8fed6e9 --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/feature-anywhere/dashboard_feature_anywhere_spec.js @@ -0,0 +1,139 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + cleanTests, + prepareTests, +} from '../../../../utils/dashboards/feature-anywhere/helpers'; + +let indexName = 'feature-anywhere-sample-index'; +const indexPatternName = 'feature-anywhere-sample-*'; +const visualizationName = 'Feature Anywhere Line Chart'; +const dashboardName = 'Feature Anywhere Dashboard'; + +describe('Feature anywhere tests', () => { + before(() => { + cleanTests(indexName, indexPatternName, visualizationName, dashboardName); + prepareTests(indexName, indexPatternName); + }); + + beforeEach(() => { + cy.visit('http://localhost:5601/app/dashboards'); + }); + + after(() => { + cleanTests(indexName, indexPatternName, visualizationName, dashboardName); + }); + + let dashboardId; + + it('Create dashboard', () => { + cy.visit('http://localhost:5601/app/dashboards'); + + cy.get('[data-test-subj="createDashboardPromptButton"]') + .should('be.visible') + .click(); + + cy.get('button').contains('Create new').click(); + + cy.get('[data-test-subj="visType-line"]').click(); + + cy.get('[data-test-subj="savedObjectFinderSearchInput"]').type( + `${indexPatternName}{enter}` + ); + + cy.get(`[title="${indexPatternName}"]`).click(); + + cy.get('.euiTitle') + .contains('Buckets') + .parent() + .find('[data-test-subj="visEditorAdd_buckets"]') + .click(); + cy.get('[data-test-subj="visEditorAdd_buckets_X-axis"]').click(); + + cy.get('.euiTitle') + .contains('Buckets') + .parent() + .within(() => { + cy.wait(1000); + cy.get('[data-test-subj="comboBoxInput"]') + .find('input') + .type('Date Histogram{enter}', { force: true }); + }); + + cy.get('[data-test-subj="visualizeEditorRenderButton"]').click({ + force: true, + }); + cy.get('[data-test-subj="visualizeSaveButton"]').click({ + force: true, + }); + cy.get('[data-test-subj="savedObjectTitle"]').type(visualizationName); + cy.get('[data-test-subj="confirmSaveSavedObjectButton"]').click({ + force: true, + }); + cy.get('[data-test-subj="dashboardSaveMenuItem"]').click({ + force: true, + }); + + cy.get('[data-test-subj="savedObjectTitle"]').type(dashboardName); + + cy.intercept('POST', '/api/saved_objects/dashboard?overwrite=true').as( + 'saveDashboard' + ); + cy.get('[data-test-subj="confirmSaveSavedObjectButton"]') + .click({ + force: true, + }) + .then(() => { + cy.wait('@saveDashboard').then((interceptor) => { + dashboardId = interceptor.response.body.id; + cy.visit(`http://localhost:5601/app/dashboards#/view/${dashboardId}`); + }); + }); + }); + + describe('Validate Dashboard', () => { + beforeEach(() => { + cy.visit(`http://localhost:5601/app/dashboards#/view/${dashboardId}`); + cy.wait(5000); + }); + + it('Visualizations should be visible', () => { + cy.getVisPanelByTitle(visualizationName); + }); + + it('Validate visualization charts', () => { + cy.getVisPanelByTitle(visualizationName).within(($panel) => { + cy.wrap($panel).getLegendNodes().contains('Count'); + + cy.getChart() + .getCircleNodes() + .then(($nodes) => { + cy.wrap($nodes).should('have.length', 1); + cy.wrap($nodes).realHover(); + }); + }); + + cy.get('div[class="visTooltip"]').within(() => { + cy.get('table tr') + .eq(1) + .then(($tr) => { + cy.wrap($tr).within(() => { + cy.get('.visTooltip__label').contains('Count'); + cy.get('.visTooltip__value').contains(2); + }); + }); + }); + }); + + it('Validate visualization events', () => { + cy.getVisPanelByTitle(visualizationName) + .openVisContextMenu() + .clickVisPanelMenuItem('View Events'); + + cy.get('.euiFlyout').find('.euiTitle').contains(visualizationName); + }); + }); +}); diff --git a/cypress/utils/dashboards/feature-anywhere/commands.js b/cypress/utils/dashboards/feature-anywhere/commands.js new file mode 100644 index 000000000..b8ca397ef --- /dev/null +++ b/cypress/utils/dashboards/feature-anywhere/commands.js @@ -0,0 +1,137 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getNodeData } from './helpers'; +import _ from 'lodash'; + +Cypress.Commands.add('getVisPanelByTitle', (title) => + cy.get(`[data-title="${title}"]`).parents('.embPanel').should('be.visible') +); + +Cypress.Commands.add('getChart', () => + cy.get('[class="chart"]').find('svg').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( + 'getTextNodes', + { + prevSubject: true, + }, + (chart) => cy.wrap(chart).find('text') +); + +Cypress.Commands.add( + 'getArcNodes', + { + prevSubject: true, + }, + (chart) => cy.wrap(chart).find('[class="arcs"]').find('path[class="slice"]') +); + +Cypress.Commands.add( + 'getCircleNodes', + { + prevSubject: true, + }, + (chart) => + cy.wrap(chart).find('[class="points line"]').find('circle[class="circle"]') +); + +Cypress.Commands.add( + 'getNodeData', + { + prevSubject: true, + }, + (node) => cy.wrap(getNodeData(node.get(0))) +); + +Cypress.Commands.add( + 'filterNodesBy', + { + prevSubject: true, + }, + ($nodes, filter, value) => { + let nodes = []; + $nodes.map((idx, node) => { + if (getNodeData(node)[filter] === value) nodes.push(node); + }); + return nodes; + } +); + +Cypress.Commands.add('getLabelNodes', { prevSubject: true }, (chart) => { + return cy.wrap(chart).find('[class="labels"]').find('text'); +}); + +Cypress.Commands.add( + 'containsText', + { prevSubject: true }, + (chart, text, exactMatch = false) => { + return cy + .wrap(chart) + .getTextNodes() + .then(($texts) => { + $texts = $texts.filter( + (idx, node) => + (exactMatch && node.firstChild.nodeValue === text) || + _.includes(node.firstChild.nodeValue, text) + ); + + if ($texts.length > 0) { + cy.assert( + true, + `expected to find a node with the text "${text}", multiple nodes found` + ); + } else if ($texts.length === 0) { + cy.assert(true, `expected to find a node with the text "${text}"`); + } else { + cy.fail( + `expected to find a node with the text "${text}" but failed to find it!` + ); + } + + return $texts; + }); + } +); + +Cypress.Commands.add( + 'getLegendNodes', + { + prevSubject: true, + }, + (visPanel) => + cy + .wrap(visPanel) + .find('ul[class="visLegend__list"]') + .find('span[class="visLegend__valueTitle"]') + .then(($legends) => { + cy.assert( + true, + `expected to find legend nodes within the vis panel, found ${$legends.length}` + ); + + return $legends; + }) +); diff --git a/cypress/utils/dashboards/feature-anywhere/helpers.js b/cypress/utils/dashboards/feature-anywhere/helpers.js new file mode 100644 index 000000000..42c5e0e07 --- /dev/null +++ b/cypress/utils/dashboards/feature-anywhere/helpers.js @@ -0,0 +1,123 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const parseAriaLabel = (label) => { + const labels = label.split(';'); + const labelEntries = labels.map((lab) => + lab + .trim() + .split(':') + .map((l) => l.trim()) + ); + return Object.fromEntries(labelEntries); +}; + +export const getNodeData = (node) => node['__data__']; + +export 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, + }); + +export 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, + }); + +export const cleanTests = ( + indexName, + indexPatternName, + visualizationName, + 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, + visualizationName, + dashboardName, + ].indexOf(obj.meta.title) !== -1 + ) { + apiRequest( + `api/saved_objects/${obj.type}/${obj.id}?force=true`, + 'DELETE' + ); + } + }); + }); + }); + }); +}; + +export const prepareTests = (indexName, indexPatternName) => { + cy.fixture( + 'dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt' + ).then((indexSettings) => devToolsRequest(indexName, 'PUT', indexSettings)); + + cy.fixture( + 'dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt' + ).then((fields) => { + apiRequest( + `api/saved_objects/index-pattern/${indexPatternName}`, + 'POST', + JSON.stringify({ + attributes: { + fields: fields, + title: indexPatternName, + timeFieldName: '@timestamp', + }, + }) + ); + }); + + cy.fixture( + 'dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json' + ).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)); + }); + }); +}; diff --git a/cypress/utils/dashboards/feature-anywhere/index.d.ts b/cypress/utils/dashboards/feature-anywhere/index.d.ts new file mode 100644 index 000000000..64bfad0d0 --- /dev/null +++ b/cypress/utils/dashboards/feature-anywhere/index.d.ts @@ -0,0 +1,91 @@ +// type definitions for custom commands like "createDefaultTodos" +/// + +declare namespace Cypress { + interface Chainable { + /** + * Returns all text nodes from the chart + * @example + * cy.get('chart').getTextNodes() + */ + getTextNodes(): Chainable; + + /** + * Returns text node containing the text from the chart and + * validates if the node is found + * @example + * cy.get('chart').containsText("Label") + */ + containsText(text: string, exactMatch?: boolean): Chainable; + + /** + * Returns visualization panel by title + * @example + * cy.getVisPanelByTitle('[Logs] Visitors by OS') + */ + getVisPanelByTitle(title: string): Chainable; + + /** + * Returns visualization chart svg + * @example + * cy.getVisPanelByTitle('[Logs] Visitors by OS').getChart() + */ + getChart(): Chainable; + + /** + * Returns all arc nodes from the chart + * @example + * cy.get('chart').getArcNodes() + */ + getArcNodes(): Chainable; + + /** + * Returns all circle nodes from the chart + * @example + * cy.get('chart').getCircleNodes() + */ + getCircleNodes(): Chainable; + + /** + * Returns all arc nodes from the chart + * @example + * cy.get('chart').getArcNode().filterNodesBy('value', 'some value') + */ + filterNodesBy(filter: string, value: string): Chainable; + + /** + * Returns node data + * @example + * cy.get('chart').getArcNode().filterNodesBy('name', 'rule').getNodeData() + */ + getNodeData(): Chainable; + + /** + * Returns all label nodes from the chart + * @example + * cy.get('chart').getLabelNodes() + */ + getLabelNodes(): Chainable; + + /** + * Returns all label nodes from the chart + * @example + * cy.get('visPanel').getLegendNodes() + */ + getLegendNodes(): 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; + } +} From f4387a4d979cc35a9e3bd55c423f2e4357d98742 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 21 Jun 2023 13:45:57 -0700 Subject: [PATCH 02/12] Add test suite Signed-off-by: Tyler Ohlsen --- .../feature-anywhere-sample.json | 10 -- .../feature-anywhere/index-pattern-fields.txt | 1 - .../index-settings-sample.txt | 1 - .../dashboard_feature_anywhere_spec.js | 139 ------------------ .../dashboards/feature-anywhere/commands.js | 137 ----------------- .../dashboards/feature-anywhere/helpers.js | 123 ---------------- .../dashboards/feature-anywhere/index.d.ts | 91 ------------ 7 files changed, 502 deletions(-) delete mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json delete mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt delete mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt delete mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/feature-anywhere/dashboard_feature_anywhere_spec.js delete mode 100644 cypress/utils/dashboards/feature-anywhere/commands.js delete mode 100644 cypress/utils/dashboards/feature-anywhere/helpers.js delete mode 100644 cypress/utils/dashboards/feature-anywhere/index.d.ts diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json deleted file mode 100644 index 434db2bc8..000000000 --- a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "name": "John", - "lastname": "Doe" - }, - { - "name": "Jane", - "lastname": "Doe" - } -] \ No newline at end of file diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt deleted file mode 100644 index 3102f056d..000000000 --- a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt +++ /dev/null @@ -1 +0,0 @@ -[{"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":"lastname","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"name","type":"string","esTypes":["text"],"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false}] \ No newline at end of file diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt b/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt deleted file mode 100644 index ac120bb3d..000000000 --- a/cypress/fixtures/dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt +++ /dev/null @@ -1 +0,0 @@ -{"mappings":{"properties":{"name":{"type":"text"},"lastname":{"type":"text"},"@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/feature-anywhere/dashboard_feature_anywhere_spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/feature-anywhere/dashboard_feature_anywhere_spec.js deleted file mode 100644 index 0a8fed6e9..000000000 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/feature-anywhere/dashboard_feature_anywhere_spec.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - cleanTests, - prepareTests, -} from '../../../../utils/dashboards/feature-anywhere/helpers'; - -let indexName = 'feature-anywhere-sample-index'; -const indexPatternName = 'feature-anywhere-sample-*'; -const visualizationName = 'Feature Anywhere Line Chart'; -const dashboardName = 'Feature Anywhere Dashboard'; - -describe('Feature anywhere tests', () => { - before(() => { - cleanTests(indexName, indexPatternName, visualizationName, dashboardName); - prepareTests(indexName, indexPatternName); - }); - - beforeEach(() => { - cy.visit('http://localhost:5601/app/dashboards'); - }); - - after(() => { - cleanTests(indexName, indexPatternName, visualizationName, dashboardName); - }); - - let dashboardId; - - it('Create dashboard', () => { - cy.visit('http://localhost:5601/app/dashboards'); - - cy.get('[data-test-subj="createDashboardPromptButton"]') - .should('be.visible') - .click(); - - cy.get('button').contains('Create new').click(); - - cy.get('[data-test-subj="visType-line"]').click(); - - cy.get('[data-test-subj="savedObjectFinderSearchInput"]').type( - `${indexPatternName}{enter}` - ); - - cy.get(`[title="${indexPatternName}"]`).click(); - - cy.get('.euiTitle') - .contains('Buckets') - .parent() - .find('[data-test-subj="visEditorAdd_buckets"]') - .click(); - cy.get('[data-test-subj="visEditorAdd_buckets_X-axis"]').click(); - - cy.get('.euiTitle') - .contains('Buckets') - .parent() - .within(() => { - cy.wait(1000); - cy.get('[data-test-subj="comboBoxInput"]') - .find('input') - .type('Date Histogram{enter}', { force: true }); - }); - - cy.get('[data-test-subj="visualizeEditorRenderButton"]').click({ - force: true, - }); - cy.get('[data-test-subj="visualizeSaveButton"]').click({ - force: true, - }); - cy.get('[data-test-subj="savedObjectTitle"]').type(visualizationName); - cy.get('[data-test-subj="confirmSaveSavedObjectButton"]').click({ - force: true, - }); - cy.get('[data-test-subj="dashboardSaveMenuItem"]').click({ - force: true, - }); - - cy.get('[data-test-subj="savedObjectTitle"]').type(dashboardName); - - cy.intercept('POST', '/api/saved_objects/dashboard?overwrite=true').as( - 'saveDashboard' - ); - cy.get('[data-test-subj="confirmSaveSavedObjectButton"]') - .click({ - force: true, - }) - .then(() => { - cy.wait('@saveDashboard').then((interceptor) => { - dashboardId = interceptor.response.body.id; - cy.visit(`http://localhost:5601/app/dashboards#/view/${dashboardId}`); - }); - }); - }); - - describe('Validate Dashboard', () => { - beforeEach(() => { - cy.visit(`http://localhost:5601/app/dashboards#/view/${dashboardId}`); - cy.wait(5000); - }); - - it('Visualizations should be visible', () => { - cy.getVisPanelByTitle(visualizationName); - }); - - it('Validate visualization charts', () => { - cy.getVisPanelByTitle(visualizationName).within(($panel) => { - cy.wrap($panel).getLegendNodes().contains('Count'); - - cy.getChart() - .getCircleNodes() - .then(($nodes) => { - cy.wrap($nodes).should('have.length', 1); - cy.wrap($nodes).realHover(); - }); - }); - - cy.get('div[class="visTooltip"]').within(() => { - cy.get('table tr') - .eq(1) - .then(($tr) => { - cy.wrap($tr).within(() => { - cy.get('.visTooltip__label').contains('Count'); - cy.get('.visTooltip__value').contains(2); - }); - }); - }); - }); - - it('Validate visualization events', () => { - cy.getVisPanelByTitle(visualizationName) - .openVisContextMenu() - .clickVisPanelMenuItem('View Events'); - - cy.get('.euiFlyout').find('.euiTitle').contains(visualizationName); - }); - }); -}); diff --git a/cypress/utils/dashboards/feature-anywhere/commands.js b/cypress/utils/dashboards/feature-anywhere/commands.js deleted file mode 100644 index b8ca397ef..000000000 --- a/cypress/utils/dashboards/feature-anywhere/commands.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { getNodeData } from './helpers'; -import _ from 'lodash'; - -Cypress.Commands.add('getVisPanelByTitle', (title) => - cy.get(`[data-title="${title}"]`).parents('.embPanel').should('be.visible') -); - -Cypress.Commands.add('getChart', () => - cy.get('[class="chart"]').find('svg').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( - 'getTextNodes', - { - prevSubject: true, - }, - (chart) => cy.wrap(chart).find('text') -); - -Cypress.Commands.add( - 'getArcNodes', - { - prevSubject: true, - }, - (chart) => cy.wrap(chart).find('[class="arcs"]').find('path[class="slice"]') -); - -Cypress.Commands.add( - 'getCircleNodes', - { - prevSubject: true, - }, - (chart) => - cy.wrap(chart).find('[class="points line"]').find('circle[class="circle"]') -); - -Cypress.Commands.add( - 'getNodeData', - { - prevSubject: true, - }, - (node) => cy.wrap(getNodeData(node.get(0))) -); - -Cypress.Commands.add( - 'filterNodesBy', - { - prevSubject: true, - }, - ($nodes, filter, value) => { - let nodes = []; - $nodes.map((idx, node) => { - if (getNodeData(node)[filter] === value) nodes.push(node); - }); - return nodes; - } -); - -Cypress.Commands.add('getLabelNodes', { prevSubject: true }, (chart) => { - return cy.wrap(chart).find('[class="labels"]').find('text'); -}); - -Cypress.Commands.add( - 'containsText', - { prevSubject: true }, - (chart, text, exactMatch = false) => { - return cy - .wrap(chart) - .getTextNodes() - .then(($texts) => { - $texts = $texts.filter( - (idx, node) => - (exactMatch && node.firstChild.nodeValue === text) || - _.includes(node.firstChild.nodeValue, text) - ); - - if ($texts.length > 0) { - cy.assert( - true, - `expected to find a node with the text "${text}", multiple nodes found` - ); - } else if ($texts.length === 0) { - cy.assert(true, `expected to find a node with the text "${text}"`); - } else { - cy.fail( - `expected to find a node with the text "${text}" but failed to find it!` - ); - } - - return $texts; - }); - } -); - -Cypress.Commands.add( - 'getLegendNodes', - { - prevSubject: true, - }, - (visPanel) => - cy - .wrap(visPanel) - .find('ul[class="visLegend__list"]') - .find('span[class="visLegend__valueTitle"]') - .then(($legends) => { - cy.assert( - true, - `expected to find legend nodes within the vis panel, found ${$legends.length}` - ); - - return $legends; - }) -); diff --git a/cypress/utils/dashboards/feature-anywhere/helpers.js b/cypress/utils/dashboards/feature-anywhere/helpers.js deleted file mode 100644 index 42c5e0e07..000000000 --- a/cypress/utils/dashboards/feature-anywhere/helpers.js +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export const parseAriaLabel = (label) => { - const labels = label.split(';'); - const labelEntries = labels.map((lab) => - lab - .trim() - .split(':') - .map((l) => l.trim()) - ); - return Object.fromEntries(labelEntries); -}; - -export const getNodeData = (node) => node['__data__']; - -export 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, - }); - -export 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, - }); - -export const cleanTests = ( - indexName, - indexPatternName, - visualizationName, - 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, - visualizationName, - dashboardName, - ].indexOf(obj.meta.title) !== -1 - ) { - apiRequest( - `api/saved_objects/${obj.type}/${obj.id}?force=true`, - 'DELETE' - ); - } - }); - }); - }); - }); -}; - -export const prepareTests = (indexName, indexPatternName) => { - cy.fixture( - 'dashboard/opensearch_dashboards/feature-anywhere/index-settings-sample.txt' - ).then((indexSettings) => devToolsRequest(indexName, 'PUT', indexSettings)); - - cy.fixture( - 'dashboard/opensearch_dashboards/feature-anywhere/index-pattern-fields.txt' - ).then((fields) => { - apiRequest( - `api/saved_objects/index-pattern/${indexPatternName}`, - 'POST', - JSON.stringify({ - attributes: { - fields: fields, - title: indexPatternName, - timeFieldName: '@timestamp', - }, - }) - ); - }); - - cy.fixture( - 'dashboard/opensearch_dashboards/feature-anywhere/feature-anywhere-sample.json' - ).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)); - }); - }); -}; diff --git a/cypress/utils/dashboards/feature-anywhere/index.d.ts b/cypress/utils/dashboards/feature-anywhere/index.d.ts deleted file mode 100644 index 64bfad0d0..000000000 --- a/cypress/utils/dashboards/feature-anywhere/index.d.ts +++ /dev/null @@ -1,91 +0,0 @@ -// type definitions for custom commands like "createDefaultTodos" -/// - -declare namespace Cypress { - interface Chainable { - /** - * Returns all text nodes from the chart - * @example - * cy.get('chart').getTextNodes() - */ - getTextNodes(): Chainable; - - /** - * Returns text node containing the text from the chart and - * validates if the node is found - * @example - * cy.get('chart').containsText("Label") - */ - containsText(text: string, exactMatch?: boolean): Chainable; - - /** - * Returns visualization panel by title - * @example - * cy.getVisPanelByTitle('[Logs] Visitors by OS') - */ - getVisPanelByTitle(title: string): Chainable; - - /** - * Returns visualization chart svg - * @example - * cy.getVisPanelByTitle('[Logs] Visitors by OS').getChart() - */ - getChart(): Chainable; - - /** - * Returns all arc nodes from the chart - * @example - * cy.get('chart').getArcNodes() - */ - getArcNodes(): Chainable; - - /** - * Returns all circle nodes from the chart - * @example - * cy.get('chart').getCircleNodes() - */ - getCircleNodes(): Chainable; - - /** - * Returns all arc nodes from the chart - * @example - * cy.get('chart').getArcNode().filterNodesBy('value', 'some value') - */ - filterNodesBy(filter: string, value: string): Chainable; - - /** - * Returns node data - * @example - * cy.get('chart').getArcNode().filterNodesBy('name', 'rule').getNodeData() - */ - getNodeData(): Chainable; - - /** - * Returns all label nodes from the chart - * @example - * cy.get('chart').getLabelNodes() - */ - getLabelNodes(): Chainable; - - /** - * Returns all label nodes from the chart - * @example - * cy.get('visPanel').getLegendNodes() - */ - getLegendNodes(): 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; - } -} From b67b3cdd6fd0c567d73437f354dbedda2531959c Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 27 Jun 2023 09:41:51 -0700 Subject: [PATCH 03/12] Add AD vis augmenter tests Signed-off-by: Tyler Ohlsen --- .../apps/vis-augmenter/dashboard_spec.js | 1 - .../sample_detector_spec.js | 20 +-- .../vis_augmenter/associate_detector_spec.js | 130 ++++++++++++++++++ .../dashboards/vis-augmenter/commands.js | 5 +- .../helpers.js | 42 ++++++ 5 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js 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 index 03f547f75..d97647c55 100644 --- 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 @@ -93,7 +93,6 @@ describe('Vis augmenter - existing dashboards work as expected', () => { beforeEach(() => { cy.visitDashboard(dashboardName); - cy.wait(5000); }); after(() => { 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 index 8008eabbf..ebcab7e29 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/sample_detector_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/sample_detector_spec.js @@ -3,27 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AD_URL } from '../../../utils/constants'; +import { createSampleDetector } from '../../../utils/helpers'; context('Sample detectors', () => { - // Helper fn that takes in a button test ID to determine - // the sample detector to create - const createSampleDetector = (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'); - }; - beforeEach(() => { cy.deleteAllIndices(); cy.deleteADSystemIndices(); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js new file mode 100644 index 000000000..b6a0380db --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -0,0 +1,130 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + createSampleDetector, + deleteVisAugmenterData, + bootstrapDashboard, + openAddAnomalyDetectorFlyout, + openAssociatedDetectorsFlyout, +} from '../../../../utils/helpers'; +import { + INDEX_PATTERN_FILEPATH_SIMPLE, + INDEX_SETTINGS_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, +} from '../../../../utils/constants'; + +describe('Associate a detector to a visualization', () => { + const indexName = 'ad-vis-augmenter-sample-index'; + const indexPatternName = 'ad-vis-augmenter-sample-*'; + const dashboardName = 'AD Vis Augmenter Dashboard'; + const detectorName = 'sample-http-responses-detector'; + const visualizationName = 'single-metric-vis'; + const visualizationSpec = { + name: visualizationName, + type: 'line', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Average', + field: 'value1', + }, + ], + }; + + before(() => { + // Create a dashboard and add some visualizations + //cy.deleteADSystemIndices(); + cy.wait(5000); + bootstrapDashboard( + INDEX_SETTINGS_FILEPATH_SIMPLE, + INDEX_PATTERN_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, + indexName, + indexPatternName, + dashboardName, + [visualizationSpec] + ); + // cy.deleteAllIndices(); + }); + + after(() => { + deleteVisAugmenterData( + indexName, + indexPatternName, + [visualizationName], + dashboardName + ); + // cy.deleteAllIndices(); + cy.deleteADSystemIndices(); + }); + + beforeEach(() => {}); + + afterEach(() => {}); + + it('Create new detector from visualization', () => { + openAddAnomalyDetectorFlyout(dashboardName, visualizationName); + cy.getElementByTestId('adAnywhereCreateDetectorButton').click(); + cy.wait(5000); + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + + // since anomaly detectors default to including the visualization name in the + // created detector name, we can filter search by this to ensure it exists + cy.get('.euiFieldSearch').type(visualizationName); + cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); + + cy.getElementByTestId('unlinkButton').click(); + cy.getElementByTestId('confirmUnlinkButton').click(); + }); + + it('Associate existing detector - creation flow', () => { + // createSampleDetector('createHttpSampleDetectorButton'); + openAddAnomalyDetectorFlyout(dashboardName, visualizationName); + + cy.get('.euiFlyout').find('.euiTitle').contains('Add anomaly detector'); + // ensuring the flyout is defaulting to detector creation vs. association + cy.getElementByTestId('adAnywhereCreateDetectorButton'); + cy.get('[id="add-anomaly-detector__existing"]').click(); + cy.wait(2000); + cy.getElementByTestId('comboBoxInput').type(`{downArrow}{enter}`); + cy.wait(2000); + cy.getElementByTestId('adAnywhereAssociateDetectorButton').click(); + cy.wait(5000); + + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + + cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); + cy.getElementByTestId('unlinkButton').click(); + cy.getElementByTestId('confirmUnlinkButton').click(); + + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.getElementByTestId('emptyAssociatedDetectorFlyoutMessage'); + }); + + it('Associate existing detector - associated detectors flow', () => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + + cy.getElementByTestId('associateDetectorButton').click(); + cy.wait(2000); + cy.getElementByTestId('comboBoxInput').type(`{downArrow}{enter}`); + cy.wait(2000); + cy.getElementByTestId('adAnywhereAssociateDetectorButton').click(); + cy.wait(5000); + + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); + + cy.getElementByTestId('unlinkButton').click(); + cy.getElementByTestId('confirmUnlinkButton').click(); + + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.getElementByTestId('emptyAssociatedDetectorFlyoutMessage'); + }); + + it.skip('View associated detector in table', () => { + // TODO: add helper fns to view associated table + }); +}); diff --git a/cypress/utils/dashboards/vis-augmenter/commands.js b/cypress/utils/dashboards/vis-augmenter/commands.js index 18b18767b..0e128e96f 100644 --- a/cypress/utils/dashboards/vis-augmenter/commands.js +++ b/cypress/utils/dashboards/vis-augmenter/commands.js @@ -26,7 +26,6 @@ Cypress.Commands.add( .find('button') .contains(text) .click() - .then(() => cy.get('.euiContextMenu')) ); Cypress.Commands.add('getMenuItems', { prevSubject: 'optional' }, (menu) => @@ -35,9 +34,11 @@ Cypress.Commands.add('getMenuItems', { prevSubject: 'optional' }, (menu) => Cypress.Commands.add('visitDashboard', (dashboardName) => { cy.visit(`${BASE_PATH}/app/dashboards`); + cy.wait(2000); cy.get('.euiFieldSearch').type(dashboardName); - cy.wait(1000); + cy.wait(2000); cy.get('[data-test-subj="itemsInMemTable"]').contains(dashboardName).click({ force: true, }); + cy.wait(5000); }); diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js index 8388339d9..f037479be 100644 --- a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { AD_URL } from './constants'; + export const selectTopItemFromFilter = ( dataTestSubjectName, allowMultipleSelections = true @@ -21,3 +23,43 @@ export const selectTopItemFromFilter = ( .click(); } }; + +export const createSampleDetector = (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'); +}; + +const openAnomalyDetectionPanel = (dashboardName, visualizationName) => { + cy.visitDashboard(dashboardName); + cy.getVisPanelByTitle(visualizationName) + .openVisContextMenu() + .clickVisPanelMenuItem('Anomaly Detection'); +}; + +export const openAddAnomalyDetectorFlyout = ( + dashboardName, + visualizationName +) => { + openAnomalyDetectionPanel(dashboardName, visualizationName); + cy.clickVisPanelMenuItem('Add anomaly detector'); + cy.wait(5000); +}; + +export const openAssociatedDetectorsFlyout = ( + dashboardName, + visualizationName +) => { + openAnomalyDetectionPanel(dashboardName, visualizationName); + cy.clickVisPanelMenuItem('Associated detectors'); +}; From d1869d24d85714700ff3d088f7a22373e62edc87 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 27 Jun 2023 12:25:23 -0700 Subject: [PATCH 04/12] More refactoring Signed-off-by: Tyler Ohlsen --- .../vis_augmenter/associate_detector_spec.js | 52 +++++-------------- .../helpers.js | 48 +++++++++++++++++ 2 files changed, 62 insertions(+), 38 deletions(-) diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js index b6a0380db..fd438b49b 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -9,6 +9,10 @@ import { bootstrapDashboard, openAddAnomalyDetectorFlyout, openAssociatedDetectorsFlyout, + createDetectorFromVis, + associateDetectorFromVis, + unlinkDetectorFromVis, + ensureDetectorIsLinked, } from '../../../../utils/helpers'; import { INDEX_PATTERN_FILEPATH_SIMPLE, @@ -20,7 +24,7 @@ describe('Associate a detector to a visualization', () => { const indexName = 'ad-vis-augmenter-sample-index'; const indexPatternName = 'ad-vis-augmenter-sample-*'; const dashboardName = 'AD Vis Augmenter Dashboard'; - const detectorName = 'sample-http-responses-detector'; + const detectorName = 'ad-vis-augmenter-detector'; const visualizationName = 'single-metric-vis'; const visualizationSpec = { name: visualizationName, @@ -67,61 +71,33 @@ describe('Associate a detector to a visualization', () => { it('Create new detector from visualization', () => { openAddAnomalyDetectorFlyout(dashboardName, visualizationName); - cy.getElementByTestId('adAnywhereCreateDetectorButton').click(); - cy.wait(5000); - openAssociatedDetectorsFlyout(dashboardName, visualizationName); + createDetectorFromVis(detectorName); - // since anomaly detectors default to including the visualization name in the - // created detector name, we can filter search by this to ensure it exists - cy.get('.euiFieldSearch').type(visualizationName); - cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); - - cy.getElementByTestId('unlinkButton').click(); - cy.getElementByTestId('confirmUnlinkButton').click(); + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); }); it('Associate existing detector - creation flow', () => { - // createSampleDetector('createHttpSampleDetectorButton'); openAddAnomalyDetectorFlyout(dashboardName, visualizationName); cy.get('.euiFlyout').find('.euiTitle').contains('Add anomaly detector'); // ensuring the flyout is defaulting to detector creation vs. association cy.getElementByTestId('adAnywhereCreateDetectorButton'); cy.get('[id="add-anomaly-detector__existing"]').click(); - cy.wait(2000); - cy.getElementByTestId('comboBoxInput').type(`{downArrow}{enter}`); - cy.wait(2000); - cy.getElementByTestId('adAnywhereAssociateDetectorButton').click(); - cy.wait(5000); - openAssociatedDetectorsFlyout(dashboardName, visualizationName); - - cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); - cy.getElementByTestId('unlinkButton').click(); - cy.getElementByTestId('confirmUnlinkButton').click(); + associateDetectorFromVis(detectorName); - openAssociatedDetectorsFlyout(dashboardName, visualizationName); - cy.getElementByTestId('emptyAssociatedDetectorFlyoutMessage'); + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); }); it('Associate existing detector - associated detectors flow', () => { openAssociatedDetectorsFlyout(dashboardName, visualizationName); - cy.getElementByTestId('associateDetectorButton').click(); - cy.wait(2000); - cy.getElementByTestId('comboBoxInput').type(`{downArrow}{enter}`); - cy.wait(2000); - cy.getElementByTestId('adAnywhereAssociateDetectorButton').click(); - cy.wait(5000); - - openAssociatedDetectorsFlyout(dashboardName, visualizationName); - cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); - - cy.getElementByTestId('unlinkButton').click(); - cy.getElementByTestId('confirmUnlinkButton').click(); + associateDetectorFromVis(detectorName); - openAssociatedDetectorsFlyout(dashboardName, visualizationName); - cy.getElementByTestId('emptyAssociatedDetectorFlyoutMessage'); + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); }); it.skip('View associated detector in table', () => { diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js index f037479be..4dfbe337c 100644 --- a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js @@ -63,3 +63,51 @@ export const openAssociatedDetectorsFlyout = ( openAnomalyDetectionPanel(dashboardName, visualizationName); cy.clickVisPanelMenuItem('Associated detectors'); }; + +// expected context: on create detector flyout +export const createDetectorFromVis = (detectorName) => { + cy.get('[id="detectorDetailsAccordion"]') + .parent() + .find('[data-test-subj="accordionTitleButton"]') + .click(); + cy.getElementByTestId('detectorNameTextInputFlyout').clear(); + cy.getElementByTestId('detectorNameTextInputFlyout').type(detectorName); + cy.getElementByTestId('adAnywhereCreateDetectorButton').click(); + cy.wait(5000); +}; + +// expected context: on associate detector flyout +export const associateDetectorFromVis = (detectorName) => { + cy.wait(2000); + cy.getElementByTestId('comboBoxInput').type( + `${detectorName}{downArrow}{enter}` + ); + cy.wait(2000); + cy.getElementByTestId('adAnywhereAssociateDetectorButton').click(); + cy.wait(5000); +}; + +export const ensureDetectorIsLinked = ( + dashboardName, + visualizationName, + detectorName +) => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.wait(1000); + cy.get('.euiFieldSearch').type(detectorName); + cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); +}; + +export const unlinkDetectorFromVis = ( + dashboardName, + visualizationName, + detectorName +) => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.wait(1000); + cy.get('.euiFieldSearch').type(detectorName); + cy.wait(1000); + cy.getElementByTestId('unlinkButton').click(); + cy.getElementByTestId('confirmUnlinkButton').click(); + cy.wait(5000); +}; From cffe0b4379c3ad172f50e3d772b2ccbcc67f46c8 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 27 Jun 2023 14:23:58 -0700 Subject: [PATCH 05/12] More tests Signed-off-by: Tyler Ohlsen --- .../vis_augmenter/associate_detector_spec.js | 34 +++++++++++++++++-- .../helpers.js | 18 ++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js index fd438b49b..827ad2ee6 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -13,6 +13,8 @@ import { associateDetectorFromVis, unlinkDetectorFromVis, ensureDetectorIsLinked, + ensureDetectorDetails, + openDetectorDetailsPageFromFlyout, } from '../../../../utils/helpers'; import { INDEX_PATTERN_FILEPATH_SIMPLE, @@ -20,7 +22,7 @@ import { SAMPLE_DATA_FILEPATH_SIMPLE, } from '../../../../utils/constants'; -describe('Associate a detector to a visualization', () => { +describe('Anomaly detection integration with vis augmenter', () => { const indexName = 'ad-vis-augmenter-sample-index'; const indexPatternName = 'ad-vis-augmenter-sample-*'; const dashboardName = 'AD Vis Augmenter Dashboard'; @@ -69,11 +71,21 @@ describe('Associate a detector to a visualization', () => { afterEach(() => {}); + it('Shows empty state when no associated detectors', () => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.getElementByTestId('emptyAssociatedDetectorFlyoutMessage'); + }); + it('Create new detector from visualization', () => { openAddAnomalyDetectorFlyout(dashboardName, visualizationName); createDetectorFromVis(detectorName); ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + + // Since this detector is created based off of vis metrics, we assume here + // the number of features will equal the number of metrics we have specified. + ensureDetectorDetails(detectorName, visualizationSpec.metrics.length); + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); }); @@ -100,7 +112,23 @@ describe('Associate a detector to a visualization', () => { unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); }); - it.skip('View associated detector in table', () => { - // TODO: add helper fns to view associated table + it('Deleting linked detector shows error once and removes from associated detectors list', () => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.getElementByTestId('associateDetectorButton').click(); + associateDetectorFromVis(detectorName); + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + openDetectorDetailsPageFromFlyout(); + cy.getElementByTestId('configurationsTab').click(); + cy.getElementByTestId('detectorNameHeader').within(() => { + cy.contains(detectorName); + }); + + cy.getElementByTestId('actionsButton').click(); + cy.getElementByTestId('deleteDetectorItem').click(); + cy.getElementByTestId('typeDeleteField').type('delete', { force: true }); + cy.getElementByTestId('confirmButton').click(); + cy.wait(5000); + + // TODO: reload dashboard and make sure the error shows up, and the associated detectors list is empty) }); }); diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js index 4dfbe337c..0983a9ac7 100644 --- a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js @@ -47,6 +47,10 @@ const openAnomalyDetectionPanel = (dashboardName, visualizationName) => { .clickVisPanelMenuItem('Anomaly Detection'); }; +export const openDetectorDetailsPageFromFlyout = () => { + cy.get('.euiBasicTable').find('.euiLink').click(); +}; + export const openAddAnomalyDetectorFlyout = ( dashboardName, visualizationName @@ -98,6 +102,20 @@ export const ensureDetectorIsLinked = ( cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); }; +export const ensureDetectorDetails = (detectorName, numFeatures) => { + openDetectorDetailsPageFromFlyout(); + cy.getElementByTestId('detectorNameHeader').within(() => { + cy.contains(detectorName); + }); + cy.getElementByTestId('resultsTab'); + cy.getElementByTestId('realTimeResultsHeader'); + cy.getElementByTestId('configurationsTab').click(); + cy.getElementByTestId('detectorSettingsHeader'); + cy.getElementByTestId('featureTable') + .find('.euiTableRow') + .should('have.length', numFeatures); +}; + export const unlinkDetectorFromVis = ( dashboardName, visualizationName, From c0790caecbceacfc5104dc3c1875c1965ed811c9 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 27 Jun 2023 15:34:14 -0700 Subject: [PATCH 06/12] Add test for AD cleanup scenario Signed-off-by: Tyler Ohlsen --- .../vis_augmenter/associate_detector_spec.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js index 827ad2ee6..fd63e6c1a 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -129,6 +129,21 @@ describe('Anomaly detection integration with vis augmenter', () => { cy.getElementByTestId('confirmButton').click(); cy.wait(5000); - // TODO: reload dashboard and make sure the error shows up, and the associated detectors list is empty) + cy.visitDashboard(dashboardName); + + // Expect an error message to show up + cy.getElementByTestId('errorToastMessage').parent().find('button').click(); + cy.get('.euiModal'); + cy.get('.euiModalFooter').find('button').click(); + cy.wait(2000); + + // Expect associated detector list to be empty (the association should be removed) + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.getElementByTestId('emptyAssociatedDetectorFlyoutMessage'); + cy.wait(2000); + + // Reload the dashboard - error toast shouldn't show anymore + cy.visitDashboard(dashboardName); + cy.getElementByTestId('errorToastMessage').should('not.exist'); }); }); From af6db743665ab4c9a3d9653e8c93c4296c4aa399 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Thu, 29 Jun 2023 10:30:14 -0700 Subject: [PATCH 07/12] Set up saved obj test suite Signed-off-by: Tyler Ohlsen --- .../vis_augmenter/associate_detector_spec.js | 4 - .../augment_vis_saved_object_spec.js | 83 +++++++++++++++++++ .../helpers.js | 4 +- 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js index fd63e6c1a..7a9c8ee4d 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -4,7 +4,6 @@ */ import { - createSampleDetector, deleteVisAugmenterData, bootstrapDashboard, openAddAnomalyDetectorFlyout, @@ -42,7 +41,6 @@ describe('Anomaly detection integration with vis augmenter', () => { before(() => { // Create a dashboard and add some visualizations - //cy.deleteADSystemIndices(); cy.wait(5000); bootstrapDashboard( INDEX_SETTINGS_FILEPATH_SIMPLE, @@ -53,7 +51,6 @@ describe('Anomaly detection integration with vis augmenter', () => { dashboardName, [visualizationSpec] ); - // cy.deleteAllIndices(); }); after(() => { @@ -63,7 +60,6 @@ describe('Anomaly detection integration with vis augmenter', () => { [visualizationName], dashboardName ); - // cy.deleteAllIndices(); cy.deleteADSystemIndices(); }); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js new file mode 100644 index 000000000..446e409b5 --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js @@ -0,0 +1,83 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + deleteVisAugmenterData, + bootstrapDashboard, + openAddAnomalyDetectorFlyout, + createDetectorFromVis, + unlinkDetectorFromVis, + ensureDetectorIsLinked, +} from '../../../../utils/helpers'; +import { + INDEX_PATTERN_FILEPATH_SIMPLE, + INDEX_SETTINGS_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, +} from '../../../../utils/constants'; + +describe('AD augment-vis saved objects', () => { + const indexName = 'ad-vis-augmenter-sample-index'; + const indexPatternName = 'ad-vis-augmenter-sample-*'; + const dashboardName = 'AD Vis Augmenter Dashboard'; + const detectorName = 'ad-vis-augmenter-detector'; + const visualizationName = 'single-metric-vis'; + const visualizationSpec = { + name: visualizationName, + type: 'line', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Average', + field: 'value1', + }, + ], + }; + + before(() => { + // Create a dashboard and add some visualizations + cy.wait(5000); + bootstrapDashboard( + INDEX_SETTINGS_FILEPATH_SIMPLE, + INDEX_PATTERN_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, + indexName, + indexPatternName, + dashboardName, + [visualizationSpec] + ); + }); + + after(() => { + deleteVisAugmenterData( + indexName, + indexPatternName, + [visualizationName], + dashboardName + ); + cy.deleteADSystemIndices(); + }); + + beforeEach(() => {}); + + afterEach(() => {}); + + it('Associating a detector creates a visible saved object', () => { + openAddAnomalyDetectorFlyout(dashboardName, visualizationName); + createDetectorFromVis(detectorName); + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + + // TODO: navigate to saved obj management page and make sure it is there + + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); + + // TODO: navigate to saved obj management page and make sure it is gone + }); + + // TODO: other tests to add: + // - unlinking detector will delete the saved obj + // - delete the saved obj will remove the association + // - ensure some details about the saved obj + // - ensure saved obj count is as expected (0, 1, 2+) +}); diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js index 0983a9ac7..144d71bb9 100644 --- a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js @@ -97,7 +97,7 @@ export const ensureDetectorIsLinked = ( detectorName ) => { openAssociatedDetectorsFlyout(dashboardName, visualizationName); - cy.wait(1000); + cy.wait(2000); cy.get('.euiFieldSearch').type(detectorName); cy.get('.euiBasicTable').find('.euiTableRow').should('have.length', 1); }; @@ -122,7 +122,7 @@ export const unlinkDetectorFromVis = ( detectorName ) => { openAssociatedDetectorsFlyout(dashboardName, visualizationName); - cy.wait(1000); + cy.wait(2000); cy.get('.euiFieldSearch').type(detectorName); cy.wait(1000); cy.getElementByTestId('unlinkButton').click(); From 98d4f4a928df7431ad6554cd2ea713a7beab2648 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Thu, 29 Jun 2023 10:52:18 -0700 Subject: [PATCH 08/12] Add reminder TODO Signed-off-by: Tyler Ohlsen --- .../vis_augmenter/associate_detector_spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js index 7a9c8ee4d..756933ba3 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -142,4 +142,7 @@ describe('Anomaly detection integration with vis augmenter', () => { cy.visitDashboard(dashboardName); cy.getElementByTestId('errorToastMessage').should('not.exist'); }); + + // TODO: add test such that events dont show up in vis edit flow. May be part of core test suite, + // but would need to ensure plugin is installed and any mock saved objs are valid... }); From c3af282180cea82c74b20dc0d5400bc43a991987 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 5 Jul 2023 16:22:06 -0700 Subject: [PATCH 09/12] Add tests regarding saved obj visibility Signed-off-by: Tyler Ohlsen --- .../augment_vis_saved_object_spec.js | 41 ++++++++++++++++--- .../dashboards/vis-augmenter/commands.js | 5 +++ .../utils/dashboards/vis-augmenter/helpers.js | 9 ++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js index 446e409b5..fb5a2a59c 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js @@ -10,6 +10,7 @@ import { createDetectorFromVis, unlinkDetectorFromVis, ensureDetectorIsLinked, + filterByObjectType, } from '../../../../utils/helpers'; import { INDEX_PATTERN_FILEPATH_SIMPLE, @@ -68,16 +69,46 @@ describe('AD augment-vis saved objects', () => { createDetectorFromVis(detectorName); ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); - // TODO: navigate to saved obj management page and make sure it is there + cy.visitSavedObjectsManagement(); + filterByObjectType('augment-vis'); + cy.getElementByTestId('savedObjectsTable') + .find('.euiTableRow') + .should('have.length', 1); + }); + + it('Created AD saved object has correct fields', () => { + cy.visitSavedObjectsManagement(); + filterByObjectType('augment-vis'); + // TODO: check some of the fields, specifically that AD-specific values + // are there + }); + it('Removing an associated deletes the saved object', () => { unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); - // TODO: navigate to saved obj management page and make sure it is gone + cy.visitSavedObjectsManagement(); + filterByObjectType('augment-vis'); + cy.getElementByTestId('savedObjectsTable') + .find('.euiTableRow') + .contains('No items found'); + }); + + it('Deleting the visualization from the edit view deletes the saved object', () => { + cy.visitSavedObjectsManagement(); + filterByObjectType('visualization'); + cy.getElementByTestId('savedObjectsTableAction-inspect').click(); + cy.getElementByTestId('savedObjectEditDelete').click(); + cy.getElementByTestId('confirmModalConfirmButton').click(); + cy.wait(3000); + + filterByObjectType('augment-vis'); + cy.getElementByTestId('savedObjectsTable') + .find('.euiTableRow') + .contains('No items found'); }); // TODO: other tests to add: - // - unlinking detector will delete the saved obj - // - delete the saved obj will remove the association // - ensure some details about the saved obj - // - ensure saved obj count is as expected (0, 1, 2+) + // - view events test suite. at least test the resource names show up to start + // - view events check: make sure action doesn't exist if there is no resources/vislayers present }); diff --git a/cypress/utils/dashboards/vis-augmenter/commands.js b/cypress/utils/dashboards/vis-augmenter/commands.js index 0e128e96f..6aea9deb7 100644 --- a/cypress/utils/dashboards/vis-augmenter/commands.js +++ b/cypress/utils/dashboards/vis-augmenter/commands.js @@ -42,3 +42,8 @@ Cypress.Commands.add('visitDashboard', (dashboardName) => { }); cy.wait(5000); }); + +Cypress.Commands.add('visitSavedObjectsManagement', () => { + cy.visit(`${BASE_PATH}/app/management/opensearch-dashboards/objects`); + cy.wait(5000); +}); diff --git a/cypress/utils/dashboards/vis-augmenter/helpers.js b/cypress/utils/dashboards/vis-augmenter/helpers.js index e8a555e92..73ebbf12e 100644 --- a/cypress/utils/dashboards/vis-augmenter/helpers.js +++ b/cypress/utils/dashboards/vis-augmenter/helpers.js @@ -314,3 +314,12 @@ export const bootstrapDashboard = ( force: true, }); }; + +export const filterByObjectType = (type) => { + cy.get('.euiFilterButton').click(); + cy.get('.euiFilterSelect__items') + .find('button') + .contains(type) + .click({ force: true }); + cy.wait(3000); +}; From f907258468bf828880ceb7ce486809bb4b4fc7ca Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 5 Jul 2023 18:38:34 -0700 Subject: [PATCH 10/12] Add view events tests Signed-off-by: Tyler Ohlsen --- .../augment_vis_saved_object_spec.js | 19 ++- .../vis_augmenter/view_anomaly_events_spec.js | 116 ++++++++++++++++++ .../helpers.js | 8 ++ 3 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js index fb5a2a59c..39ad5a820 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { CommonUI } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; import { deleteVisAugmenterData, bootstrapDashboard, @@ -19,6 +20,7 @@ import { } from '../../../../utils/constants'; describe('AD augment-vis saved objects', () => { + const commonUI = new CommonUI(cy); const indexName = 'ad-vis-augmenter-sample-index'; const indexPatternName = 'ad-vis-augmenter-sample-*'; const dashboardName = 'AD Vis Augmenter Dashboard'; @@ -81,9 +83,19 @@ describe('AD augment-vis saved objects', () => { filterByObjectType('augment-vis'); // TODO: check some of the fields, specifically that AD-specific values // are there + cy.getElementByTestId('savedObjectsTableAction-inspect').click(); + cy.contains('originPlugin'); + commonUI.checkElementExists('[value="anomalyDetectionDashboards"]', 1); + cy.contains('pluginResource.type'); + commonUI.checkElementExists('[value="Anomaly Detectors"]', 1); + cy.contains('pluginResource.id'); + cy.contains('visLayerExpressionFn.type'); + commonUI.checkElementExists('[value="PointInTimeEvents"]', 1); + cy.contains('visLayerExpressionFn.name'); + commonUI.checkElementExists('[value="overlay_anomalies"]', 1); }); - it('Removing an associated deletes the saved object', () => { + it('Removing an association deletes the saved object', () => { unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); cy.visitSavedObjectsManagement(); @@ -106,9 +118,4 @@ describe('AD augment-vis saved objects', () => { .find('.euiTableRow') .contains('No items found'); }); - - // TODO: other tests to add: - // - ensure some details about the saved obj - // - view events test suite. at least test the resource names show up to start - // - view events check: make sure action doesn't exist if there is no resources/vislayers present }); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js new file mode 100644 index 000000000..575a8cb99 --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js @@ -0,0 +1,116 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CommonUI } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; + +import { + deleteVisAugmenterData, + bootstrapDashboard, + openAddAnomalyDetectorFlyout, + createDetectorFromVis, + unlinkDetectorFromVis, + ensureDetectorIsLinked, + filterByObjectType, + openViewEventsFlyout, +} from '../../../../utils/helpers'; +import { + INDEX_PATTERN_FILEPATH_SIMPLE, + INDEX_SETTINGS_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, +} from '../../../../utils/constants'; + +describe('View anomaly events in flyout', () => { + const commonUI = new CommonUI(cy); + const indexName = 'ad-vis-augmenter-sample-index'; + const indexPatternName = 'ad-vis-augmenter-sample-*'; + const dashboardName = 'AD Vis Augmenter Dashboard'; + const detectorName = 'ad-vis-augmenter-detector'; + const visualizationName = 'single-metric-vis'; + const visualizationSpec = { + name: visualizationName, + type: 'line', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Average', + field: 'value1', + }, + ], + }; + + before(() => { + // Create a dashboard and add some visualizations + cy.wait(5000); + bootstrapDashboard( + INDEX_SETTINGS_FILEPATH_SIMPLE, + INDEX_PATTERN_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, + indexName, + indexPatternName, + dashboardName, + [visualizationSpec] + ); + }); + + after(() => { + deleteVisAugmenterData( + indexName, + indexPatternName, + [visualizationName], + dashboardName + ); + cy.deleteADSystemIndices(); + }); + + beforeEach(() => {}); + + afterEach(() => {}); + + it('Action does not exist if there are no VisLayers for a visualization', () => { + cy.getVisPanelByTitle(visualizationName) + .openVisContextMenu() + .getMenuItems() + .contains('View Events') + .should('not.exist'); + }); + + it('Action does exist if there are VisLayers for a visualization', () => { + openAddAnomalyDetectorFlyout(dashboardName, visualizationName); + createDetectorFromVis(detectorName); + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + + cy.visitDashboard(dashboardName); + cy.getVisPanelByTitle(visualizationName) + .openVisContextMenu() + .getMenuItems() + .contains('View Events') + .should('exist'); + }); + + it('Basic components show up in flyout', () => { + openViewEventsFlyout(dashboardName, visualizationName); + cy.get('.euiFlyoutHeader').contains(visualizationName); + cy.getElementByTestId('baseVis'); + cy.getElementByTestId('eventVis'); + cy.getElementByTestId('timelineVis'); + cy.getElementByTestId('pluginResourceDescription'); + // cy.contains(detectorName); + cy.getElementByTestId('pluginResourceDescription').within(() => { + cy.contains(detectorName); + cy.get('.euiLink'); + cy.get(`[target="_blank"]`); + }); + }); + + it('Removing all VisLayers hides the view events action again', () => { + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); + cy.visitDashboard(dashboardName); + cy.getVisPanelByTitle(visualizationName) + .openVisContextMenu() + .getMenuItems() + .contains('View Events') + .should('not.exist'); + }); +}); diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js index 144d71bb9..96bda3d93 100644 --- a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js @@ -68,6 +68,14 @@ export const openAssociatedDetectorsFlyout = ( cy.clickVisPanelMenuItem('Associated detectors'); }; +export const openViewEventsFlyout = (dashboardName, visualizationName) => { + cy.visitDashboard(dashboardName); + cy.getVisPanelByTitle(visualizationName) + .openVisContextMenu() + .clickVisPanelMenuItem('View Events'); + cy.wait(5000); +}; + // expected context: on create detector flyout export const createDetectorFromVis = (detectorName) => { cy.get('[id="detectorDetailsAccordion"]') From 706f7252c3fdc9fd5e5c1dea20668474137e4c8a Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 5 Jul 2023 18:45:49 -0700 Subject: [PATCH 11/12] cleanup Signed-off-by: Tyler Ohlsen --- .../vis_augmenter/associate_detector_spec.js | 3 --- .../vis_augmenter/augment_vis_saved_object_spec.js | 2 -- .../vis_augmenter/view_anomaly_events_spec.js | 5 ----- 3 files changed, 10 deletions(-) diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js index 756933ba3..7a9c8ee4d 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -142,7 +142,4 @@ describe('Anomaly detection integration with vis augmenter', () => { cy.visitDashboard(dashboardName); cy.getElementByTestId('errorToastMessage').should('not.exist'); }); - - // TODO: add test such that events dont show up in vis edit flow. May be part of core test suite, - // but would need to ensure plugin is installed and any mock saved objs are valid... }); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js index 39ad5a820..6ad1b4302 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js @@ -81,8 +81,6 @@ describe('AD augment-vis saved objects', () => { it('Created AD saved object has correct fields', () => { cy.visitSavedObjectsManagement(); filterByObjectType('augment-vis'); - // TODO: check some of the fields, specifically that AD-specific values - // are there cy.getElementByTestId('savedObjectsTableAction-inspect').click(); cy.contains('originPlugin'); commonUI.checkElementExists('[value="anomalyDetectionDashboards"]', 1); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js index 575a8cb99..40583a39f 100644 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { CommonUI } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; - import { deleteVisAugmenterData, bootstrapDashboard, @@ -12,7 +10,6 @@ import { createDetectorFromVis, unlinkDetectorFromVis, ensureDetectorIsLinked, - filterByObjectType, openViewEventsFlyout, } from '../../../../utils/helpers'; import { @@ -22,7 +19,6 @@ import { } from '../../../../utils/constants'; describe('View anomaly events in flyout', () => { - const commonUI = new CommonUI(cy); const indexName = 'ad-vis-augmenter-sample-index'; const indexPatternName = 'ad-vis-augmenter-sample-*'; const dashboardName = 'AD Vis Augmenter Dashboard'; @@ -96,7 +92,6 @@ describe('View anomaly events in flyout', () => { cy.getElementByTestId('eventVis'); cy.getElementByTestId('timelineVis'); cy.getElementByTestId('pluginResourceDescription'); - // cy.contains(detectorName); cy.getElementByTestId('pluginResourceDescription').within(() => { cy.contains(detectorName); cy.get('.euiLink'); From afa787bea5be1e6fbca1891330a90b6d86155f96 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Thu, 6 Jul 2023 09:57:39 -0700 Subject: [PATCH 12/12] remove import Signed-off-by: Tyler Ohlsen --- cypress/utils/dashboards/vis-augmenter/commands.js | 1 - 1 file changed, 1 deletion(-) diff --git a/cypress/utils/dashboards/vis-augmenter/commands.js b/cypress/utils/dashboards/vis-augmenter/commands.js index 6aea9deb7..e0ee418de 100644 --- a/cypress/utils/dashboards/vis-augmenter/commands.js +++ b/cypress/utils/dashboards/vis-augmenter/commands.js @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import _ from 'lodash'; import { BASE_PATH } from '../../constants'; Cypress.Commands.add('getVisPanelByTitle', (title) =>