From 46dd03af3ee8423a218deebed299be55ecf9ff73 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Fri, 7 Jul 2023 11:10:49 +0100 Subject: [PATCH 01/46] Test to ensure when a shortlink is copied, tenant is changed and short link is visited. The tenant from the link is visited. Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../tenancy_change_on_shortlink.js | 139 ++++++++++++++++++ cypress/utils/commands.js | 11 +- 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js new file mode 100644 index 000000000..63ad22981 --- /dev/null +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -0,0 +1,139 @@ +/* + +* Copyright OpenSearch Contributors + +* SPDX-License-Identifier: Apache-2.0 + +*/ +import { CURRENT_TENANT } from '../../../utils/commands'; +import tenantDescription from '../../../fixtures/plugins/security-dashboards-plugin/tenants/testTenant.json'; + +const tenantName = 'test'; + +if (Cypress.env('SECURITY_ENABLED')) { + describe('Multi Tenancy Tests: ', () => { + before(() => { + cy.server(); + cy.createTenant(tenantName, tenantDescription); + + try { + cy.createIndexPattern( + 'index-pattern1', + { + title: 's*', + timeFieldName: 'timestamp', + }, + indexPatternGlobalTenantHeaderSetUp + ); + } catch (error) { + // Ignore + } + try { + cy.createIndexPattern( + 'index-pattern2', + { + title: 'se*', + timeFieldName: 'timestamp', + }, + indexPatternPrivateTenantHeaderSetUp + ); + } catch (error) { + // Ignore + } + }); + + it('Tests that when the short URL is copied and pasted, it will route correctly with the right tenant', function () { + cy.visit('/app/dashboards#create', { + excludeTenant: true, + onBeforeLoad(win) { + // set up session storage as we would expect to emulate browser + win.sessionStorage.setItem( + 'opendistro::security::tenant::show_popup', + false + ); + win.localStorage.setItem( + 'opendistro::security::tenant::saved', + '__user__' + ); + }, + }); + + // 1. Create a dashboard (to be able to share it later) + cy.getElementByTestId('dashboardSaveMenuItem').click(); + + const randomNumber = Cypress._.random(0, 1e6); + const dashboardName = 'Cypress test: My dashboard - ' + randomNumber; + cy.getElementByTestId('savedObjectTitle').type(dashboardName); + + cy.intercept({ + method: 'POST', + url: '/api/saved_objects/_bulk_get', + }).as('waitForReloadingDashboard'); + cy.getElementByTestId('confirmSaveSavedObjectButton').click(); + cy.wait('@waitForReloadingDashboard'); + + // 2. Open top share navigation to access copy short url + cy.get('[data-test-subj="shareTopNavButton"]').click(); + cy.getElementByTestId('sharePanel-Permalinks').click(); + + // 3. Create the short url, wait for response + cy.intercept('POST', '/api/shorten_url').as('getShortUrl'); + // If the url already contains the tenant parameter, it will be stored in the short url. That will work in the app + // but would render this test useless. We're testing that resolved short urls without the tenant parameter work as well. + cy.url().should('not.contain', 'security_tenant'); + cy.getElementByTestId('createShortUrl').click(); + cy.wait('@getShortUrl'); + + function switchTenantTo(newTenant) { + cy.getElementByTestId('account-popover').click(); + cy.getElementByTestId('switch-tenants').click(); + cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); + cy.intercept({ + method: 'POST', + url: '/api/v1/multitenancy/tenant', + }).as('waitForUpdatingTenants'); + + cy.getElementByTestId('tenant-switch-modal') + .find('[data-test-subj="confirm"]') + .click(); + + cy.wait('@waitForUpdatingTenants'); + cy.intercept({ + method: 'GET', + url: '/api/v1/auth/dashboardsinfo', + }).as('waitForReloadAfterTenantSwitch'); + cy.wait('@waitForReloadAfterTenantSwitch'); + cy.wait('@waitForReloadAfterTenantSwitch'); + } + + //4. Switch tenant & visit shortURL link to ensure tenant from short URL is retained + cy.get('[data-test-subj="copyShareUrlButton"]') + .invoke('attr', 'data-share-url') + .should('contain', '/goto/') + .then((shortUrl) => { + cy.log('Short url is ' + shortUrl); + // Navigate away to avoid the non existing dashboard in the next tenant. + // For some reason, using cy.visit() will break things - Cypress can't find the account-popover unless I wait for N seconds. + cy.waitForLoader(); + switchTenantTo('global'); + // The tests override the cy.visit method, so we need to set the tenant so that the custom command can pick it up. + CURRENT_TENANT.newTenant = 'private'; + cy.visit(shortUrl, { + //waitForGetTenant: true + onBeforeLoad(win) { + // Here we are simulating the new tab scenario which isn't supported by Cypress + win.sessionStorage.clear(); + }, + }); + cy.url({ timeout: 10000 }).should( + 'contain', + 'security_tenant=__user__' + ); + cy.getElementByTestId('breadcrumb last').should( + 'contain.text', + dashboardName + ); + }); + }); + }); +} \ No newline at end of file diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index ce28849bc..2eb7db57f 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -46,7 +46,15 @@ Cypress.Commands.overwrite('visit', (orig, url, options) => { auth: ADMIN_AUTH, }; } - newOptions.qs = { security_tenant: CURRENT_TENANT.defaultTenant }; + newOptions.qs = { + ...newOptions.qs, + security_tenant: CURRENT_TENANT.defaultTenant, + }; + + if (newOptions.excludeTenant) { + delete newOptions.qs.security_tenant; + } + if (waitForGetTenant) { cy.intercept('GET', '/api/v1/multitenancy/tenant').as('getTenant'); orig(url, newOptions); @@ -60,6 +68,7 @@ Cypress.Commands.overwrite('visit', (orig, url, options) => { } }); + /** * Overwrite request command to support authentication similar to visit. * The request function parameters can be url, or (method, url), or (method, url, body). From f68f455096a442c29872f009cc9af0e0f2183472 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Fri, 7 Jul 2023 11:40:33 +0100 Subject: [PATCH 02/46] After link copy, allow tenant change Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../change_tenant_successfully.js | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js diff --git a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js new file mode 100644 index 000000000..9edee7531 --- /dev/null +++ b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js @@ -0,0 +1,65 @@ +/* + +* Copyright OpenSearch Contributors + +* SPDX-License-Identifier: Apache-2.0 + +*/ + +import { CURRENT_TENANT } from '../../../utils/commands'; +import tenantDescription from '../../../fixtures/plugins/security-dashboards-plugin/tenants/testTenant.json'; + +const tenantName = 'test'; + +if (Cypress.env('SECURITY_ENABLED')) { + describe('Switch tenants when visiting copyed links: ', () => { + before(() => { + cy.server(); + + cy.createTenant(tenantName, tenantDescription); + }); + if (Cypress.browser.name === 'chrome') { + it('Checks that the tenant switcher can switch tenants despite a different tenant being present in the tenant query parameter.', function () { + CURRENT_TENANT.newTenant = 'private'; + function switchTenantTo(newTenant) { + cy.getElementByTestId('account-popover').click(); + cy.getElementByTestId('switch-tenants').click(); + cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); + + cy.intercept({ + method: 'GET', + url: '/api/v1/auth/dashboardsinfo', + }).as('waitForReloadAfterTenantSwitch'); + + cy.intercept({ + method: 'POST', + url: '/api/v1/multitenancy/tenant', + }).as('waitForUpdatingTenants'); + + cy.getElementByTestId('tenant-switch-modal') + .find('[data-test-subj="confirm"]') + .click(); + cy.wait('@waitForUpdatingTenants'); + + // Make sure dashboards has really reloaded. + // @waitForReloadAfterTenantSwitch should be triggered twice + cy.wait('@waitForReloadAfterTenantSwitch'); + cy.wait('@waitForReloadAfterTenantSwitch'); + } + cy.visit('/app/home', { + // Clean up + onBeforeLoad(win) { + window.localStorage.clear(); + window.sessionStorage.clear(); + }, + }).then(() => { + cy.waitForLoader(); + switchTenantTo('global'); + cy.waitForLoader(); + cy.getElementByTestId('account-popover').click(); + cy.get('#tenantName').should('contain.text', 'Global'); + }); + }); + } + }); +} \ No newline at end of file From 631a03fc80bd27adf4943ffd1ad1c3bb75ff75ea Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Tue, 11 Jul 2023 16:31:30 +0100 Subject: [PATCH 03/46] Addressing comments, removing function to it's own file and removing unneeded code in before. Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../change_tenant_successfully.js | 63 ++++++------------- .../switch_tenant.js | 32 ++++++++++ .../tenancy_change_on_shortlink.js | 62 ++---------------- 3 files changed, 56 insertions(+), 101 deletions(-) create mode 100644 cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js diff --git a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js index 9edee7531..6453b02db 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js +++ b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js @@ -8,8 +8,9 @@ import { CURRENT_TENANT } from '../../../utils/commands'; import tenantDescription from '../../../fixtures/plugins/security-dashboards-plugin/tenants/testTenant.json'; +import { switchTenantTo } from './switch_tenant'; -const tenantName = 'test'; +const tenantName = 'tenant'; if (Cypress.env('SECURITY_ENABLED')) { describe('Switch tenants when visiting copyed links: ', () => { @@ -18,48 +19,22 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.createTenant(tenantName, tenantDescription); }); - if (Cypress.browser.name === 'chrome') { - it('Checks that the tenant switcher can switch tenants despite a different tenant being present in the tenant query parameter.', function () { - CURRENT_TENANT.newTenant = 'private'; - function switchTenantTo(newTenant) { - cy.getElementByTestId('account-popover').click(); - cy.getElementByTestId('switch-tenants').click(); - cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); - - cy.intercept({ - method: 'GET', - url: '/api/v1/auth/dashboardsinfo', - }).as('waitForReloadAfterTenantSwitch'); - - cy.intercept({ - method: 'POST', - url: '/api/v1/multitenancy/tenant', - }).as('waitForUpdatingTenants'); - - cy.getElementByTestId('tenant-switch-modal') - .find('[data-test-subj="confirm"]') - .click(); - cy.wait('@waitForUpdatingTenants'); - - // Make sure dashboards has really reloaded. - // @waitForReloadAfterTenantSwitch should be triggered twice - cy.wait('@waitForReloadAfterTenantSwitch'); - cy.wait('@waitForReloadAfterTenantSwitch'); - } - cy.visit('/app/home', { - // Clean up - onBeforeLoad(win) { - window.localStorage.clear(); - window.sessionStorage.clear(); - }, - }).then(() => { - cy.waitForLoader(); - switchTenantTo('global'); - cy.waitForLoader(); - cy.getElementByTestId('account-popover').click(); - cy.get('#tenantName').should('contain.text', 'Global'); - }); + it('Checks that the tenant switcher can switch tenants despite a different tenant being present in the tenant query parameter.', function () { + CURRENT_TENANT.newTenant = tenantName; + + cy.visit('/app/home', { + // Clean up + onBeforeLoad(win) { + window.localStorage.clear(); + window.sessionStorage.clear(); + }, + }).then(() => { + cy.waitForLoader(); + switchTenantTo('global'); + cy.waitForLoader(); + cy.getElementByTestId('account-popover').click(); + cy.get('#tenantName').should('contain.text', 'Global'); }); - } + }); }); -} \ No newline at end of file +} diff --git a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js new file mode 100644 index 000000000..1fb108575 --- /dev/null +++ b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export function switchTenantTo(newTenant) { + cy.getElementByTestId('account-popover').click(); + cy.getElementByTestId('switch-tenants').click(); + cy.intercept({ + method: 'GET', + url: '/api/v1/auth/dashboardsinfo', + }).as('waitForReloadAfterTenantSwitch'); + + cy.intercept({ + method: 'POST', + url: '/api/v1/multitenancy/tenant', + }).as('waitForUpdatingTenants'); + + cy.wait(2000); + cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); + + cy.getElementByTestId('tenant-switch-modal') + .find('[data-test-subj="confirm"]') + .click(); + cy.wait('@waitForUpdatingTenants'); + + // Make sure dashboards has really reloaded. + // @waitForReloadAfterTenantSwitch should be triggered twice + cy.wait('@waitForReloadAfterTenantSwitch'); + cy.wait('@waitForReloadAfterTenantSwitch'); + } + \ No newline at end of file diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index 63ad22981..da088c253 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -5,43 +5,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { CURRENT_TENANT } from '../../../utils/commands'; -import tenantDescription from '../../../fixtures/plugins/security-dashboards-plugin/tenants/testTenant.json'; -const tenantName = 'test'; +import { CURRENT_TENANT } from '../../../utils/commands'; +import { switchTenantTo } from './switch_tenant'; if (Cypress.env('SECURITY_ENABLED')) { describe('Multi Tenancy Tests: ', () => { - before(() => { - cy.server(); - cy.createTenant(tenantName, tenantDescription); - - try { - cy.createIndexPattern( - 'index-pattern1', - { - title: 's*', - timeFieldName: 'timestamp', - }, - indexPatternGlobalTenantHeaderSetUp - ); - } catch (error) { - // Ignore - } - try { - cy.createIndexPattern( - 'index-pattern2', - { - title: 'se*', - timeFieldName: 'timestamp', - }, - indexPatternPrivateTenantHeaderSetUp - ); - } catch (error) { - // Ignore - } - }); - it('Tests that when the short URL is copied and pasted, it will route correctly with the right tenant', function () { cy.visit('/app/dashboards#create', { excludeTenant: true, @@ -62,7 +31,7 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.getElementByTestId('dashboardSaveMenuItem').click(); const randomNumber = Cypress._.random(0, 1e6); - const dashboardName = 'Cypress test: My dashboard - ' + randomNumber; + const dashboardName = 'Cypress dashboard - ' + randomNumber; cy.getElementByTestId('savedObjectTitle').type(dashboardName); cy.intercept({ @@ -71,6 +40,7 @@ if (Cypress.env('SECURITY_ENABLED')) { }).as('waitForReloadingDashboard'); cy.getElementByTestId('confirmSaveSavedObjectButton').click(); cy.wait('@waitForReloadingDashboard'); + cy.wait(2000); // 2. Open top share navigation to access copy short url cy.get('[data-test-subj="shareTopNavButton"]').click(); @@ -84,28 +54,6 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.getElementByTestId('createShortUrl').click(); cy.wait('@getShortUrl'); - function switchTenantTo(newTenant) { - cy.getElementByTestId('account-popover').click(); - cy.getElementByTestId('switch-tenants').click(); - cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); - cy.intercept({ - method: 'POST', - url: '/api/v1/multitenancy/tenant', - }).as('waitForUpdatingTenants'); - - cy.getElementByTestId('tenant-switch-modal') - .find('[data-test-subj="confirm"]') - .click(); - - cy.wait('@waitForUpdatingTenants'); - cy.intercept({ - method: 'GET', - url: '/api/v1/auth/dashboardsinfo', - }).as('waitForReloadAfterTenantSwitch'); - cy.wait('@waitForReloadAfterTenantSwitch'); - cy.wait('@waitForReloadAfterTenantSwitch'); - } - //4. Switch tenant & visit shortURL link to ensure tenant from short URL is retained cy.get('[data-test-subj="copyShareUrlButton"]') .invoke('attr', 'data-share-url') @@ -136,4 +84,4 @@ if (Cypress.env('SECURITY_ENABLED')) { }); }); }); -} \ No newline at end of file +} From 836089ad752f11ea2f4d8455264cf216b8234859 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Tue, 11 Jul 2023 16:33:02 +0100 Subject: [PATCH 04/46] Spelling error rectified Signed-off-by: leanneeliatra Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../security-dashboards-plugin/change_tenant_successfully.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js index 6453b02db..c7442c1a6 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js +++ b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js @@ -13,7 +13,7 @@ import { switchTenantTo } from './switch_tenant'; const tenantName = 'tenant'; if (Cypress.env('SECURITY_ENABLED')) { - describe('Switch tenants when visiting copyed links: ', () => { + describe('Switch tenants when visiting copied links: ', () => { before(() => { cy.server(); From 5b0df4a8f431b9d2e04dbb3379f8065f09aa63b0 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Wed, 12 Jul 2023 10:48:07 +0100 Subject: [PATCH 05/46] Linting errors resolved Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../change_tenant_successfully.js | 11 ++--- .../switch_tenant.js | 46 +++++++++---------- .../tenancy_change_on_shortlink.js | 9 ++-- cypress/utils/commands.js | 1 - 4 files changed, 30 insertions(+), 37 deletions(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js index c7442c1a6..e777b09aa 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js +++ b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js @@ -1,10 +1,7 @@ /* - -* Copyright OpenSearch Contributors - -* SPDX-License-Identifier: Apache-2.0 - -*/ + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ import { CURRENT_TENANT } from '../../../utils/commands'; import tenantDescription from '../../../fixtures/plugins/security-dashboards-plugin/tenants/testTenant.json'; @@ -24,7 +21,7 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.visit('/app/home', { // Clean up - onBeforeLoad(win) { + onBeforeLoad() { window.localStorage.clear(); window.sessionStorage.clear(); }, diff --git a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js index 1fb108575..c35b26def 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js +++ b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js @@ -4,29 +4,29 @@ */ export function switchTenantTo(newTenant) { - cy.getElementByTestId('account-popover').click(); - cy.getElementByTestId('switch-tenants').click(); - cy.intercept({ - method: 'GET', - url: '/api/v1/auth/dashboardsinfo', - }).as('waitForReloadAfterTenantSwitch'); - - cy.intercept({ - method: 'POST', - url: '/api/v1/multitenancy/tenant', - }).as('waitForUpdatingTenants'); - - cy.wait(2000); - cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); - - cy.getElementByTestId('tenant-switch-modal') + cy.getElementByTestId('account-popover').click(); + cy.getElementByTestId('switch-tenants').click(); + cy.intercept({ + method: 'GET', + url: '/api/v1/auth/dashboardsinfo', + }).as('waitForReloadAfterTenantSwitch'); + + cy.intercept({ + method: 'POST', + url: '/api/v1/multitenancy/tenant', + }).as('waitForUpdatingTenants'); + + cy.wait(2000); + cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); + + cy.getElementByTestId('tenant-switch-modal') .find('[data-test-subj="confirm"]') .click(); - cy.wait('@waitForUpdatingTenants'); - - // Make sure dashboards has really reloaded. - // @waitForReloadAfterTenantSwitch should be triggered twice - cy.wait('@waitForReloadAfterTenantSwitch'); - cy.wait('@waitForReloadAfterTenantSwitch'); - } + cy.wait('@waitForUpdatingTenants'); + + // Make sure dashboards has really reloaded. + // @waitForReloadAfterTenantSwitch should be triggered twice + cy.wait('@waitForReloadAfterTenantSwitch'); + cy.wait('@waitForReloadAfterTenantSwitch'); +} \ No newline at end of file diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index da088c253..5b05270fd 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -1,10 +1,7 @@ /* - -* Copyright OpenSearch Contributors - -* SPDX-License-Identifier: Apache-2.0 - -*/ + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ import { CURRENT_TENANT } from '../../../utils/commands'; import { switchTenantTo } from './switch_tenant'; diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index 2eb7db57f..f3fb82960 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -68,7 +68,6 @@ Cypress.Commands.overwrite('visit', (orig, url, options) => { } }); - /** * Overwrite request command to support authentication similar to visit. * The request function parameters can be url, or (method, url), or (method, url, body). From d5f35203748b1115fe69d71594b138320a72f023 Mon Sep 17 00:00:00 2001 From: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Date: Fri, 14 Jul 2023 14:23:12 +0100 Subject: [PATCH 06/46] Update cypress/utils/commands.js Co-authored-by: Yulong Ruan Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- cypress/utils/commands.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index f3fb82960..189371ea3 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -46,13 +46,11 @@ Cypress.Commands.overwrite('visit', (orig, url, options) => { auth: ADMIN_AUTH, }; } - newOptions.qs = { - ...newOptions.qs, - security_tenant: CURRENT_TENANT.defaultTenant, - }; - - if (newOptions.excludeTenant) { - delete newOptions.qs.security_tenant; + if (!newOptions.excludeTenant) { + newOptions.qs = { + ...newOptions.qs, + security_tenant: CURRENT_TENANT.defaultTenant, + }; } if (waitForGetTenant) { From dee3b576e71c0b093aceb77dbe6e85de63751888 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Mon, 17 Jul 2023 09:55:11 +0100 Subject: [PATCH 07/46] Changed data-test-subj to getElementByTestId for consitency Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../security-dashboards-plugin/tenancy_change_on_shortlink.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index 5b05270fd..54738dd27 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -40,7 +40,7 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.wait(2000); // 2. Open top share navigation to access copy short url - cy.get('[data-test-subj="shareTopNavButton"]').click(); + cy.getElementByTestId('shareTopNavButton').click(); cy.getElementByTestId('sharePanel-Permalinks').click(); // 3. Create the short url, wait for response @@ -52,7 +52,7 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.wait('@getShortUrl'); //4. Switch tenant & visit shortURL link to ensure tenant from short URL is retained - cy.get('[data-test-subj="copyShareUrlButton"]') + cy.getElementByTestId('copyShareUrlButton') .invoke('attr', 'data-share-url') .should('contain', '/goto/') .then((shortUrl) => { From 9f1ac57332fd22601a92ccc5d8933001caa559f5 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Tue, 18 Jul 2023 10:09:36 +0100 Subject: [PATCH 08/46] Switch tenant updated to use interceps and should() for improved test performance Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../switch_tenant.js | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js index c35b26def..edaee761d 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js +++ b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js @@ -2,31 +2,43 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - + export function switchTenantTo(newTenant) { cy.getElementByTestId('account-popover').click(); - cy.getElementByTestId('switch-tenants').click(); cy.intercept({ method: 'GET', - url: '/api/v1/auth/dashboardsinfo', - }).as('waitForReloadAfterTenantSwitch'); + url: '/api/v1/auth/dashboardsinfo', + }).as('waitForDashboardsInfo'); + + cy.intercept({ + method: 'GET', + url: '/api/v1/configuration/account', + }).as('waitForAccountInfo'); + + + cy.getElementByTestId('switch-tenants').click(); + + // Make sure the switch tenant panel has finished loading. + // Otherwise, the tenant radio buttons may till be disabled and the click won't have any effect. + cy.wait('@waitForDashboardsInfo') + cy.wait('@waitForAccountInfo') + + cy.get('[id="' + newTenant + '"][name="tenantSwitchRadios"]').should('be.enabled'); + cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); cy.intercept({ method: 'POST', url: '/api/v1/multitenancy/tenant', }).as('waitForUpdatingTenants'); - - cy.wait(2000); - cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); - cy.getElementByTestId('tenant-switch-modal') - .find('[data-test-subj="confirm"]') - .click(); - cy.wait('@waitForUpdatingTenants'); + .find('[data-test-subj="confirm"]') + .click(); + + cy.wait('@waitForUpdatingTenants') // Make sure dashboards has really reloaded. // @waitForReloadAfterTenantSwitch should be triggered twice - cy.wait('@waitForReloadAfterTenantSwitch'); - cy.wait('@waitForReloadAfterTenantSwitch'); -} - \ No newline at end of file + cy.wait('@waitForDashboardsInfo'); + cy.wait('@waitForDashboardsInfo'); + +} \ No newline at end of file From dff22e8a141d314a3a5d28216b443cb4d810f5b6 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Tue, 18 Jul 2023 10:10:44 +0100 Subject: [PATCH 09/46] switch tenant changed to use intercepts and should() for increaced test performance. Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../plugins/security-dashboards-plugin/switch_tenant.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js index edaee761d..f8d63a949 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js +++ b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js @@ -15,14 +15,7 @@ export function switchTenantTo(newTenant) { url: '/api/v1/configuration/account', }).as('waitForAccountInfo'); - cy.getElementByTestId('switch-tenants').click(); - - // Make sure the switch tenant panel has finished loading. - // Otherwise, the tenant radio buttons may till be disabled and the click won't have any effect. - cy.wait('@waitForDashboardsInfo') - cy.wait('@waitForAccountInfo') - cy.get('[id="' + newTenant + '"][name="tenantSwitchRadios"]').should('be.enabled'); cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); @@ -40,5 +33,4 @@ export function switchTenantTo(newTenant) { // @waitForReloadAfterTenantSwitch should be triggered twice cy.wait('@waitForDashboardsInfo'); cy.wait('@waitForDashboardsInfo'); - } \ No newline at end of file From 2074437dd32aa89488dbabb7cdf960d6153a24d8 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Tue, 18 Jul 2023 10:15:37 +0100 Subject: [PATCH 10/46] Adding commen to explain how the addtion of the should() helps Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../plugins/security-dashboards-plugin/switch_tenant.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js index f8d63a949..ec86bbe78 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js +++ b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js @@ -16,6 +16,7 @@ export function switchTenantTo(newTenant) { }).as('waitForAccountInfo'); cy.getElementByTestId('switch-tenants').click(); + //should ensures the dialog window is fully loaded and the radios can be selected. cy.get('[id="' + newTenant + '"][name="tenantSwitchRadios"]').should('be.enabled'); cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); From 14a870b5fa700f46c3facbb5056532f38d0aa189 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Tue, 18 Jul 2023 10:50:02 +0100 Subject: [PATCH 11/46] Global declaration of the tenant removed. The clearing of session storage is not needed actually, cy.visit() does this for us Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../change_tenant_successfully.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js index e777b09aa..3799264ec 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js +++ b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js @@ -4,28 +4,20 @@ */ import { CURRENT_TENANT } from '../../../utils/commands'; -import tenantDescription from '../../../fixtures/plugins/security-dashboards-plugin/tenants/testTenant.json'; import { switchTenantTo } from './switch_tenant'; -const tenantName = 'tenant'; if (Cypress.env('SECURITY_ENABLED')) { describe('Switch tenants when visiting copied links: ', () => { + const tenantName = 'private'; + before(() => { cy.server(); - - cy.createTenant(tenantName, tenantDescription); }); it('Checks that the tenant switcher can switch tenants despite a different tenant being present in the tenant query parameter.', function () { CURRENT_TENANT.newTenant = tenantName; - cy.visit('/app/home', { - // Clean up - onBeforeLoad() { - window.localStorage.clear(); - window.sessionStorage.clear(); - }, - }).then(() => { + cy.visit('/app/home').then(() => { cy.waitForLoader(); switchTenantTo('global'); cy.waitForLoader(); From b0cf9bdcab6468b2d1866b56dbe49437fb049464 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Tue, 18 Jul 2023 16:50:37 +0100 Subject: [PATCH 12/46] Additition of 'createDashboard' method to allow programatic dashboard creation. Also improvements to the test. Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../tenancy_change_on_shortlink.js | 108 ++++++++++++------ cypress/utils/commands.js | 22 +++- cypress/utils/index.d.ts | 14 +++ 3 files changed, 109 insertions(+), 35 deletions(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index 54738dd27..8dfda810c 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -1,46 +1,85 @@ /* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +*/ import { CURRENT_TENANT } from '../../../utils/commands'; import { switchTenantTo } from './switch_tenant'; +import indexPatternGlobalTenantHeaderSetUp + from "../../../fixtures/plugins/security-dashboards-plugin/indexpatterns/indexPatternGlobalTenantHeader.json"; +import indexPatternPrivateTenantHeaderSetUp + from "../../../fixtures/plugins/security-dashboards-plugin/indexpatterns/indexPatternPrivateTenantHeader.json"; if (Cypress.env('SECURITY_ENABLED')) { describe('Multi Tenancy Tests: ', () => { + before(() => { + cy.server(); + + cy.createIndexPattern( + 'index-pattern1', + { + title: 's*', + timeFieldName: 'timestamp', + }, + indexPatternGlobalTenantHeaderSetUp + ); + + cy.createIndexPattern( + 'index-pattern2', + { + title: 'se*', + timeFieldName: 'timestamp', + }, + indexPatternPrivateTenantHeaderSetUp + ); + }); + it('Tests that when the short URL is copied and pasted, it will route correctly with the right tenant', function () { - cy.visit('/app/dashboards#create', { - excludeTenant: true, - onBeforeLoad(win) { + const randomNumber = Cypress._.random(0, 1e6); + const dashboardName = 'Cypress dashboard - ' + randomNumber; + // We are programmatically creating a dashboard so that the test + // always have the same view. An empty list would show the empty prompt. + // Also, this saves us some typing, clicking and waiting in the test. + cy.createDashboard({ + title: dashboardName + }, { + security_tenant: 'private' + }) + + // When creating the shortUrl, we don't want to have the security_tenant + // parameter in the url - otherwise it will be stored in the shortUrl + // itself, which would make this test obsolete. + // But it is also hard to get the tests running reliably when opening + // Dashboards without the parameter (tenant selector popup etc.). + // Hence, we do some navigation to "lose" the query parameter. + CURRENT_TENANT.newTenant = 'private'; + cy.visit('/app/home', { + waitForGetTenant: true, + onBeforeLoad(window) { // set up session storage as we would expect to emulate browser - win.sessionStorage.setItem( + window.sessionStorage.setItem( 'opendistro::security::tenant::show_popup', false ); - win.localStorage.setItem( + + window.localStorage.setItem( 'opendistro::security::tenant::saved', '__user__' ); }, - }); + } ); + // Navigate to the Dashboards app + cy.getElementByTestId('toggleNavButton').should('be.visible').click(); + // After clicking the navigation, the security_tenant parameter should be gone + cy.get('[href$="/app/dashboards#/list"]').should('be.visible').click(); - // 1. Create a dashboard (to be able to share it later) - cy.getElementByTestId('dashboardSaveMenuItem').click(); + // The test subj seems to replace spaces with a dash, so we convert the dashboard name here too. + // Go to the dashboard we have created + const selectorDashboardName = dashboardName.split(' ').join('-'); + cy.getElementByTestId('dashboardListingTitleLink-' + selectorDashboardName).should('be.visible').click(); - const randomNumber = Cypress._.random(0, 1e6); - const dashboardName = 'Cypress dashboard - ' + randomNumber; - cy.getElementByTestId('savedObjectTitle').type(dashboardName); - - cy.intercept({ - method: 'POST', - url: '/api/saved_objects/_bulk_get', - }).as('waitForReloadingDashboard'); - cy.getElementByTestId('confirmSaveSavedObjectButton').click(); - cy.wait('@waitForReloadingDashboard'); - cy.wait(2000); - - // 2. Open top share navigation to access copy short url - cy.getElementByTestId('shareTopNavButton').click(); + // Open top share navigation to access copy short url + cy.getElementByTestId('shareTopNavButton').should('be.visible').click(); cy.getElementByTestId('sharePanel-Permalinks').click(); // 3. Create the short url, wait for response @@ -51,25 +90,25 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.getElementByTestId('createShortUrl').click(); cy.wait('@getShortUrl'); + //4. Switch tenant & visit shortURL link to ensure tenant from short URL is retained - cy.getElementByTestId('copyShareUrlButton') + cy.get('[data-test-subj="copyShareUrlButton"]') .invoke('attr', 'data-share-url') .should('contain', '/goto/') .then((shortUrl) => { cy.log('Short url is ' + shortUrl); // Navigate away to avoid the non existing dashboard in the next tenant. - // For some reason, using cy.visit() will break things - Cypress can't find the account-popover unless I wait for N seconds. - cy.waitForLoader(); switchTenantTo('global'); - // The tests override the cy.visit method, so we need to set the tenant so that the custom command can pick it up. - CURRENT_TENANT.newTenant = 'private'; - cy.visit(shortUrl, { - //waitForGetTenant: true - onBeforeLoad(win) { + + // Since we can't reliably read the clipboard data, we have to append the tenant parameter manually + cy.visit(shortUrl + '?security_tenant=private', { + excludeTenant: true, // We are passing the tenant as a query parameter. Mainly because of readability. + onBeforeLoad(window) { // Here we are simulating the new tab scenario which isn't supported by Cypress - win.sessionStorage.clear(); + window.sessionStorage.clear(); }, }); + cy.url({ timeout: 10000 }).should( 'contain', 'security_tenant=__user__' @@ -82,3 +121,4 @@ if (Cypress.env('SECURITY_ENABLED')) { }); }); } + diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index 189371ea3..b17ebd351 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -337,7 +337,8 @@ Cypress.Commands.add('deleteSavedObjectByType', (type, search) => { Cypress.Commands.add('createIndexPattern', (id, attributes, header = {}) => { const url = `${ Cypress.config().baseUrl - }/api/saved_objects/index-pattern/${id}`; + }/api/saved_objects/index-pattern/${id}?overwrite=true`; // When running tests locally if ran multiple times the tests fail. The fix is to set Overwrite to true. + cy.request({ method: 'POST', @@ -354,6 +355,25 @@ Cypress.Commands.add('createIndexPattern', (id, attributes, header = {}) => { }); }); +Cypress.Commands.add('createDashboard', (attributes= {}, headers = {}) => { + const url = `${ + Cypress.config().baseUrl + }/api/saved_objects/dashboard`; + + cy.request({ + method: 'POST', + url, + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + ...headers, + }, + body: JSON.stringify({ + attributes, + }), + }); +}); + Cypress.Commands.add('changeDefaultTenant', (attributes, header = {}) => { const url = Cypress.env('openSearchUrl') + '/_plugins/_security/api/tenancy/config'; diff --git a/cypress/utils/index.d.ts b/cypress/utils/index.d.ts index 4f436c3e0..b1f98d309 100644 --- a/cypress/utils/index.d.ts +++ b/cypress/utils/index.d.ts @@ -87,6 +87,20 @@ declare namespace Cypress { header: string, ): Chainable; + /** + * Adds a dashboard + * @example + * cy.createDashboard({ title: 'My dashboard'}) + */ + createDashboard( + attributes: { + title: string; + [key: string]: any; + }, + headers?: { + [key: string]: any; + } + ): Chainable; /** * Changes the Default tenant for the domain. From c4c418ad588a9aa424c38f4ccfce5b242ec920c9 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Tue, 18 Jul 2023 18:11:06 +0100 Subject: [PATCH 13/46] When testing we are looking for the private tenant. Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../security-dashboards-plugin/tenancy_change_on_shortlink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index 8dfda810c..16619e777 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -111,7 +111,7 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.url({ timeout: 10000 }).should( 'contain', - 'security_tenant=__user__' + 'security_tenant=private' ); cy.getElementByTestId('breadcrumb last').should( 'contain.text', From a9db5e5d74d4408cb21e3076e2e162ef176bcbf0 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Wed, 19 Jul 2023 12:26:53 +0100 Subject: [PATCH 14/46] check changed to allow for both 'private' and __user__ Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../security-dashboards-plugin/tenancy_change_on_shortlink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index 16619e777..1f406fa18 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -111,7 +111,7 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.url({ timeout: 10000 }).should( 'contain', - 'security_tenant=private' + 'security_tenant=' ); cy.getElementByTestId('breadcrumb last').should( 'contain.text', From 51d5d4273efd00d68c0f6b7da27eee0834466578 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Thu, 20 Jul 2023 10:07:46 +0100 Subject: [PATCH 15/46] linting errors resolved Signed-off-by: leanneeliatra Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../change_tenant_successfully.js | 1 - .../switch_tenant.js | 12 +-- .../tenancy_change_on_shortlink.js | 78 +++++++++---------- cypress/utils/commands.js | 7 +- package-lock.json | 6 +- package.json | 2 +- 6 files changed, 52 insertions(+), 54 deletions(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js index 3799264ec..76e44ae2a 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js +++ b/cypress/integration/plugins/security-dashboards-plugin/change_tenant_successfully.js @@ -6,7 +6,6 @@ import { CURRENT_TENANT } from '../../../utils/commands'; import { switchTenantTo } from './switch_tenant'; - if (Cypress.env('SECURITY_ENABLED')) { describe('Switch tenants when visiting copied links: ', () => { const tenantName = 'private'; diff --git a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js index ec86bbe78..5666eca8f 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js +++ b/cypress/integration/plugins/security-dashboards-plugin/switch_tenant.js @@ -2,7 +2,7 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - + export function switchTenantTo(newTenant) { cy.getElementByTestId('account-popover').click(); cy.intercept({ @@ -17,8 +17,10 @@ export function switchTenantTo(newTenant) { cy.getElementByTestId('switch-tenants').click(); //should ensures the dialog window is fully loaded and the radios can be selected. - cy.get('[id="' + newTenant + '"][name="tenantSwitchRadios"]').should('be.enabled'); - cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); + cy.get('[id="' + newTenant + '"][name="tenantSwitchRadios"]').should( + 'be.enabled' + ); + cy.get('.euiRadio__label[for="' + newTenant + '"]').click(); cy.intercept({ method: 'POST', @@ -28,10 +30,10 @@ export function switchTenantTo(newTenant) { .find('[data-test-subj="confirm"]') .click(); - cy.wait('@waitForUpdatingTenants') + cy.wait('@waitForUpdatingTenants'); // Make sure dashboards has really reloaded. // @waitForReloadAfterTenantSwitch should be triggered twice cy.wait('@waitForDashboardsInfo'); cy.wait('@waitForDashboardsInfo'); -} \ No newline at end of file +} diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index 1f406fa18..fcd2d6903 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -1,38 +1,36 @@ /* -* Copyright OpenSearch Contributors -* SPDX-License-Identifier: Apache-2.0 -*/ + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ import { CURRENT_TENANT } from '../../../utils/commands'; import { switchTenantTo } from './switch_tenant'; -import indexPatternGlobalTenantHeaderSetUp - from "../../../fixtures/plugins/security-dashboards-plugin/indexpatterns/indexPatternGlobalTenantHeader.json"; -import indexPatternPrivateTenantHeaderSetUp - from "../../../fixtures/plugins/security-dashboards-plugin/indexpatterns/indexPatternPrivateTenantHeader.json"; +import indexPatternGlobalTenantHeaderSetUp from '../../../fixtures/plugins/security-dashboards-plugin/indexpatterns/indexPatternGlobalTenantHeader.json'; +import indexPatternPrivateTenantHeaderSetUp from '../../../fixtures/plugins/security-dashboards-plugin/indexpatterns/indexPatternPrivateTenantHeader.json'; if (Cypress.env('SECURITY_ENABLED')) { describe('Multi Tenancy Tests: ', () => { - before(() => { - cy.server(); + before(() => { + cy.server(); - cy.createIndexPattern( - 'index-pattern1', - { - title: 's*', - timeFieldName: 'timestamp', - }, - indexPatternGlobalTenantHeaderSetUp - ); + cy.createIndexPattern( + 'index-pattern1', + { + title: 's*', + timeFieldName: 'timestamp', + }, + indexPatternGlobalTenantHeaderSetUp + ); - cy.createIndexPattern( - 'index-pattern2', - { - title: 'se*', - timeFieldName: 'timestamp', - }, - indexPatternPrivateTenantHeaderSetUp - ); - }); + cy.createIndexPattern( + 'index-pattern2', + { + title: 'se*', + timeFieldName: 'timestamp', + }, + indexPatternPrivateTenantHeaderSetUp + ); + }); it('Tests that when the short URL is copied and pasted, it will route correctly with the right tenant', function () { const randomNumber = Cypress._.random(0, 1e6); @@ -40,11 +38,14 @@ if (Cypress.env('SECURITY_ENABLED')) { // We are programmatically creating a dashboard so that the test // always have the same view. An empty list would show the empty prompt. // Also, this saves us some typing, clicking and waiting in the test. - cy.createDashboard({ - title: dashboardName - }, { - security_tenant: 'private' - }) + cy.createDashboard( + { + title: dashboardName, + }, + { + security_tenant: 'private', + } + ); // When creating the shortUrl, we don't want to have the security_tenant // parameter in the url - otherwise it will be stored in the shortUrl @@ -67,7 +68,7 @@ if (Cypress.env('SECURITY_ENABLED')) { '__user__' ); }, - } ); + }); // Navigate to the Dashboards app cy.getElementByTestId('toggleNavButton').should('be.visible').click(); // After clicking the navigation, the security_tenant parameter should be gone @@ -76,7 +77,11 @@ if (Cypress.env('SECURITY_ENABLED')) { // The test subj seems to replace spaces with a dash, so we convert the dashboard name here too. // Go to the dashboard we have created const selectorDashboardName = dashboardName.split(' ').join('-'); - cy.getElementByTestId('dashboardListingTitleLink-' + selectorDashboardName).should('be.visible').click(); + cy.getElementByTestId( + 'dashboardListingTitleLink-' + selectorDashboardName + ) + .should('be.visible') + .click(); // Open top share navigation to access copy short url cy.getElementByTestId('shareTopNavButton').should('be.visible').click(); @@ -90,7 +95,6 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.getElementByTestId('createShortUrl').click(); cy.wait('@getShortUrl'); - //4. Switch tenant & visit shortURL link to ensure tenant from short URL is retained cy.get('[data-test-subj="copyShareUrlButton"]') .invoke('attr', 'data-share-url') @@ -109,10 +113,7 @@ if (Cypress.env('SECURITY_ENABLED')) { }, }); - cy.url({ timeout: 10000 }).should( - 'contain', - 'security_tenant=' - ); + cy.url({ timeout: 10000 }).should('contain', 'security_tenant='); cy.getElementByTestId('breadcrumb last').should( 'contain.text', dashboardName @@ -121,4 +122,3 @@ if (Cypress.env('SECURITY_ENABLED')) { }); }); } - diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index b17ebd351..1b753dc5d 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -338,7 +338,6 @@ Cypress.Commands.add('createIndexPattern', (id, attributes, header = {}) => { const url = `${ Cypress.config().baseUrl }/api/saved_objects/index-pattern/${id}?overwrite=true`; // When running tests locally if ran multiple times the tests fail. The fix is to set Overwrite to true. - cy.request({ method: 'POST', @@ -355,10 +354,8 @@ Cypress.Commands.add('createIndexPattern', (id, attributes, header = {}) => { }); }); -Cypress.Commands.add('createDashboard', (attributes= {}, headers = {}) => { - const url = `${ - Cypress.config().baseUrl - }/api/saved_objects/dashboard`; +Cypress.Commands.add('createDashboard', (attributes = {}, headers = {}) => { + const url = `${Cypress.config().baseUrl}/api/saved_objects/dashboard`; cy.request({ method: 'POST', diff --git a/package-lock.json b/package-lock.json index be3b195ff..933607a33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2387,9 +2387,9 @@ "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" diff --git a/package.json b/package.json index a4f304776..ec47eb5cb 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1", "cypress:open": "cypress open", "cypress:run-without-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=false", - "cypress:run-with-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200", + "cypress:run-with-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200", "cypress:run-with-security-and-aggregation-view": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200,AGGREGATION_VIEW=true", "cypress:run-plugin-tests-without-security": "yarn cypress:run-without-security --spec 'cypress/integration/plugins/*/*.js'", "cypress:run-plugin-tests-with-security": "yarn cypress:run-with-security --spec 'cypress/integration/plugins/*/*.js'", From e91238b44cd958bdcf099066ffe608dee3524d69 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Mon, 24 Jul 2023 15:38:53 +0100 Subject: [PATCH 16/46] merging in changes on remote Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../tenancy_change_on_shortlink.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index fcd2d6903..958746740 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -83,8 +83,18 @@ if (Cypress.env('SECURITY_ENABLED')) { .should('be.visible') .click(); - // Open top share navigation to access copy short url - cy.getElementByTestId('shareTopNavButton').should('be.visible').click(); + cy.getElementByTestId('savedObjectTitle').type(dashboardName); + + cy.intercept({ + method: 'POST', + url: '/api/saved_objects/_bulk_get', + }).as('waitForReloadingDashboard'); + cy.getElementByTestId('confirmSaveSavedObjectButton').click(); + cy.wait('@waitForReloadingDashboard'); + cy.wait(2000); + + // 2. Open top share navigation to access copy short url + cy.getElementByTestId('shareTopNavButton').click(); cy.getElementByTestId('sharePanel-Permalinks').click(); // 3. Create the short url, wait for response @@ -96,7 +106,7 @@ if (Cypress.env('SECURITY_ENABLED')) { cy.wait('@getShortUrl'); //4. Switch tenant & visit shortURL link to ensure tenant from short URL is retained - cy.get('[data-test-subj="copyShareUrlButton"]') + cy.getElementByTestId('copyShareUrlButton') .invoke('attr', 'data-share-url') .should('contain', '/goto/') .then((shortUrl) => { From ec83509df18e5aa96bc69e862e2474f879f9c24c Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Thu, 27 Jul 2023 10:02:38 +0100 Subject: [PATCH 17/46] Adding back --headless parameter. Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ec47eb5cb..a4f304776 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1", "cypress:open": "cypress open", "cypress:run-without-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=false", - "cypress:run-with-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200", + "cypress:run-with-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200", "cypress:run-with-security-and-aggregation-view": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200,AGGREGATION_VIEW=true", "cypress:run-plugin-tests-without-security": "yarn cypress:run-without-security --spec 'cypress/integration/plugins/*/*.js'", "cypress:run-plugin-tests-with-security": "yarn cypress:run-with-security --spec 'cypress/integration/plugins/*/*.js'", From 3303e2345d3e42301893720be43697102c058b77 Mon Sep 17 00:00:00 2001 From: leanneeliatra Date: Thu, 27 Jul 2023 10:07:25 +0100 Subject: [PATCH 18/46] undoing package-lock changes. Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 933607a33..be3b195ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2387,9 +2387,9 @@ "dev": true }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dev": true, "requires": { "lru-cache": "^6.0.0" From 02ad4e4275849a3031ad457cd21bd533618afa6d Mon Sep 17 00:00:00 2001 From: Kawika Avilla Date: Mon, 10 Jul 2023 12:16:15 -0700 Subject: [PATCH 19/46] Use library from release tag instead of git reference (#706) The module installed from the OSD test library seems to update or not based on caching issues. Related proposal: https://github.com/opensearch-project/opensearch-dashboards-test-library/issues/36 Signed-off-by: Kawika Avilla Signed-off-by: leanne.laceybyrne@eliatra.com --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index be3b195ff..662183044 100644 --- a/package-lock.json +++ b/package-lock.json @@ -182,8 +182,8 @@ "dev": true }, "@opensearch-dashboards-test/opensearch-dashboards-test-library": { - "version": "git+https://github.com/opensearch-project/opensearch-dashboards-test-library.git#bae148619679abdc1eb2865da25929ca4180c1ce", - "from": "git+https://github.com/opensearch-project/opensearch-dashboards-test-library.git#main" + "version": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.4.tar.gz", + "integrity": "sha512-IGxw7GVFRyKgjVs+ubTDynrOEvqlI7gTua+Lf2LbDL9g+f6qQ64gnCyMQWeu3mr+w38r+rvN6Uq0AGCcbQ9mlA==" }, "@types/json5": { "version": "0.0.29", diff --git a/package.json b/package.json index a4f304776..142882d49 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "homepage": "https://github.com/opensearch-project/opensearch-dashboards-functional-test", "dependencies": { "@cypress/skip-test": "^2.6.1", - "@opensearch-dashboards-test/opensearch-dashboards-test-library": "git+https://github.com/opensearch-project/opensearch-dashboards-test-library.git#main", + "@opensearch-dashboards-test/opensearch-dashboards-test-library": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.4.tar.gz", "brace": "^0.11.1", "prettier": "^2.5.1" }, From 1d46c05f9a2f4b2c973f1ff26f15a88d211bd4d4 Mon Sep 17 00:00:00 2001 From: Sirazh Gabdullin Date: Tue, 11 Jul 2023 05:25:33 +0600 Subject: [PATCH 20/46] update testSplitTables test (#731) Signed-off-by: Sirazh Gabdullin Signed-off-by: leanne.laceybyrne@eliatra.com --- .../apps/vis_builder/vis_types/table.spec.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js index 69900379b..d04accb72 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js @@ -50,14 +50,14 @@ if (Cypress.env('VISBUILDER_ENABLED')) { cy.getElementByTestId('field-categories.keyword-showDetails').drag( '[data-test-subj="dropBoxAddField-split_row"]' ); - testSplitTables('', 4); + testSplitTables(4); removeBucket('dropBoxField-split_row-0'); // vis builder should render splitted tables in columns cy.getElementByTestId('field-categories.keyword-showDetails').drag( '[data-test-subj="dropBoxAddField-split_column"]' ); - testSplitTables('visTable__groupInColumns', 4); + testSplitTables(4); }); after(() => { @@ -85,9 +85,9 @@ export const removeBucket = (bucket) => { }); }; -export const testSplitTables = (dir, num) => { +export const testSplitTables = (num) => { cy.getElementByTestId('visTable') - .should('have.class', `visTable ${dir}`.trim()) + .should('have.class', `visTable`) .find('[class="visTable__group"]') .should(($tables) => { // should have found specified number of tables From 00811ccd46e819da8bf6ee65b7b529ff6a75faef Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 11 Jul 2023 12:04:25 -0700 Subject: [PATCH 21/46] [Vis Augmenter / Feature Anywhere] Add tests in core OSD and AD plugin (#739) * [Vis Augmenter / Feature Anywhere] Add test suite for vanilla OSD + helper fns for plugins (#725) * feature anywhere initial tests Signed-off-by: Jovan Cvetkovic * Add test suite Signed-off-by: Tyler Ohlsen * Remove unnecessary test case Signed-off-by: Tyler Ohlsen * Optimize getters Signed-off-by: Tyler Ohlsen --------- Signed-off-by: Jovan Cvetkovic Signed-off-by: Tyler Ohlsen Co-authored-by: Jovan Cvetkovic * [Feature Anywhere / Vis Augmenter] Add test flows for integration with AD plugin (#727) * feature anywhere initial tests Signed-off-by: Jovan Cvetkovic * Add test suite Signed-off-by: Tyler Ohlsen * Add AD vis augmenter tests Signed-off-by: Tyler Ohlsen * More refactoring Signed-off-by: Tyler Ohlsen * More tests Signed-off-by: Tyler Ohlsen * Add test for AD cleanup scenario Signed-off-by: Tyler Ohlsen * Set up saved obj test suite Signed-off-by: Tyler Ohlsen * Add reminder TODO Signed-off-by: Tyler Ohlsen * Add tests regarding saved obj visibility Signed-off-by: Tyler Ohlsen * Add view events tests Signed-off-by: Tyler Ohlsen * cleanup Signed-off-by: Tyler Ohlsen * remove import Signed-off-by: Tyler Ohlsen --------- Signed-off-by: Jovan Cvetkovic Signed-off-by: Tyler Ohlsen Co-authored-by: Jovan Cvetkovic --------- Signed-off-by: Jovan Cvetkovic Signed-off-by: Tyler Ohlsen Co-authored-by: Jovan Cvetkovic Signed-off-by: leanne.laceybyrne@eliatra.com --- .../sample-data-simple/data.json | 27 ++ .../index-pattern-fields.txt | 1 + .../sample-data-simple/index-settings.txt | 1 + .../apps/vis-augmenter/dashboard_spec.js | 129 +++++++ .../sample_detector_spec.js | 20 +- .../vis_augmenter/associate_detector_spec.js | 145 ++++++++ .../augment_vis_saved_object_spec.js | 119 +++++++ .../vis_augmenter/view_anomaly_events_spec.js | 111 ++++++ cypress/utils/dashboards/commands.js | 1 + cypress/utils/dashboards/constants.js | 1 + .../dashboards/vis-augmenter/commands.js | 48 +++ .../dashboards/vis-augmenter/constants.js | 15 + .../utils/dashboards/vis-augmenter/helpers.js | 325 ++++++++++++++++++ .../utils/dashboards/vis-augmenter/index.d.ts | 45 +++ cypress/utils/helpers.js | 1 + .../helpers.js | 116 +++++++ 16 files changed, 1086 insertions(+), 19 deletions(-) create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js create mode 100644 cypress/utils/dashboards/vis-augmenter/commands.js create mode 100644 cypress/utils/dashboards/vis-augmenter/constants.js create mode 100644 cypress/utils/dashboards/vis-augmenter/helpers.js create mode 100644 cypress/utils/dashboards/vis-augmenter/index.d.ts diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json new file mode 100644 index 000000000..5124ea41e --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json @@ -0,0 +1,27 @@ +[ + { + "value1": 1, + "value2": 10, + "value3": 5 + }, + { + "value1": 5, + "value2": 1, + "value3": 3 + }, + { + "value1": 9, + "value2": 6, + "value3": 2 + }, + { + "value1": 2, + "value2": 1, + "value3": 1 + }, + { + "value1": 12, + "value2": 5, + "value3": 4 + } +] diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt new file mode 100644 index 000000000..2e010deba --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt @@ -0,0 +1 @@ +[{"count":0,"name":"@timestamp","type":"date","esTypes":["date"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"_id","type":"string","esTypes":["_id"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_index","type":"string","esTypes":["_index"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_score","type":"number","scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_source","type":"_source","esTypes":["_source"],"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_type","type":"string","scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"value1","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"value2","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"value3","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false}] \ No newline at end of file diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt new file mode 100644 index 000000000..d4d78055d --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt @@ -0,0 +1 @@ +{"mappings":{"properties":{"value1":{"type":"integer"},"value2":{"type":"integer"},"value3":{"type":"integer"},"@timestamp":{"type":"date", "format":"epoch_millis"}}},"settings":{"index":{"number_of_shards":"1","number_of_replicas":"1"}}} \ No newline at end of file diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js new file mode 100644 index 000000000..d97647c55 --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js @@ -0,0 +1,129 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + INDEX_PATTERN_FILEPATH_SIMPLE, + INDEX_SETTINGS_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, +} from '../../../../../utils/constants'; +import { + deleteVisAugmenterData, + bootstrapDashboard, +} from '../../../../../utils/dashboards/vis-augmenter/helpers'; + +describe('Vis augmenter - existing dashboards work as expected', () => { + describe('dashboard with ineligible, eligible, and vega visualizations', () => { + const indexName = 'vis-augmenter-sample-index'; + const indexPatternName = 'vis-augmenter-sample-*'; + const dashboardName = 'Vis Augmenter Dashboard'; + const visualizationSpecs = [ + { + name: 'count-agg-vis', + type: 'line', + indexPattern: indexPatternName, + metrics: [], + }, + { + name: 'single-metric-vis', + type: 'line', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Average', + field: 'value1', + }, + ], + }, + { + name: 'multi-metric-vis', + type: 'line', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Average', + field: 'value1', + }, + { + aggregation: 'Average', + field: 'value2', + }, + { + aggregation: 'Max', + field: 'value3', + }, + ], + }, + { + name: 'area-vis', + type: 'area', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Max', + field: 'value2', + }, + ], + }, + { + name: 'vega-vis', + type: 'vega', + indexPattern: indexPatternName, + metrics: [], + }, + ]; + + const visualizationNames = visualizationSpecs.map( + (visualizationSpec) => visualizationSpec.name + ); + + before(() => { + // Create a dashboard and add some visualizations + bootstrapDashboard( + INDEX_SETTINGS_FILEPATH_SIMPLE, + INDEX_PATTERN_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, + indexName, + indexPatternName, + dashboardName, + visualizationSpecs + ); + }); + + beforeEach(() => { + cy.visitDashboard(dashboardName); + }); + + after(() => { + deleteVisAugmenterData( + indexName, + indexPatternName, + visualizationNames, + dashboardName + ); + }); + + it('View events option does not exist for any visualization', () => { + visualizationNames.forEach((visualizationName) => { + cy.getVisPanelByTitle(visualizationName) + .openVisContextMenu() + .getMenuItems() + .contains('View Events') + .should('not.exist'); + }); + }); + + it('Validate non-vega visualizations are not rendered with vega under the hood', () => { + visualizationSpecs.forEach((visualizationSpec) => { + cy.getVisPanelByTitle(visualizationSpec.name).within(() => { + if (visualizationSpec.type === 'vega') { + cy.get('.vgaVis__view').should('exist'); + } else { + cy.get('.vgaVis__view').should('not.exist'); + } + }); + }); + }); + }); +}); diff --git a/cypress/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..7a9c8ee4d --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -0,0 +1,145 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + deleteVisAugmenterData, + bootstrapDashboard, + openAddAnomalyDetectorFlyout, + openAssociatedDetectorsFlyout, + createDetectorFromVis, + associateDetectorFromVis, + unlinkDetectorFromVis, + ensureDetectorIsLinked, + ensureDetectorDetails, + openDetectorDetailsPageFromFlyout, +} from '../../../../utils/helpers'; +import { + INDEX_PATTERN_FILEPATH_SIMPLE, + INDEX_SETTINGS_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, +} from '../../../../utils/constants'; + +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'; + 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('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); + }); + + it('Associate existing detector - creation flow', () => { + 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(); + + associateDetectorFromVis(detectorName); + + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); + }); + + it('Associate existing detector - associated detectors flow', () => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.getElementByTestId('associateDetectorButton').click(); + associateDetectorFromVis(detectorName); + + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); + }); + + 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); + + 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'); + }); +}); 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..6ad1b4302 --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js @@ -0,0 +1,119 @@ +/* + * 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, +} 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 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('Associating a detector creates a visible saved object', () => { + openAddAnomalyDetectorFlyout(dashboardName, visualizationName); + createDetectorFromVis(detectorName); + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + + 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'); + 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 association deletes the saved object', () => { + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); + + 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'); + }); +}); 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..40583a39f --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js @@ -0,0 +1,111 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + deleteVisAugmenterData, + bootstrapDashboard, + openAddAnomalyDetectorFlyout, + createDetectorFromVis, + unlinkDetectorFromVis, + ensureDetectorIsLinked, + 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 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.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/dashboards/commands.js b/cypress/utils/dashboards/commands.js index e7a4e39b1..54615c999 100644 --- a/cypress/utils/dashboards/commands.js +++ b/cypress/utils/dashboards/commands.js @@ -5,6 +5,7 @@ import './vis_builder/commands'; import './vis_type_table/commands'; +import './vis-augmenter/commands'; Cypress.Commands.add('waitForLoader', () => { const opts = { log: false }; diff --git a/cypress/utils/dashboards/constants.js b/cypress/utils/dashboards/constants.js index e16741d1e..c2c797c85 100644 --- a/cypress/utils/dashboards/constants.js +++ b/cypress/utils/dashboards/constants.js @@ -17,3 +17,4 @@ export const SAVED_OBJECTS_PATH = export * from './vis_builder/constants'; export * from './vis_type_table/constants'; +export * from './vis-augmenter/constants'; diff --git a/cypress/utils/dashboards/vis-augmenter/commands.js b/cypress/utils/dashboards/vis-augmenter/commands.js new file mode 100644 index 000000000..e0ee418de --- /dev/null +++ b/cypress/utils/dashboards/vis-augmenter/commands.js @@ -0,0 +1,48 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BASE_PATH } from '../../constants'; + +Cypress.Commands.add('getVisPanelByTitle', (title) => + cy.get(`[data-title="${title}"]`).parents('.embPanel').should('be.visible') +); + +Cypress.Commands.add('openVisContextMenu', { prevSubject: true }, (panel) => + cy + .wrap(panel) + .find(`[data-test-subj="embeddablePanelContextMenuClosed"]`) + .click() + .then(() => cy.get('.euiContextMenu')) +); + +Cypress.Commands.add( + 'clickVisPanelMenuItem', + { prevSubject: 'optional' }, + (menu, text) => + (menu ? cy.wrap(menu) : cy.get('.euiContextMenu')) + .find('button') + .contains(text) + .click() +); + +Cypress.Commands.add('getMenuItems', { prevSubject: 'optional' }, (menu) => + (menu ? cy.wrap(menu) : cy.get('.euiContextMenu')).find('button') +); + +Cypress.Commands.add('visitDashboard', (dashboardName) => { + cy.visit(`${BASE_PATH}/app/dashboards`); + cy.wait(2000); + cy.get('.euiFieldSearch').type(dashboardName); + cy.wait(2000); + cy.get('[data-test-subj="itemsInMemTable"]').contains(dashboardName).click({ + force: true, + }); + 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/constants.js b/cypress/utils/dashboards/vis-augmenter/constants.js new file mode 100644 index 000000000..6c7bfa365 --- /dev/null +++ b/cypress/utils/dashboards/vis-augmenter/constants.js @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +const SAMPLE_DATA_DIR_SIMPLE = + 'dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/'; + +export const SAMPLE_DATA_FILEPATH_SIMPLE = SAMPLE_DATA_DIR_SIMPLE + 'data.json'; + +export const INDEX_PATTERN_FILEPATH_SIMPLE = + SAMPLE_DATA_DIR_SIMPLE + 'index-pattern-fields.txt'; + +export const INDEX_SETTINGS_FILEPATH_SIMPLE = + SAMPLE_DATA_DIR_SIMPLE + 'index-settings.txt'; diff --git a/cypress/utils/dashboards/vis-augmenter/helpers.js b/cypress/utils/dashboards/vis-augmenter/helpers.js new file mode 100644 index 000000000..73ebbf12e --- /dev/null +++ b/cypress/utils/dashboards/vis-augmenter/helpers.js @@ -0,0 +1,325 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { isEmpty } from 'lodash'; +import { MiscUtils } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; + +const apiRequest = (url, method = 'POST', body = undefined, qs = undefined) => + cy.request({ + method: method, + failOnStatusCode: false, + url: url, + headers: { + 'content-type': 'application/json', + 'osd-xsrf': true, + }, + body: body, + qs: qs, + }); + +const devToolsRequest = ( + url, + method = 'POST', + body = undefined, + qs = undefined +) => + cy.request({ + method: 'POST', + form: false, + failOnStatusCode: false, + url: encodeURI(`api/console/proxy?path=${url}&method=${method}`), + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + body: body, + qs: qs, + }); + +/** + * Cleans up the index & all associated saved objects (index pattern, visualizations, + * dashboards, etc.) created during the test run + */ +export const deleteVisAugmenterData = ( + indexName, + indexPatternName, + visualizationNames, + dashboardName +) => { + devToolsRequest(indexName, 'DELETE').then(() => { + apiRequest( + `api/saved_objects/index-pattern/${indexPatternName}`, + 'DELETE' + ).then(() => { + apiRequest( + ` +api/opensearch-dashboards/management/saved_objects/_find?perPage=5000&page=1&fields=id&type=config&type=url&type=index-pattern&type=query&type=dashboard&type=visualization&type=visualization-visbuilder&type=augment-vis&type=search&sortField=type`, + 'GET' + ).then((response) => { + response.body.saved_objects.forEach((obj) => { + if ( + obj.type !== 'config' && + [ + indexName, + indexPatternName, + ...visualizationNames, + dashboardName, + ].indexOf(obj.meta.title) !== -1 + ) { + apiRequest( + `api/saved_objects/${obj.type}/${obj.id}?force=true`, + 'DELETE' + ); + } + }); + }); + }); + }); +}; + +/** + * Fetch the fixtures to create and configure an index, index pattern, + * and ingesting sample data to the index + */ +export const ingestVisAugmenterData = ( + indexName, + indexPatternName, + indexSettingsFilepath, + indexPatternFieldsFilepath, + sampleDataFilepath +) => { + cy.fixture(indexSettingsFilepath).then((indexSettings) => + devToolsRequest(indexName, 'PUT', indexSettings) + ); + + cy.fixture(indexPatternFieldsFilepath).then((fields) => { + apiRequest( + `api/saved_objects/index-pattern/${indexPatternName}`, + 'POST', + JSON.stringify({ + attributes: { + fields: fields, + title: indexPatternName, + timeFieldName: '@timestamp', + }, + }) + ); + }); + + cy.fixture(sampleDataFilepath).then((indexData) => { + indexData.forEach((item, idx) => { + let date = new Date(); + item['@timestamp'] = date.setMinutes(date.getMinutes() - 1); + devToolsRequest(`${indexName}/_doc/${idx}`, 'POST', JSON.stringify(item)); + }); + }); +}; + +/** + * Creating a new visualization from a dashboard, and finishing at the + * vis edit page + */ +const bootstrapCreateFromDashboard = (visType, indexPatternName) => { + cy.getElementByTestId('dashboardAddNewPanelButton') + .should('be.visible') + .click(); + + cy.getElementByTestId(`visType-${visType}`).click(); + + // vega charts don't have a secondary modal to configure the + // index pattern / saved search. Skip those steps here + if (visType !== 'vega') { + cy.getElementByTestId('savedObjectFinderSearchInput').type( + `${indexPatternName}{enter}` + ); + cy.get(`[title="${indexPatternName}"]`).click(); + } +}; + +const setXAxisDateHistogram = () => { + cy.get('.euiTitle') + .contains('Buckets') + .parent() + .find('[data-test-subj="visEditorAdd_buckets"]') + .click(); + + cy.getElementByTestId('visEditorAdd_buckets_X-axis').click(); + + cy.get('.euiTitle') + .contains('Buckets') + .parent() + .within(() => { + cy.wait(1000); + cy.getElementByTestId('comboBoxInput') + .find('input') + .type('Date Histogram{enter}', { force: true }); + }); +}; + +/** + * From the vis edit view, return to the dashboard + */ +const saveVisualizationAndReturn = (visualizationName) => { + cy.getElementByTestId('visualizeEditorRenderButton').click({ + force: true, + }); + cy.getElementByTestId('visualizeSaveButton').click({ + force: true, + }); + cy.getElementByTestId('savedObjectTitle').type(visualizationName); + cy.getElementByTestId('confirmSaveSavedObjectButton').click({ + force: true, + }); +}; + +/** + * Creates a list of specified metrics under the y-axis section + * in the vis editor page + */ +const setYAxis = (metrics) => { + metrics.forEach((metric, index) => { + // There is always a default count metric populated. So, for the first + // added metric, we need to overwrite it. For additional metrics, we + // can just click the "Add" button + if (index === 0) { + cy.getElementByTestId('metricsAggGroup') + .find('.euiAccordion__button') + .click({ force: true }); + } else { + cy.getElementByTestId('visEditorAdd_metrics').click(); + cy.getElementByTestId('visEditorAdd_metrics_Y-axis').click(); + } + addMetric(metric, index); + }); +}; + +/** + * Adds a metric to the specified index in the vis editor page + */ +const addMetric = (metric, index) => { + cy.getElementByTestId('metricsAggGroup') + .find(`[data-test-subj="visEditorAggAccordion${index + 1}"]`) + .within(() => { + cy.wait(1000); + cy.getElementByTestId('comboBoxSearchInput').type( + `${metric.aggregation}{downarrow}{enter}`, + { + force: true, + } + ); + cy.contains(`${metric.aggregation}`).click({ force: true }); + }); + + // non-count aggregations will have an additional field value to set + if (metric.aggregation !== 'Count' && metric.aggregation !== 'count') { + cy.getElementByTestId('metricsAggGroup') + .find(`[data-test-subj="visEditorAggAccordion${index + 1}"]`) + .find('[data-test-subj="visDefaultEditorField"]') + .within(() => { + cy.wait(1000); + cy.getElementByTestId('comboBoxSearchInput').type( + `${metric.field}{downarrow}{enter}`, + { + force: true, + } + ); + }); + } + + // re-collapse the accordion + cy.getElementByTestId(`visEditorAggAccordion${index + 1}`) + .find('.euiAccordion__button') + .first() + .click({ force: true }); +}; + +/** + * Creates an individual visualization, assuming runner is + * starting from dashboard edit view. + */ +export const createVisualizationFromDashboard = ( + visType, + indexPatternName, + visualizationName, + metrics +) => { + bootstrapCreateFromDashboard(visType, indexPatternName); + + // Vega visualizations don't configure axes the same way, + // so ignore those here. Note we still want to support the vega type, + // but don't support any custom specs, as the default spec may be + // sufficient for now + if (visType !== 'vega') { + if (!isEmpty(metrics)) { + setYAxis(metrics); + } + setXAxisDateHistogram(); + } + saveVisualizationAndReturn(visualizationName); +}; + +/** + * Ingests the specified sample data, creates and saves a specified + * list of visualizations, and saves them all to a new dashboard + */ +export const bootstrapDashboard = ( + indexSettingsFilepath, + indexPatternFieldsFilepath, + sampleDataFilepath, + indexName, + indexPatternName, + dashboardName, + visualizationSpecs +) => { + const miscUtils = new MiscUtils(cy); + deleteVisAugmenterData( + indexName, + indexPatternName, + visualizationSpecs.map((visualizationSpec) => visualizationSpec.name), + dashboardName + ); + ingestVisAugmenterData( + indexName, + indexPatternName, + indexSettingsFilepath, + indexPatternFieldsFilepath, + sampleDataFilepath + ); + + miscUtils.visitPage('app/dashboards'); + + cy.getElementByTestId('createDashboardPromptButton') + .should('be.visible') + .click(); + + // Create several different visualizations + visualizationSpecs.forEach((visualizationSpec) => { + createVisualizationFromDashboard( + visualizationSpec.type, + visualizationSpec.indexPattern, + visualizationSpec.name, + visualizationSpec.metrics + ); + }); + + cy.getElementByTestId('dashboardSaveMenuItem').click({ + force: true, + }); + + cy.getElementByTestId('savedObjectTitle').type(dashboardName); + + cy.getElementByTestId('confirmSaveSavedObjectButton').click({ + force: true, + }); +}; + +export const filterByObjectType = (type) => { + cy.get('.euiFilterButton').click(); + cy.get('.euiFilterSelect__items') + .find('button') + .contains(type) + .click({ force: true }); + cy.wait(3000); +}; diff --git a/cypress/utils/dashboards/vis-augmenter/index.d.ts b/cypress/utils/dashboards/vis-augmenter/index.d.ts new file mode 100644 index 000000000..ded2d27d3 --- /dev/null +++ b/cypress/utils/dashboards/vis-augmenter/index.d.ts @@ -0,0 +1,45 @@ +// type definitions for custom commands like "createDefaultTodos" +/// + +declare namespace Cypress { + interface Chainable { + /** + * Returns visualization panel by title + * @example + * cy.getVisPanelByTitle('[Logs] Visitors by OS') + */ + getVisPanelByTitle(title: string): Chainable; + + /** + * Opens vis panel context menu + * @example + * cy.get('visPanel').openVisContextMenu() + */ + openVisContextMenu(): Chainable; + + /** + * Clicks vis panel context menu item + * @example + * cy.clickVisPanelMenuItem('Alerting') + */ + clickVisPanelMenuItem(text: string): Chainable; + + /** + * Gets all items in the context menu + * @example + * cy.getVisPanelByTitle('my-visualization') + .openVisContextMenu() + .getMenuItems() + .contains('View Events') + .should('exist'); + */ + getMenuItems(): Chainable; + + /** + * Visits a dashboard + * @example + * cy.visitDashboard('My-Dashboard') + */ + visitDashboard(dashboardName: string): Chainable; + } +} diff --git a/cypress/utils/helpers.js b/cypress/utils/helpers.js index a64e431fa..bb67116f6 100644 --- a/cypress/utils/helpers.js +++ b/cypress/utils/helpers.js @@ -4,3 +4,4 @@ */ export * from './plugins/anomaly-detection-dashboards-plugin/helpers'; +export * from './dashboards/vis-augmenter/helpers'; diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js index 8388339d9..96bda3d93 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,117 @@ 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 openDetectorDetailsPageFromFlyout = () => { + cy.get('.euiBasicTable').find('.euiLink').click(); +}; + +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'); +}; + +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"]') + .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(2000); + cy.get('.euiFieldSearch').type(detectorName); + 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, + detectorName +) => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.wait(2000); + cy.get('.euiFieldSearch').type(detectorName); + cy.wait(1000); + cy.getElementByTestId('unlinkButton').click(); + cy.getElementByTestId('confirmUnlinkButton').click(); + cy.wait(5000); +}; From 9a74842aab7ab788523e6ef51ed973fc3577a891 Mon Sep 17 00:00:00 2001 From: Manasvini B Suryanarayana Date: Tue, 11 Jul 2023 17:24:19 -0700 Subject: [PATCH 22/46] Revert "[Vis Augmenter / Feature Anywhere] Add tests in core OSD and AD plugin (#739)" (#748) This reverts commit 07a67d724710109bd95800dd5ce973e83dbbe10a. Signed-off-by: manasvinibs Signed-off-by: leanne.laceybyrne@eliatra.com --- .../sample-data-simple/data.json | 27 -- .../index-pattern-fields.txt | 1 - .../sample-data-simple/index-settings.txt | 1 - .../apps/vis-augmenter/dashboard_spec.js | 129 ------- .../sample_detector_spec.js | 20 +- .../vis_augmenter/associate_detector_spec.js | 145 -------- .../augment_vis_saved_object_spec.js | 119 ------- .../vis_augmenter/view_anomaly_events_spec.js | 111 ------ cypress/utils/dashboards/commands.js | 1 - cypress/utils/dashboards/constants.js | 1 - .../dashboards/vis-augmenter/commands.js | 48 --- .../dashboards/vis-augmenter/constants.js | 15 - .../utils/dashboards/vis-augmenter/helpers.js | 325 ------------------ .../utils/dashboards/vis-augmenter/index.d.ts | 45 --- cypress/utils/helpers.js | 1 - .../helpers.js | 116 ------- 16 files changed, 19 insertions(+), 1086 deletions(-) delete mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json delete mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt delete mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt delete mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js delete mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js delete mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js delete mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js delete mode 100644 cypress/utils/dashboards/vis-augmenter/commands.js delete mode 100644 cypress/utils/dashboards/vis-augmenter/constants.js delete mode 100644 cypress/utils/dashboards/vis-augmenter/helpers.js delete mode 100644 cypress/utils/dashboards/vis-augmenter/index.d.ts diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json deleted file mode 100644 index 5124ea41e..000000000 --- a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "value1": 1, - "value2": 10, - "value3": 5 - }, - { - "value1": 5, - "value2": 1, - "value3": 3 - }, - { - "value1": 9, - "value2": 6, - "value3": 2 - }, - { - "value1": 2, - "value2": 1, - "value3": 1 - }, - { - "value1": 12, - "value2": 5, - "value3": 4 - } -] diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt deleted file mode 100644 index 2e010deba..000000000 --- a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/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":"value1","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"value2","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"value3","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false}] \ No newline at end of file diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt deleted file mode 100644 index d4d78055d..000000000 --- a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt +++ /dev/null @@ -1 +0,0 @@ -{"mappings":{"properties":{"value1":{"type":"integer"},"value2":{"type":"integer"},"value3":{"type":"integer"},"@timestamp":{"type":"date", "format":"epoch_millis"}}},"settings":{"index":{"number_of_shards":"1","number_of_replicas":"1"}}} \ No newline at end of file diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js deleted file mode 100644 index d97647c55..000000000 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - INDEX_PATTERN_FILEPATH_SIMPLE, - INDEX_SETTINGS_FILEPATH_SIMPLE, - SAMPLE_DATA_FILEPATH_SIMPLE, -} from '../../../../../utils/constants'; -import { - deleteVisAugmenterData, - bootstrapDashboard, -} from '../../../../../utils/dashboards/vis-augmenter/helpers'; - -describe('Vis augmenter - existing dashboards work as expected', () => { - describe('dashboard with ineligible, eligible, and vega visualizations', () => { - const indexName = 'vis-augmenter-sample-index'; - const indexPatternName = 'vis-augmenter-sample-*'; - const dashboardName = 'Vis Augmenter Dashboard'; - const visualizationSpecs = [ - { - name: 'count-agg-vis', - type: 'line', - indexPattern: indexPatternName, - metrics: [], - }, - { - name: 'single-metric-vis', - type: 'line', - indexPattern: indexPatternName, - metrics: [ - { - aggregation: 'Average', - field: 'value1', - }, - ], - }, - { - name: 'multi-metric-vis', - type: 'line', - indexPattern: indexPatternName, - metrics: [ - { - aggregation: 'Average', - field: 'value1', - }, - { - aggregation: 'Average', - field: 'value2', - }, - { - aggregation: 'Max', - field: 'value3', - }, - ], - }, - { - name: 'area-vis', - type: 'area', - indexPattern: indexPatternName, - metrics: [ - { - aggregation: 'Max', - field: 'value2', - }, - ], - }, - { - name: 'vega-vis', - type: 'vega', - indexPattern: indexPatternName, - metrics: [], - }, - ]; - - const visualizationNames = visualizationSpecs.map( - (visualizationSpec) => visualizationSpec.name - ); - - before(() => { - // Create a dashboard and add some visualizations - bootstrapDashboard( - INDEX_SETTINGS_FILEPATH_SIMPLE, - INDEX_PATTERN_FILEPATH_SIMPLE, - SAMPLE_DATA_FILEPATH_SIMPLE, - indexName, - indexPatternName, - dashboardName, - visualizationSpecs - ); - }); - - beforeEach(() => { - cy.visitDashboard(dashboardName); - }); - - after(() => { - deleteVisAugmenterData( - indexName, - indexPatternName, - visualizationNames, - dashboardName - ); - }); - - it('View events option does not exist for any visualization', () => { - visualizationNames.forEach((visualizationName) => { - cy.getVisPanelByTitle(visualizationName) - .openVisContextMenu() - .getMenuItems() - .contains('View Events') - .should('not.exist'); - }); - }); - - it('Validate non-vega visualizations are not rendered with vega under the hood', () => { - visualizationSpecs.forEach((visualizationSpec) => { - cy.getVisPanelByTitle(visualizationSpec.name).within(() => { - if (visualizationSpec.type === 'vega') { - cy.get('.vgaVis__view').should('exist'); - } else { - cy.get('.vgaVis__view').should('not.exist'); - } - }); - }); - }); - }); -}); diff --git a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/sample_detector_spec.js b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/sample_detector_spec.js index ebcab7e29..8008eabbf 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,9 +3,27 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { createSampleDetector } from '../../../utils/helpers'; +import { AD_URL } from '../../../utils/constants'; 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 deleted file mode 100644 index 7a9c8ee4d..000000000 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - deleteVisAugmenterData, - bootstrapDashboard, - openAddAnomalyDetectorFlyout, - openAssociatedDetectorsFlyout, - createDetectorFromVis, - associateDetectorFromVis, - unlinkDetectorFromVis, - ensureDetectorIsLinked, - ensureDetectorDetails, - openDetectorDetailsPageFromFlyout, -} from '../../../../utils/helpers'; -import { - INDEX_PATTERN_FILEPATH_SIMPLE, - INDEX_SETTINGS_FILEPATH_SIMPLE, - SAMPLE_DATA_FILEPATH_SIMPLE, -} from '../../../../utils/constants'; - -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'; - 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('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); - }); - - it('Associate existing detector - creation flow', () => { - 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(); - - associateDetectorFromVis(detectorName); - - ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); - unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); - }); - - it('Associate existing detector - associated detectors flow', () => { - openAssociatedDetectorsFlyout(dashboardName, visualizationName); - cy.getElementByTestId('associateDetectorButton').click(); - associateDetectorFromVis(detectorName); - - ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); - unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); - }); - - 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); - - 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'); - }); -}); 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 deleted file mode 100644 index 6ad1b4302..000000000 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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, -} 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 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('Associating a detector creates a visible saved object', () => { - openAddAnomalyDetectorFlyout(dashboardName, visualizationName); - createDetectorFromVis(detectorName); - ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); - - 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'); - 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 association deletes the saved object', () => { - unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); - - 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'); - }); -}); 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 deleted file mode 100644 index 40583a39f..000000000 --- a/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - deleteVisAugmenterData, - bootstrapDashboard, - openAddAnomalyDetectorFlyout, - createDetectorFromVis, - unlinkDetectorFromVis, - ensureDetectorIsLinked, - 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 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.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/dashboards/commands.js b/cypress/utils/dashboards/commands.js index 54615c999..e7a4e39b1 100644 --- a/cypress/utils/dashboards/commands.js +++ b/cypress/utils/dashboards/commands.js @@ -5,7 +5,6 @@ import './vis_builder/commands'; import './vis_type_table/commands'; -import './vis-augmenter/commands'; Cypress.Commands.add('waitForLoader', () => { const opts = { log: false }; diff --git a/cypress/utils/dashboards/constants.js b/cypress/utils/dashboards/constants.js index c2c797c85..e16741d1e 100644 --- a/cypress/utils/dashboards/constants.js +++ b/cypress/utils/dashboards/constants.js @@ -17,4 +17,3 @@ export const SAVED_OBJECTS_PATH = export * from './vis_builder/constants'; export * from './vis_type_table/constants'; -export * from './vis-augmenter/constants'; diff --git a/cypress/utils/dashboards/vis-augmenter/commands.js b/cypress/utils/dashboards/vis-augmenter/commands.js deleted file mode 100644 index e0ee418de..000000000 --- a/cypress/utils/dashboards/vis-augmenter/commands.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BASE_PATH } from '../../constants'; - -Cypress.Commands.add('getVisPanelByTitle', (title) => - cy.get(`[data-title="${title}"]`).parents('.embPanel').should('be.visible') -); - -Cypress.Commands.add('openVisContextMenu', { prevSubject: true }, (panel) => - cy - .wrap(panel) - .find(`[data-test-subj="embeddablePanelContextMenuClosed"]`) - .click() - .then(() => cy.get('.euiContextMenu')) -); - -Cypress.Commands.add( - 'clickVisPanelMenuItem', - { prevSubject: 'optional' }, - (menu, text) => - (menu ? cy.wrap(menu) : cy.get('.euiContextMenu')) - .find('button') - .contains(text) - .click() -); - -Cypress.Commands.add('getMenuItems', { prevSubject: 'optional' }, (menu) => - (menu ? cy.wrap(menu) : cy.get('.euiContextMenu')).find('button') -); - -Cypress.Commands.add('visitDashboard', (dashboardName) => { - cy.visit(`${BASE_PATH}/app/dashboards`); - cy.wait(2000); - cy.get('.euiFieldSearch').type(dashboardName); - cy.wait(2000); - cy.get('[data-test-subj="itemsInMemTable"]').contains(dashboardName).click({ - force: true, - }); - 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/constants.js b/cypress/utils/dashboards/vis-augmenter/constants.js deleted file mode 100644 index 6c7bfa365..000000000 --- a/cypress/utils/dashboards/vis-augmenter/constants.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -const SAMPLE_DATA_DIR_SIMPLE = - 'dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/'; - -export const SAMPLE_DATA_FILEPATH_SIMPLE = SAMPLE_DATA_DIR_SIMPLE + 'data.json'; - -export const INDEX_PATTERN_FILEPATH_SIMPLE = - SAMPLE_DATA_DIR_SIMPLE + 'index-pattern-fields.txt'; - -export const INDEX_SETTINGS_FILEPATH_SIMPLE = - SAMPLE_DATA_DIR_SIMPLE + 'index-settings.txt'; diff --git a/cypress/utils/dashboards/vis-augmenter/helpers.js b/cypress/utils/dashboards/vis-augmenter/helpers.js deleted file mode 100644 index 73ebbf12e..000000000 --- a/cypress/utils/dashboards/vis-augmenter/helpers.js +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { isEmpty } from 'lodash'; -import { MiscUtils } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; - -const apiRequest = (url, method = 'POST', body = undefined, qs = undefined) => - cy.request({ - method: method, - failOnStatusCode: false, - url: url, - headers: { - 'content-type': 'application/json', - 'osd-xsrf': true, - }, - body: body, - qs: qs, - }); - -const devToolsRequest = ( - url, - method = 'POST', - body = undefined, - qs = undefined -) => - cy.request({ - method: 'POST', - form: false, - failOnStatusCode: false, - url: encodeURI(`api/console/proxy?path=${url}&method=${method}`), - headers: { - 'content-type': 'application/json;charset=UTF-8', - 'osd-xsrf': true, - }, - body: body, - qs: qs, - }); - -/** - * Cleans up the index & all associated saved objects (index pattern, visualizations, - * dashboards, etc.) created during the test run - */ -export const deleteVisAugmenterData = ( - indexName, - indexPatternName, - visualizationNames, - dashboardName -) => { - devToolsRequest(indexName, 'DELETE').then(() => { - apiRequest( - `api/saved_objects/index-pattern/${indexPatternName}`, - 'DELETE' - ).then(() => { - apiRequest( - ` -api/opensearch-dashboards/management/saved_objects/_find?perPage=5000&page=1&fields=id&type=config&type=url&type=index-pattern&type=query&type=dashboard&type=visualization&type=visualization-visbuilder&type=augment-vis&type=search&sortField=type`, - 'GET' - ).then((response) => { - response.body.saved_objects.forEach((obj) => { - if ( - obj.type !== 'config' && - [ - indexName, - indexPatternName, - ...visualizationNames, - dashboardName, - ].indexOf(obj.meta.title) !== -1 - ) { - apiRequest( - `api/saved_objects/${obj.type}/${obj.id}?force=true`, - 'DELETE' - ); - } - }); - }); - }); - }); -}; - -/** - * Fetch the fixtures to create and configure an index, index pattern, - * and ingesting sample data to the index - */ -export const ingestVisAugmenterData = ( - indexName, - indexPatternName, - indexSettingsFilepath, - indexPatternFieldsFilepath, - sampleDataFilepath -) => { - cy.fixture(indexSettingsFilepath).then((indexSettings) => - devToolsRequest(indexName, 'PUT', indexSettings) - ); - - cy.fixture(indexPatternFieldsFilepath).then((fields) => { - apiRequest( - `api/saved_objects/index-pattern/${indexPatternName}`, - 'POST', - JSON.stringify({ - attributes: { - fields: fields, - title: indexPatternName, - timeFieldName: '@timestamp', - }, - }) - ); - }); - - cy.fixture(sampleDataFilepath).then((indexData) => { - indexData.forEach((item, idx) => { - let date = new Date(); - item['@timestamp'] = date.setMinutes(date.getMinutes() - 1); - devToolsRequest(`${indexName}/_doc/${idx}`, 'POST', JSON.stringify(item)); - }); - }); -}; - -/** - * Creating a new visualization from a dashboard, and finishing at the - * vis edit page - */ -const bootstrapCreateFromDashboard = (visType, indexPatternName) => { - cy.getElementByTestId('dashboardAddNewPanelButton') - .should('be.visible') - .click(); - - cy.getElementByTestId(`visType-${visType}`).click(); - - // vega charts don't have a secondary modal to configure the - // index pattern / saved search. Skip those steps here - if (visType !== 'vega') { - cy.getElementByTestId('savedObjectFinderSearchInput').type( - `${indexPatternName}{enter}` - ); - cy.get(`[title="${indexPatternName}"]`).click(); - } -}; - -const setXAxisDateHistogram = () => { - cy.get('.euiTitle') - .contains('Buckets') - .parent() - .find('[data-test-subj="visEditorAdd_buckets"]') - .click(); - - cy.getElementByTestId('visEditorAdd_buckets_X-axis').click(); - - cy.get('.euiTitle') - .contains('Buckets') - .parent() - .within(() => { - cy.wait(1000); - cy.getElementByTestId('comboBoxInput') - .find('input') - .type('Date Histogram{enter}', { force: true }); - }); -}; - -/** - * From the vis edit view, return to the dashboard - */ -const saveVisualizationAndReturn = (visualizationName) => { - cy.getElementByTestId('visualizeEditorRenderButton').click({ - force: true, - }); - cy.getElementByTestId('visualizeSaveButton').click({ - force: true, - }); - cy.getElementByTestId('savedObjectTitle').type(visualizationName); - cy.getElementByTestId('confirmSaveSavedObjectButton').click({ - force: true, - }); -}; - -/** - * Creates a list of specified metrics under the y-axis section - * in the vis editor page - */ -const setYAxis = (metrics) => { - metrics.forEach((metric, index) => { - // There is always a default count metric populated. So, for the first - // added metric, we need to overwrite it. For additional metrics, we - // can just click the "Add" button - if (index === 0) { - cy.getElementByTestId('metricsAggGroup') - .find('.euiAccordion__button') - .click({ force: true }); - } else { - cy.getElementByTestId('visEditorAdd_metrics').click(); - cy.getElementByTestId('visEditorAdd_metrics_Y-axis').click(); - } - addMetric(metric, index); - }); -}; - -/** - * Adds a metric to the specified index in the vis editor page - */ -const addMetric = (metric, index) => { - cy.getElementByTestId('metricsAggGroup') - .find(`[data-test-subj="visEditorAggAccordion${index + 1}"]`) - .within(() => { - cy.wait(1000); - cy.getElementByTestId('comboBoxSearchInput').type( - `${metric.aggregation}{downarrow}{enter}`, - { - force: true, - } - ); - cy.contains(`${metric.aggregation}`).click({ force: true }); - }); - - // non-count aggregations will have an additional field value to set - if (metric.aggregation !== 'Count' && metric.aggregation !== 'count') { - cy.getElementByTestId('metricsAggGroup') - .find(`[data-test-subj="visEditorAggAccordion${index + 1}"]`) - .find('[data-test-subj="visDefaultEditorField"]') - .within(() => { - cy.wait(1000); - cy.getElementByTestId('comboBoxSearchInput').type( - `${metric.field}{downarrow}{enter}`, - { - force: true, - } - ); - }); - } - - // re-collapse the accordion - cy.getElementByTestId(`visEditorAggAccordion${index + 1}`) - .find('.euiAccordion__button') - .first() - .click({ force: true }); -}; - -/** - * Creates an individual visualization, assuming runner is - * starting from dashboard edit view. - */ -export const createVisualizationFromDashboard = ( - visType, - indexPatternName, - visualizationName, - metrics -) => { - bootstrapCreateFromDashboard(visType, indexPatternName); - - // Vega visualizations don't configure axes the same way, - // so ignore those here. Note we still want to support the vega type, - // but don't support any custom specs, as the default spec may be - // sufficient for now - if (visType !== 'vega') { - if (!isEmpty(metrics)) { - setYAxis(metrics); - } - setXAxisDateHistogram(); - } - saveVisualizationAndReturn(visualizationName); -}; - -/** - * Ingests the specified sample data, creates and saves a specified - * list of visualizations, and saves them all to a new dashboard - */ -export const bootstrapDashboard = ( - indexSettingsFilepath, - indexPatternFieldsFilepath, - sampleDataFilepath, - indexName, - indexPatternName, - dashboardName, - visualizationSpecs -) => { - const miscUtils = new MiscUtils(cy); - deleteVisAugmenterData( - indexName, - indexPatternName, - visualizationSpecs.map((visualizationSpec) => visualizationSpec.name), - dashboardName - ); - ingestVisAugmenterData( - indexName, - indexPatternName, - indexSettingsFilepath, - indexPatternFieldsFilepath, - sampleDataFilepath - ); - - miscUtils.visitPage('app/dashboards'); - - cy.getElementByTestId('createDashboardPromptButton') - .should('be.visible') - .click(); - - // Create several different visualizations - visualizationSpecs.forEach((visualizationSpec) => { - createVisualizationFromDashboard( - visualizationSpec.type, - visualizationSpec.indexPattern, - visualizationSpec.name, - visualizationSpec.metrics - ); - }); - - cy.getElementByTestId('dashboardSaveMenuItem').click({ - force: true, - }); - - cy.getElementByTestId('savedObjectTitle').type(dashboardName); - - cy.getElementByTestId('confirmSaveSavedObjectButton').click({ - force: true, - }); -}; - -export const filterByObjectType = (type) => { - cy.get('.euiFilterButton').click(); - cy.get('.euiFilterSelect__items') - .find('button') - .contains(type) - .click({ force: true }); - cy.wait(3000); -}; diff --git a/cypress/utils/dashboards/vis-augmenter/index.d.ts b/cypress/utils/dashboards/vis-augmenter/index.d.ts deleted file mode 100644 index ded2d27d3..000000000 --- a/cypress/utils/dashboards/vis-augmenter/index.d.ts +++ /dev/null @@ -1,45 +0,0 @@ -// type definitions for custom commands like "createDefaultTodos" -/// - -declare namespace Cypress { - interface Chainable { - /** - * Returns visualization panel by title - * @example - * cy.getVisPanelByTitle('[Logs] Visitors by OS') - */ - getVisPanelByTitle(title: string): Chainable; - - /** - * Opens vis panel context menu - * @example - * cy.get('visPanel').openVisContextMenu() - */ - openVisContextMenu(): Chainable; - - /** - * Clicks vis panel context menu item - * @example - * cy.clickVisPanelMenuItem('Alerting') - */ - clickVisPanelMenuItem(text: string): Chainable; - - /** - * Gets all items in the context menu - * @example - * cy.getVisPanelByTitle('my-visualization') - .openVisContextMenu() - .getMenuItems() - .contains('View Events') - .should('exist'); - */ - getMenuItems(): Chainable; - - /** - * Visits a dashboard - * @example - * cy.visitDashboard('My-Dashboard') - */ - visitDashboard(dashboardName: string): Chainable; - } -} diff --git a/cypress/utils/helpers.js b/cypress/utils/helpers.js index bb67116f6..a64e431fa 100644 --- a/cypress/utils/helpers.js +++ b/cypress/utils/helpers.js @@ -4,4 +4,3 @@ */ export * from './plugins/anomaly-detection-dashboards-plugin/helpers'; -export * from './dashboards/vis-augmenter/helpers'; diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js index 96bda3d93..8388339d9 100644 --- a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js +++ b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js @@ -3,8 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AD_URL } from './constants'; - export const selectTopItemFromFilter = ( dataTestSubjectName, allowMultipleSelections = true @@ -23,117 +21,3 @@ 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 openDetectorDetailsPageFromFlyout = () => { - cy.get('.euiBasicTable').find('.euiLink').click(); -}; - -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'); -}; - -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"]') - .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(2000); - cy.get('.euiFieldSearch').type(detectorName); - 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, - detectorName -) => { - openAssociatedDetectorsFlyout(dashboardName, visualizationName); - cy.wait(2000); - cy.get('.euiFieldSearch').type(detectorName); - cy.wait(1000); - cy.getElementByTestId('unlinkButton').click(); - cy.getElementByTestId('confirmUnlinkButton').click(); - cy.wait(5000); -}; From f15d9bacc6e809946ddacded9f28e4aa956c1877 Mon Sep 17 00:00:00 2001 From: Kristen Tian <105667444+kristenTian@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:59:19 -0700 Subject: [PATCH 23/46] Test sample data with multiple data source enabled on local cluster (#756) Signed-off-by: Kristen Tian Signed-off-by: leanne.laceybyrne@eliatra.com --- ...hboard_sample_data_with_datasource_spec.js | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/dashboard_sample_data_with_datasource_spec.js diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/dashboard_sample_data_with_datasource_spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/dashboard_sample_data_with_datasource_spec.js new file mode 100644 index 000000000..0056a537e --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/dashboard_sample_data_with_datasource_spec.js @@ -0,0 +1,166 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + CommonUI, + MiscUtils, +} from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; + +const commonUI = new CommonUI(cy); +const miscUtils = new MiscUtils(cy); +const baseURL = new URL(Cypress.config().baseUrl); +// remove trailing slash +const path = baseURL.pathname.replace(/\/$/, ''); + +if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { + describe('dashboard local cluster sample data validation', () => { + before(() => {}); + + after(() => {}); + + describe('checking home page', () => { + before(() => { + // Go to the home page + miscUtils.visitPage('app/home#'); + cy.window().then((win) => + win.localStorage.setItem('home:welcome:show', false) + ); + cy.reload(true); + }); + + after(() => { + cy.window().then((win) => + win.localStorage.removeItem('home:welcome:show') + ); + }); + + it('checking tutorial_directory display', () => { + // Check that tutorial_directory is visible + commonUI.checkElementExists( + `a[href="${path}/app/home#/tutorial_directory"]`, + 2 + ); + }); + }); + + describe('adding sample data', () => { + before(() => { + miscUtils.addSampleData(); + }); + + after(() => { + miscUtils.removeSampleData(); + }); + + it('checking ecommerce dashboards displayed', () => { + miscUtils.viewData('ecommerce'); + commonUI.checkElementContainsValue( + 'span[title="[eCommerce] Revenue Dashboard"]', + 1, + '\\[eCommerce\\] Revenue Dashboard' + ); + commonUI.checkElementContainsValue( + 'div[data-test-subj="markdownBody"] > h3', + 1, + 'Sample eCommerce Data' + ); + }); + + it('checking flights dashboards displayed', () => { + miscUtils.viewData('flights'); + commonUI.checkElementContainsValue( + 'span[title="[Flights] Global Flight Dashboard"]', + 1, + '\\[Flights\\] Global Flight Dashboard' + ); + commonUI.checkElementContainsValue( + 'div[data-test-subj="markdownBody"] > h3', + 1, + 'Sample Flight data' + ); + }); + + it('checking web logs dashboards displayed', () => { + miscUtils.viewData('logs'); + commonUI.checkElementContainsValue( + 'span[title="[Logs] Web Traffic"]', + 1, + '\\[Logs\\] Web Traffic' + ); + commonUI.checkElementContainsValue( + 'div[data-test-subj="markdownBody"] > h3', + 1, + 'Sample Logs Data' + ); + }); + + describe('checking index patterns', () => { + before(() => { + miscUtils.visitPage( + 'app/management/opensearch-dashboards/indexPatterns' + ); + }); + + after(() => {}); + + it('checking ecommerce index patterns are added', () => { + commonUI.checkElementContainsValue( + 'div[data-test-subj="indexPatternTable"]', + 1, + 'opensearch_dashboards_sample_data_ecommerce' + ); + }); + + it('checking flights index patterns are added', () => { + commonUI.checkElementContainsValue( + 'div[data-test-subj="indexPatternTable"]', + 1, + 'opensearch_dashboards_sample_data_flights' + ); + }); + + it('checking web logs index patterns are added', () => { + commonUI.checkElementContainsValue( + 'div[data-test-subj="indexPatternTable"]', + 1, + 'opensearch_dashboards_sample_data_logs' + ); + }); + }); + + describe('checking saved objects', () => { + before(() => { + miscUtils.visitPage('app/management/opensearch-dashboards/objects'); + }); + + after(() => {}); + + it('checking ecommerce object is saved', () => { + commonUI.checkElementContainsValue( + 'div[data-test-subj="savedObjectsTable"]', + 1, + 'opensearch_dashboards_sample_data_ecommerce' + ); + }); + + it('checking flights object is saved', () => { + commonUI.checkElementContainsValue( + 'div[data-test-subj="savedObjectsTable"]', + 1, + 'opensearch_dashboards_sample_data_flights' + ); + }); + + it('checking web logs object is saved', () => { + commonUI.checkElementContainsValue( + 'div[data-test-subj="savedObjectsTable"]', + 1, + 'opensearch_dashboards_sample_data_logs' + ); + }); + }); + }); + }); +} From 2a8fd3ac249890e6b197a536d1aeb85a520282ad Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Thu, 13 Jul 2023 16:34:42 -0700 Subject: [PATCH 24/46] [Vis Augmenter] Add tests in OSD & AD plugin (#752) * [Vis Augmenter / Feature Anywhere] Add test suite for vanilla OSD + helper fns for plugins (#725) * feature anywhere initial tests Signed-off-by: Jovan Cvetkovic * Add test suite Signed-off-by: Tyler Ohlsen * Remove unnecessary test case Signed-off-by: Tyler Ohlsen * Optimize getters Signed-off-by: Tyler Ohlsen --------- Signed-off-by: Jovan Cvetkovic Signed-off-by: Tyler Ohlsen Co-authored-by: Jovan Cvetkovic * [Feature Anywhere / Vis Augmenter] Add test flows for integration with AD plugin (#727) * feature anywhere initial tests Signed-off-by: Jovan Cvetkovic * Add test suite Signed-off-by: Tyler Ohlsen * Add AD vis augmenter tests Signed-off-by: Tyler Ohlsen * More refactoring Signed-off-by: Tyler Ohlsen * More tests Signed-off-by: Tyler Ohlsen * Add test for AD cleanup scenario Signed-off-by: Tyler Ohlsen * Set up saved obj test suite Signed-off-by: Tyler Ohlsen * Add reminder TODO Signed-off-by: Tyler Ohlsen * Add tests regarding saved obj visibility Signed-off-by: Tyler Ohlsen * Add view events tests Signed-off-by: Tyler Ohlsen * cleanup Signed-off-by: Tyler Ohlsen * remove import Signed-off-by: Tyler Ohlsen --------- Signed-off-by: Jovan Cvetkovic Signed-off-by: Tyler Ohlsen Co-authored-by: Jovan Cvetkovic * Fix NPE Signed-off-by: Tyler Ohlsen * Fix bug of non-empty dashboard; remove unnecessary test Signed-off-by: Tyler Ohlsen * Simplify dashboard creation Signed-off-by: Tyler Ohlsen * Update undefined check Signed-off-by: Tyler Ohlsen --------- Signed-off-by: Jovan Cvetkovic Signed-off-by: Tyler Ohlsen Co-authored-by: Jovan Cvetkovic Signed-off-by: leanne.laceybyrne@eliatra.com --- .../sample-data-simple/data.json | 27 ++ .../index-pattern-fields.txt | 1 + .../sample-data-simple/index-settings.txt | 1 + .../apps/vis-augmenter/dashboard_spec.js | 129 +++++++ .../sample_detector_spec.js | 20 +- .../vis_augmenter/associate_detector_spec.js | 145 ++++++++ .../augment_vis_saved_object_spec.js | 105 ++++++ .../vis_augmenter/view_anomaly_events_spec.js | 111 ++++++ cypress/utils/dashboards/commands.js | 1 + cypress/utils/dashboards/constants.js | 1 + .../dashboards/vis-augmenter/commands.js | 48 +++ .../dashboards/vis-augmenter/constants.js | 15 + .../utils/dashboards/vis-augmenter/helpers.js | 322 ++++++++++++++++++ .../utils/dashboards/vis-augmenter/index.d.ts | 45 +++ cypress/utils/helpers.js | 1 + .../helpers.js | 116 +++++++ 16 files changed, 1069 insertions(+), 19 deletions(-) create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt create mode 100644 cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js create mode 100644 cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js create mode 100644 cypress/utils/dashboards/vis-augmenter/commands.js create mode 100644 cypress/utils/dashboards/vis-augmenter/constants.js create mode 100644 cypress/utils/dashboards/vis-augmenter/helpers.js create mode 100644 cypress/utils/dashboards/vis-augmenter/index.d.ts diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json new file mode 100644 index 000000000..5124ea41e --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/data.json @@ -0,0 +1,27 @@ +[ + { + "value1": 1, + "value2": 10, + "value3": 5 + }, + { + "value1": 5, + "value2": 1, + "value3": 3 + }, + { + "value1": 9, + "value2": 6, + "value3": 2 + }, + { + "value1": 2, + "value2": 1, + "value3": 1 + }, + { + "value1": 12, + "value2": 5, + "value3": 4 + } +] diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt new file mode 100644 index 000000000..2e010deba --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-pattern-fields.txt @@ -0,0 +1 @@ +[{"count":0,"name":"@timestamp","type":"date","esTypes":["date"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"count":0,"name":"_id","type":"string","esTypes":["_id"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_index","type":"string","esTypes":["_index"],"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"_score","type":"number","scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_source","type":"_source","esTypes":["_source"],"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"_type","type":"string","scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"count":0,"name":"value1","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"value2","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"count":0,"name":"value3","type":"number","scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false}] \ No newline at end of file diff --git a/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt new file mode 100644 index 000000000..d4d78055d --- /dev/null +++ b/cypress/fixtures/dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/index-settings.txt @@ -0,0 +1 @@ +{"mappings":{"properties":{"value1":{"type":"integer"},"value2":{"type":"integer"},"value3":{"type":"integer"},"@timestamp":{"type":"date", "format":"epoch_millis"}}},"settings":{"index":{"number_of_shards":"1","number_of_replicas":"1"}}} \ No newline at end of file diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js new file mode 100644 index 000000000..d97647c55 --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis-augmenter/dashboard_spec.js @@ -0,0 +1,129 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + INDEX_PATTERN_FILEPATH_SIMPLE, + INDEX_SETTINGS_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, +} from '../../../../../utils/constants'; +import { + deleteVisAugmenterData, + bootstrapDashboard, +} from '../../../../../utils/dashboards/vis-augmenter/helpers'; + +describe('Vis augmenter - existing dashboards work as expected', () => { + describe('dashboard with ineligible, eligible, and vega visualizations', () => { + const indexName = 'vis-augmenter-sample-index'; + const indexPatternName = 'vis-augmenter-sample-*'; + const dashboardName = 'Vis Augmenter Dashboard'; + const visualizationSpecs = [ + { + name: 'count-agg-vis', + type: 'line', + indexPattern: indexPatternName, + metrics: [], + }, + { + name: 'single-metric-vis', + type: 'line', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Average', + field: 'value1', + }, + ], + }, + { + name: 'multi-metric-vis', + type: 'line', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Average', + field: 'value1', + }, + { + aggregation: 'Average', + field: 'value2', + }, + { + aggregation: 'Max', + field: 'value3', + }, + ], + }, + { + name: 'area-vis', + type: 'area', + indexPattern: indexPatternName, + metrics: [ + { + aggregation: 'Max', + field: 'value2', + }, + ], + }, + { + name: 'vega-vis', + type: 'vega', + indexPattern: indexPatternName, + metrics: [], + }, + ]; + + const visualizationNames = visualizationSpecs.map( + (visualizationSpec) => visualizationSpec.name + ); + + before(() => { + // Create a dashboard and add some visualizations + bootstrapDashboard( + INDEX_SETTINGS_FILEPATH_SIMPLE, + INDEX_PATTERN_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, + indexName, + indexPatternName, + dashboardName, + visualizationSpecs + ); + }); + + beforeEach(() => { + cy.visitDashboard(dashboardName); + }); + + after(() => { + deleteVisAugmenterData( + indexName, + indexPatternName, + visualizationNames, + dashboardName + ); + }); + + it('View events option does not exist for any visualization', () => { + visualizationNames.forEach((visualizationName) => { + cy.getVisPanelByTitle(visualizationName) + .openVisContextMenu() + .getMenuItems() + .contains('View Events') + .should('not.exist'); + }); + }); + + it('Validate non-vega visualizations are not rendered with vega under the hood', () => { + visualizationSpecs.forEach((visualizationSpec) => { + cy.getVisPanelByTitle(visualizationSpec.name).within(() => { + if (visualizationSpec.type === 'vega') { + cy.get('.vgaVis__view').should('exist'); + } else { + cy.get('.vgaVis__view').should('not.exist'); + } + }); + }); + }); + }); +}); diff --git a/cypress/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..7a9c8ee4d --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/associate_detector_spec.js @@ -0,0 +1,145 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + deleteVisAugmenterData, + bootstrapDashboard, + openAddAnomalyDetectorFlyout, + openAssociatedDetectorsFlyout, + createDetectorFromVis, + associateDetectorFromVis, + unlinkDetectorFromVis, + ensureDetectorIsLinked, + ensureDetectorDetails, + openDetectorDetailsPageFromFlyout, +} from '../../../../utils/helpers'; +import { + INDEX_PATTERN_FILEPATH_SIMPLE, + INDEX_SETTINGS_FILEPATH_SIMPLE, + SAMPLE_DATA_FILEPATH_SIMPLE, +} from '../../../../utils/constants'; + +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'; + 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('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); + }); + + it('Associate existing detector - creation flow', () => { + 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(); + + associateDetectorFromVis(detectorName); + + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); + }); + + it('Associate existing detector - associated detectors flow', () => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.getElementByTestId('associateDetectorButton').click(); + associateDetectorFromVis(detectorName); + + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); + }); + + 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); + + 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'); + }); +}); 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..1f02b0f90 --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/augment_vis_saved_object_spec.js @@ -0,0 +1,105 @@ +/* + * 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, +} 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 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('Associating a detector creates a visible saved object', () => { + openAddAnomalyDetectorFlyout(dashboardName, visualizationName); + createDetectorFromVis(detectorName); + ensureDetectorIsLinked(dashboardName, visualizationName, detectorName); + + 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'); + 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 association deletes the saved object', () => { + unlinkDetectorFromVis(dashboardName, visualizationName, detectorName); + + cy.visitSavedObjectsManagement(); + filterByObjectType('augment-vis'); + cy.getElementByTestId('savedObjectsTable') + .find('.euiTableRow') + .contains('No items found'); + }); +}); 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..40583a39f --- /dev/null +++ b/cypress/integration/plugins/anomaly-detection-dashboards-plugin/vis_augmenter/view_anomaly_events_spec.js @@ -0,0 +1,111 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + deleteVisAugmenterData, + bootstrapDashboard, + openAddAnomalyDetectorFlyout, + createDetectorFromVis, + unlinkDetectorFromVis, + ensureDetectorIsLinked, + 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 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.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/dashboards/commands.js b/cypress/utils/dashboards/commands.js index e7a4e39b1..54615c999 100644 --- a/cypress/utils/dashboards/commands.js +++ b/cypress/utils/dashboards/commands.js @@ -5,6 +5,7 @@ import './vis_builder/commands'; import './vis_type_table/commands'; +import './vis-augmenter/commands'; Cypress.Commands.add('waitForLoader', () => { const opts = { log: false }; diff --git a/cypress/utils/dashboards/constants.js b/cypress/utils/dashboards/constants.js index e16741d1e..c2c797c85 100644 --- a/cypress/utils/dashboards/constants.js +++ b/cypress/utils/dashboards/constants.js @@ -17,3 +17,4 @@ export const SAVED_OBJECTS_PATH = export * from './vis_builder/constants'; export * from './vis_type_table/constants'; +export * from './vis-augmenter/constants'; diff --git a/cypress/utils/dashboards/vis-augmenter/commands.js b/cypress/utils/dashboards/vis-augmenter/commands.js new file mode 100644 index 000000000..e0ee418de --- /dev/null +++ b/cypress/utils/dashboards/vis-augmenter/commands.js @@ -0,0 +1,48 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BASE_PATH } from '../../constants'; + +Cypress.Commands.add('getVisPanelByTitle', (title) => + cy.get(`[data-title="${title}"]`).parents('.embPanel').should('be.visible') +); + +Cypress.Commands.add('openVisContextMenu', { prevSubject: true }, (panel) => + cy + .wrap(panel) + .find(`[data-test-subj="embeddablePanelContextMenuClosed"]`) + .click() + .then(() => cy.get('.euiContextMenu')) +); + +Cypress.Commands.add( + 'clickVisPanelMenuItem', + { prevSubject: 'optional' }, + (menu, text) => + (menu ? cy.wrap(menu) : cy.get('.euiContextMenu')) + .find('button') + .contains(text) + .click() +); + +Cypress.Commands.add('getMenuItems', { prevSubject: 'optional' }, (menu) => + (menu ? cy.wrap(menu) : cy.get('.euiContextMenu')).find('button') +); + +Cypress.Commands.add('visitDashboard', (dashboardName) => { + cy.visit(`${BASE_PATH}/app/dashboards`); + cy.wait(2000); + cy.get('.euiFieldSearch').type(dashboardName); + cy.wait(2000); + cy.get('[data-test-subj="itemsInMemTable"]').contains(dashboardName).click({ + force: true, + }); + 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/constants.js b/cypress/utils/dashboards/vis-augmenter/constants.js new file mode 100644 index 000000000..6c7bfa365 --- /dev/null +++ b/cypress/utils/dashboards/vis-augmenter/constants.js @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +const SAMPLE_DATA_DIR_SIMPLE = + 'dashboard/opensearch_dashboards/vis-augmenter/sample-data-simple/'; + +export const SAMPLE_DATA_FILEPATH_SIMPLE = SAMPLE_DATA_DIR_SIMPLE + 'data.json'; + +export const INDEX_PATTERN_FILEPATH_SIMPLE = + SAMPLE_DATA_DIR_SIMPLE + 'index-pattern-fields.txt'; + +export const INDEX_SETTINGS_FILEPATH_SIMPLE = + SAMPLE_DATA_DIR_SIMPLE + 'index-settings.txt'; diff --git a/cypress/utils/dashboards/vis-augmenter/helpers.js b/cypress/utils/dashboards/vis-augmenter/helpers.js new file mode 100644 index 000000000..0ed78e6d6 --- /dev/null +++ b/cypress/utils/dashboards/vis-augmenter/helpers.js @@ -0,0 +1,322 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { isEmpty } from 'lodash'; +import { MiscUtils } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; + +const apiRequest = (url, method = 'POST', body = undefined, qs = undefined) => + cy.request({ + method: method, + failOnStatusCode: false, + url: url, + headers: { + 'content-type': 'application/json', + 'osd-xsrf': true, + }, + body: body, + qs: qs, + }); + +const devToolsRequest = ( + url, + method = 'POST', + body = undefined, + qs = undefined +) => + cy.request({ + method: 'POST', + form: false, + failOnStatusCode: false, + url: encodeURI(`api/console/proxy?path=${url}&method=${method}`), + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'osd-xsrf': true, + }, + body: body, + qs: qs, + }); + +/** + * Cleans up the index & all associated saved objects (index pattern, visualizations, + * dashboards, etc.) created during the test run + */ +export const deleteVisAugmenterData = ( + indexName, + indexPatternName, + visualizationNames, + dashboardName +) => { + devToolsRequest(indexName, 'DELETE').then(() => { + apiRequest( + `api/saved_objects/index-pattern/${indexPatternName}`, + 'DELETE' + ).then(() => { + apiRequest( + ` +api/opensearch-dashboards/management/saved_objects/_find?perPage=5000&page=1&fields=id&type=config&type=url&type=index-pattern&type=query&type=dashboard&type=visualization&type=visualization-visbuilder&type=augment-vis&type=search&sortField=type`, + 'GET' + ).then((response) => { + if (!response.body.saved_objects) return; + response.body.saved_objects.forEach((obj) => { + if ( + obj.type !== 'config' && + [ + indexName, + indexPatternName, + ...visualizationNames, + dashboardName, + ].indexOf(obj.meta.title) !== -1 + ) { + apiRequest( + `api/saved_objects/${obj.type}/${obj.id}?force=true`, + 'DELETE' + ); + } + }); + }); + }); + }); +}; + +/** + * Fetch the fixtures to create and configure an index, index pattern, + * and ingesting sample data to the index + */ +export const ingestVisAugmenterData = ( + indexName, + indexPatternName, + indexSettingsFilepath, + indexPatternFieldsFilepath, + sampleDataFilepath +) => { + cy.fixture(indexSettingsFilepath).then((indexSettings) => + devToolsRequest(indexName, 'PUT', indexSettings) + ); + + cy.fixture(indexPatternFieldsFilepath).then((fields) => { + apiRequest( + `api/saved_objects/index-pattern/${indexPatternName}`, + 'POST', + JSON.stringify({ + attributes: { + fields: fields, + title: indexPatternName, + timeFieldName: '@timestamp', + }, + }) + ); + }); + + cy.fixture(sampleDataFilepath).then((indexData) => { + indexData.forEach((item, idx) => { + let date = new Date(); + item['@timestamp'] = date.setMinutes(date.getMinutes() - 1); + devToolsRequest(`${indexName}/_doc/${idx}`, 'POST', JSON.stringify(item)); + }); + }); +}; + +/** + * Creating a new visualization from a dashboard, and finishing at the + * vis edit page + */ +const bootstrapCreateFromDashboard = (visType, indexPatternName) => { + cy.getElementByTestId('dashboardAddNewPanelButton') + .should('be.visible') + .click(); + + cy.getElementByTestId(`visType-${visType}`).click(); + + // vega charts don't have a secondary modal to configure the + // index pattern / saved search. Skip those steps here + if (visType !== 'vega') { + cy.getElementByTestId('savedObjectFinderSearchInput').type( + `${indexPatternName}{enter}` + ); + cy.get(`[title="${indexPatternName}"]`).click(); + } +}; + +const setXAxisDateHistogram = () => { + cy.get('.euiTitle') + .contains('Buckets') + .parent() + .find('[data-test-subj="visEditorAdd_buckets"]') + .click(); + + cy.getElementByTestId('visEditorAdd_buckets_X-axis').click(); + + cy.get('.euiTitle') + .contains('Buckets') + .parent() + .within(() => { + cy.wait(1000); + cy.getElementByTestId('comboBoxInput') + .find('input') + .type('Date Histogram{enter}', { force: true }); + }); +}; + +/** + * From the vis edit view, return to the dashboard + */ +const saveVisualizationAndReturn = (visualizationName) => { + cy.getElementByTestId('visualizeEditorRenderButton').click({ + force: true, + }); + cy.getElementByTestId('visualizeSaveButton').click({ + force: true, + }); + cy.getElementByTestId('savedObjectTitle').type(visualizationName); + cy.getElementByTestId('confirmSaveSavedObjectButton').click({ + force: true, + }); +}; + +/** + * Creates a list of specified metrics under the y-axis section + * in the vis editor page + */ +const setYAxis = (metrics) => { + metrics.forEach((metric, index) => { + // There is always a default count metric populated. So, for the first + // added metric, we need to overwrite it. For additional metrics, we + // can just click the "Add" button + if (index === 0) { + cy.getElementByTestId('metricsAggGroup') + .find('.euiAccordion__button') + .click({ force: true }); + } else { + cy.getElementByTestId('visEditorAdd_metrics').click(); + cy.getElementByTestId('visEditorAdd_metrics_Y-axis').click(); + } + addMetric(metric, index); + }); +}; + +/** + * Adds a metric to the specified index in the vis editor page + */ +const addMetric = (metric, index) => { + cy.getElementByTestId('metricsAggGroup') + .find(`[data-test-subj="visEditorAggAccordion${index + 1}"]`) + .within(() => { + cy.wait(1000); + cy.getElementByTestId('comboBoxSearchInput').type( + `${metric.aggregation}{downarrow}{enter}`, + { + force: true, + } + ); + cy.contains(`${metric.aggregation}`).click({ force: true }); + }); + + // non-count aggregations will have an additional field value to set + if (metric.aggregation !== 'Count' && metric.aggregation !== 'count') { + cy.getElementByTestId('metricsAggGroup') + .find(`[data-test-subj="visEditorAggAccordion${index + 1}"]`) + .find('[data-test-subj="visDefaultEditorField"]') + .within(() => { + cy.wait(1000); + cy.getElementByTestId('comboBoxSearchInput').type( + `${metric.field}{downarrow}{enter}`, + { + force: true, + } + ); + }); + } + + // re-collapse the accordion + cy.getElementByTestId(`visEditorAggAccordion${index + 1}`) + .find('.euiAccordion__button') + .first() + .click({ force: true }); +}; + +/** + * Creates an individual visualization, assuming runner is + * starting from dashboard edit view. + */ +export const createVisualizationFromDashboard = ( + visType, + indexPatternName, + visualizationName, + metrics +) => { + bootstrapCreateFromDashboard(visType, indexPatternName); + + // Vega visualizations don't configure axes the same way, + // so ignore those here. Note we still want to support the vega type, + // but don't support any custom specs, as the default spec may be + // sufficient for now + if (visType !== 'vega') { + if (!isEmpty(metrics)) { + setYAxis(metrics); + } + setXAxisDateHistogram(); + } + saveVisualizationAndReturn(visualizationName); +}; + +/** + * Ingests the specified sample data, creates and saves a specified + * list of visualizations, and saves them all to a new dashboard + */ +export const bootstrapDashboard = ( + indexSettingsFilepath, + indexPatternFieldsFilepath, + sampleDataFilepath, + indexName, + indexPatternName, + dashboardName, + visualizationSpecs +) => { + const miscUtils = new MiscUtils(cy); + deleteVisAugmenterData( + indexName, + indexPatternName, + visualizationSpecs.map((visualizationSpec) => visualizationSpec.name), + dashboardName + ); + ingestVisAugmenterData( + indexName, + indexPatternName, + indexSettingsFilepath, + indexPatternFieldsFilepath, + sampleDataFilepath + ); + + miscUtils.visitPage('app/dashboards#/create'); + + // Create several different visualizations + visualizationSpecs.forEach((visualizationSpec) => { + createVisualizationFromDashboard( + visualizationSpec.type, + visualizationSpec.indexPattern, + visualizationSpec.name, + visualizationSpec.metrics + ); + }); + + cy.getElementByTestId('dashboardSaveMenuItem').click({ + force: true, + }); + + cy.getElementByTestId('savedObjectTitle').type(dashboardName); + + cy.getElementByTestId('confirmSaveSavedObjectButton').click({ + force: true, + }); +}; + +export const filterByObjectType = (type) => { + cy.get('.euiFilterButton').click(); + cy.get('.euiFilterSelect__items') + .find('button') + .contains(type) + .click({ force: true }); + cy.wait(3000); +}; diff --git a/cypress/utils/dashboards/vis-augmenter/index.d.ts b/cypress/utils/dashboards/vis-augmenter/index.d.ts new file mode 100644 index 000000000..ded2d27d3 --- /dev/null +++ b/cypress/utils/dashboards/vis-augmenter/index.d.ts @@ -0,0 +1,45 @@ +// type definitions for custom commands like "createDefaultTodos" +/// + +declare namespace Cypress { + interface Chainable { + /** + * Returns visualization panel by title + * @example + * cy.getVisPanelByTitle('[Logs] Visitors by OS') + */ + getVisPanelByTitle(title: string): Chainable; + + /** + * Opens vis panel context menu + * @example + * cy.get('visPanel').openVisContextMenu() + */ + openVisContextMenu(): Chainable; + + /** + * Clicks vis panel context menu item + * @example + * cy.clickVisPanelMenuItem('Alerting') + */ + clickVisPanelMenuItem(text: string): Chainable; + + /** + * Gets all items in the context menu + * @example + * cy.getVisPanelByTitle('my-visualization') + .openVisContextMenu() + .getMenuItems() + .contains('View Events') + .should('exist'); + */ + getMenuItems(): Chainable; + + /** + * Visits a dashboard + * @example + * cy.visitDashboard('My-Dashboard') + */ + visitDashboard(dashboardName: string): Chainable; + } +} diff --git a/cypress/utils/helpers.js b/cypress/utils/helpers.js index a64e431fa..bb67116f6 100644 --- a/cypress/utils/helpers.js +++ b/cypress/utils/helpers.js @@ -4,3 +4,4 @@ */ export * from './plugins/anomaly-detection-dashboards-plugin/helpers'; +export * from './dashboards/vis-augmenter/helpers'; diff --git a/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js b/cypress/utils/plugins/anomaly-detection-dashboards-plugin/helpers.js index 8388339d9..96bda3d93 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,117 @@ 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 openDetectorDetailsPageFromFlyout = () => { + cy.get('.euiBasicTable').find('.euiLink').click(); +}; + +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'); +}; + +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"]') + .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(2000); + cy.get('.euiFieldSearch').type(detectorName); + 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, + detectorName +) => { + openAssociatedDetectorsFlyout(dashboardName, visualizationName); + cy.wait(2000); + cy.get('.euiFieldSearch').type(detectorName); + cy.wait(1000); + cy.getElementByTestId('unlinkButton').click(); + cy.getElementByTestId('confirmUnlinkButton').click(); + cy.wait(5000); +}; From 6ebfd9c6d3278cc9f0eb94dea9b161bcc6c69e91 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Tue, 18 Jul 2023 20:31:55 +0800 Subject: [PATCH 25/46] fix: CVE of tough-cookie and word-wrap (#763) Signed-off-by: SuZhou-Joe Signed-off-by: leanne.laceybyrne@eliatra.com --- package-lock.json | 92 +++++++++++++++++++++++++++++++---------------- package.json | 7 +++- 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 662183044..b2f40b101 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,12 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@babel/code-frame": { "version": "7.12.11", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", @@ -113,6 +119,26 @@ "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + } + }, + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } } }, "@cypress/skip-test": { @@ -911,6 +937,20 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2140,20 +2180,6 @@ "mimic-fn": "^2.1.0" } }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, "ospath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", @@ -2280,6 +2306,12 @@ "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", "dev": true }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -2312,6 +2344,12 @@ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -2636,16 +2674,6 @@ "rimraf": "^3.0.0" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -2727,6 +2755,16 @@ "punycode": "^2.1.0" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -2772,12 +2810,6 @@ "is-symbol": "^1.0.3" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 142882d49..247b96b79 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "lint": "eslint . --ext .js", "pkg-version": "./scripts/getpkgversion.sh", "generate:test-data": "node ./scripts/generate_data/index.js", - "postinstall": "husky install" + "postinstall": "husky install", + "preinstall": "npx npm-force-resolutions" }, "repository": { "type": "git", @@ -57,5 +58,9 @@ "husky": "^6.0.0", "luxon": "^3.2.1", "mocha-junit-reporter": "^2.0.0" + }, + "resolutions": { + "tough-cookie": "^4.1.3", + "optionator": "^0.9.3" } } From 2432032c1b66d3edb3212bfa8ae6dfa2d0a6145f Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Thu, 20 Jul 2023 14:01:26 +0800 Subject: [PATCH 26/46] feat: use "overrides" to install desired version of tough-cookie and word-wrap. (#772) * feat: use npm v8 to install and initialize package-lock Signed-off-by: SuZhou-Joe * feat: add Node version requirement in DEVELOPER_GUIDE Signed-off-by: SuZhou-Joe --------- Signed-off-by: SuZhou-Joe Signed-off-by: leanne.laceybyrne@eliatra.com --- DEVELOPER_GUIDE.md | 2 + package-lock.json | 4491 +++++++++++++++++++++++++++++++++++++++++++- package.json | 5 +- 3 files changed, 4485 insertions(+), 13 deletions(-) diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 097fce232..690ed1c84 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -23,6 +23,8 @@ If you would like to install and develop OpenSearch Dashboards or its plugins, p You should have a running instance of OpenSearch Dashboards to run these tests against them. Refer to the [OpenSearch Dashboards Developer guide](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/DEVELOPER_GUIDE.md) for details on how to do that. +- Node v16.20.0 + ### Installation To install the dependencies run diff --git a/package-lock.json b/package-lock.json index b2f40b101..73a89e5ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,4061 @@ { "name": "opensearch-dashboards-functional-test", "version": "3.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "opensearch-dashboards-functional-test", + "version": "3.0.0", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "@cypress/skip-test": "^2.6.1", + "@opensearch-dashboards-test/opensearch-dashboards-test-library": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.4.tar.gz", + "brace": "^0.11.1", + "prettier": "^2.5.1" + }, + "devDependencies": { + "@faker-js/faker": "^7.6.0", + "commander": "^9.4.1", + "cypress": "9.5.4", + "cypress-multi-reporters": "^1.5.0", + "cypress-real-events": "1.7.6", + "eslint": "^7.0.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-cypress": "^2.12.1", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.0.0", + "husky": "^6.0.0", + "luxon": "^3.2.1", + "mocha-junit-reporter": "^2.0.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmmirror.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.9.tgz", + "integrity": "sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.16.7", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cypress/request": { + "version": "2.88.10", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.10.tgz", + "integrity": "sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/request/node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@cypress/request/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@cypress/skip-test": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@cypress/skip-test/-/skip-test-2.6.1.tgz", + "integrity": "sha512-X+ibefBiuOmC5gKG91wRIT0/OqXeETYvu7zXktjZ3yLeO186Y8ia0K7/gQUpAwuUi28DuqMd1+7tBQVtPkzbPA==" + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz", + "integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==", + "dev": true, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@opensearch-dashboards-test/opensearch-dashboards-test-library": { + "version": "1.0.4", + "resolved": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.4.tar.gz", + "integrity": "sha512-IGxw7GVFRyKgjVs+ubTDynrOEvqlI7gTua+Lf2LbDL9g+f6qQ64gnCyMQWeu3mr+w38r+rvN6Uq0AGCcbQ9mlA==", + "license": "ISC" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.18.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz", + "integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q==", + "dev": true + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true + }, + "node_modules/@types/sizzle": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", + "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/brace": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", + "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "peer": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "peer": true + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "peer": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", + "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", + "dev": true + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", + "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.1.tgz", + "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==", + "dev": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/cypress": { + "version": "9.5.4", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.5.4.tgz", + "integrity": "sha512-6AyJAD8phe7IMvOL4oBsI9puRNOWxZjl8z1lgixJMcgJ85JJmyKeP6uqNA0dI1z14lmJ7Qklf2MOgP/xdAqJ/Q==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@cypress/request": "^2.88.10", + "@cypress/xvfb": "^1.2.4", + "@types/node": "^14.14.31", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.6.0", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.1", + "commander": "^5.1.0", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.2", + "enquirer": "^2.3.6", + "eventemitter2": "^6.4.3", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.0", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.6", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.3.2", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cypress-multi-reporters": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.5.0.tgz", + "integrity": "sha512-6rJ1rk1RpjZwTeydCDc8r3iOmWj2ZEYo++oDTJHNEu7eetb3W1cYDNo5CdxF/r0bo7TLQsOEpBHOCYBZfPVt/g==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "mocha": ">=3.1.2" + } + }, + "node_modules/cypress-real-events": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/cypress-real-events/-/cypress-real-events-1.7.6.tgz", + "integrity": "sha512-yP6GnRrbm6HK5q4DH6Nnupz37nOfZu/xn1xFYqsE2o4G73giPWQOdu6375QYpwfU1cvHNCgyD2bQ2hPH9D7NMw==", + "dev": true, + "peerDependencies": { + "cypress": "^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x" + } + }, + "node_modules/cypress/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cypress/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dayjs": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.1.tgz", + "integrity": "sha512-ER7EjqVAMkRRsxNCC5YqJ9d9VQYuWdGt7aiH2qA5R5wt8ZmWaP2dLUSIK6y/kVzLMlmh1Tvu5xUf4M/wdGJ5KA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es-abstract": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.3.tgz", + "integrity": "sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.6", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", + "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-cypress": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz", + "integrity": "sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==", + "dev": true, + "dependencies": { + "globals": "^11.12.0" + }, + "peerDependencies": { + "eslint": ">= 3.2.1" + } + }, + "node_modules/eslint-plugin-cypress/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-header": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", + "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==", + "dev": true, + "peerDependencies": { + "eslint": ">=7.7.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.5.tgz", + "integrity": "sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==", + "dev": true + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "peer": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "peer": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", + "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "dependencies": { + "async": "^3.2.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "peer": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/husky": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", + "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", + "dev": true, + "bin": { + "husky": "lib/bin.js" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, + "node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "peer": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", + "dev": true, + "engines": { + "node": "> 0.8" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/luxon": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz", + "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dev": true, + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha-junit-reporter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.0.0.tgz", + "integrity": "sha512-20HoWh2HEfhqmigfXOKUhZQyX23JImskc37ZOhIjBKoBEsb+4cAFRJpAVhFpnvsztLklW/gFVzsrobjLwmX4lA==", + "dev": true, + "dependencies": { + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "strip-ansi": "^4.0.0", + "xml": "^1.0.0" + }, + "peerDependencies": { + "mocha": ">=2.2.5" + } + }, + "node_modules/mocha-junit-reporter/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mocha-junit-reporter/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/mocha-junit-reporter/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/mocha-junit-reporter/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=", + "dev": true + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", + "dev": true + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "peer": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "peer": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "dev": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "node_modules/table/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/table/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "peer": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true, + "peer": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "peer": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + } + }, "dependencies": { "@aashutoshrathi/word-wrap": { "version": "1.2.6", @@ -116,7 +4169,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -255,7 +4308,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "aggregate-error": { "version": "3.1.0", @@ -317,6 +4371,17 @@ "color-convert": "^2.0.1" } }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "peer": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, "arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -429,6 +4494,13 @@ "tweetnacl": "^0.14.3" } }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "peer": true + }, "blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -456,6 +4528,23 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "peer": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "peer": true + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -494,6 +4583,13 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "peer": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -533,6 +4629,23 @@ "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", "dev": true }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "peer": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, "ci-info": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", @@ -574,6 +4687,18 @@ "string-width": "^4.2.0" } }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "peer": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -726,7 +4851,8 @@ "version": "1.7.6", "resolved": "https://registry.npmjs.org/cypress-real-events/-/cypress-real-events-1.7.6.tgz", "integrity": "sha512-yP6GnRrbm6HK5q4DH6Nnupz37nOfZu/xn1xFYqsE2o4G73giPWQOdu6375QYpwfU1cvHNCgyD2bQ2hPH9D7NMw==", - "dev": true + "dev": true, + "requires": {} }, "dashdash": { "version": "1.14.1", @@ -744,9 +4870,9 @@ "dev": true }, "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -760,6 +4886,13 @@ } } }, + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "peer": true + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -782,6 +4915,13 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "peer": true + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -877,6 +5017,13 @@ "is-symbol": "^1.0.2" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "peer": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -920,7 +5067,7 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "progress": "^2.0.0", "regexpp": "^3.1.0", "semver": "^7.2.1", @@ -966,7 +5113,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.6", @@ -1030,7 +5178,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-import": { "version": "2.26.0", @@ -1308,6 +5457,34 @@ "flat-cache": "^3.0.4" } }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "peer": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "peer": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "peer": true + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -1359,6 +5536,14 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true, + "peer": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1389,6 +5574,13 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "peer": true + }, "get-intrinsic": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", @@ -1529,6 +5721,13 @@ "has-symbols": "^1.0.2" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "peer": true + }, "http-signature": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", @@ -1628,6 +5827,16 @@ "has-bigints": "^1.0.1" } }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "peer": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -1714,6 +5923,13 @@ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "peer": true + }, "is-number-object": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", @@ -1729,6 +5945,13 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "peer": true + }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -1914,6 +6137,16 @@ "wrap-ansi": "^7.0.0" } }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "peer": true, + "requires": { + "p-locate": "^5.0.0" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2061,6 +6294,92 @@ "minimist": "^1.2.5" } }, + "mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "peer": true, + "requires": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "peer": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "peer": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "mocha-junit-reporter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.0.0.tgz", @@ -2112,12 +6431,26 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "peer": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "peer": true + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -2186,6 +6519,26 @@ "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=", "dev": true }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "peer": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "peer": true, + "requires": { + "p-limit": "^3.0.2" + } + }, "p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -2204,6 +6557,13 @@ "callsites": "^3.0.0" } }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "peer": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2234,6 +6594,13 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "peer": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -2312,6 +6679,26 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "peer": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "peer": true, + "requires": { + "picomatch": "^2.2.1" + } + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -2338,6 +6725,13 @@ "throttleit": "^1.0.0" } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "peer": true + }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -2433,6 +6827,16 @@ "lru-cache": "^6.0.0" } }, + "serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "peer": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2674,6 +7078,16 @@ "rimraf": "^3.0.0" } }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "peer": true, + "requires": { + "is-number": "^7.0.0" + } + }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -2810,6 +7224,13 @@ "is-symbol": "^1.0.3" } }, + "workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true, + "peer": true + }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -2833,12 +7254,55 @@ "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", "dev": true }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "peer": true + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "peer": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "peer": true + }, + "yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "peer": true, + "requires": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + } + }, "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", @@ -2848,6 +7312,13 @@ "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "peer": true } } } diff --git a/package.json b/package.json index 247b96b79..92fbf3cbc 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,7 @@ "lint": "eslint . --ext .js", "pkg-version": "./scripts/getpkgversion.sh", "generate:test-data": "node ./scripts/generate_data/index.js", - "postinstall": "husky install", - "preinstall": "npx npm-force-resolutions" + "postinstall": "husky install" }, "repository": { "type": "git", @@ -59,7 +58,7 @@ "luxon": "^3.2.1", "mocha-junit-reporter": "^2.0.0" }, - "resolutions": { + "overrides": { "tough-cookie": "^4.1.3", "optionator": "^0.9.3" } From 07a661356d7adb4e978306ae3bafb3acffbab0af Mon Sep 17 00:00:00 2001 From: Miki Date: Thu, 20 Jul 2023 14:30:52 -0700 Subject: [PATCH 27/46] Improve date selection across versions of OUI (#778) Signed-off-by: Miki Signed-off-by: leanne.laceybyrne@eliatra.com --- cypress/utils/commands.js | 19 +++++++++++++++++++ cypress/utils/dashboards/commands.js | 28 ++++++++++++++++++++++------ cypress/utils/index.d.ts | 20 +++++++++++++++++++- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index 1b753dc5d..90127014f 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -208,6 +208,25 @@ Cypress.Commands.add('getElementByTestId', (testId, options = {}) => { return cy.get(`[data-test-subj="${testId}"]`, options); }); +Cypress.Commands.add('getElementsByTestIds', (testIds, options = {}) => { + const selectors = [testIds] + .flat(Infinity) + .map((testId) => `[data-test-subj="${testId}"]`); + return cy.get(selectors.join(','), options); +}); + +Cypress.Commands.add( + 'whenTestIdNotFound', + (testIds, callbackFn, options = {}) => { + const selectors = [testIds] + .flat(Infinity) + .map((testId) => `[data-test-subj="${testId}"]`); + cy.get('body', options).then(($body) => { + if ($body.find(selectors.join(',')).length === 0) callbackFn(); + }); + } +); + Cypress.Commands.add('createIndex', (index, policyID = null, settings = {}) => { cy.request('PUT', `${Cypress.env('openSearchUrl')}/${index}`, settings); if (policyID != null) { diff --git a/cypress/utils/dashboards/commands.js b/cypress/utils/dashboards/commands.js index 54615c999..1d2188c4f 100644 --- a/cypress/utils/dashboards/commands.js +++ b/cypress/utils/dashboards/commands.js @@ -47,13 +47,29 @@ Cypress.Commands.add('setTopNavDate', (start, end, submit = true) => { message: `Start: ${start} :: End: ${end}`, }); - // Click date picker - cy.getElementByTestId('superDatePickerShowDatesButton', opts).click(opts); - - // Click start date - cy.getElementByTestId('superDatePickerstartDatePopoverButton', opts).click( + /* Find any one of the two buttons that change/open the date picker: + * * if `superDatePickerShowDatesButton` is found, it will switch the mode to dates + * * in some versions of OUI, the switch will open the date selection dialog as well + * * if `superDatePickerstartDatePopoverButton` is found, it will open the date selection dialog + */ + cy.getElementsByTestIds( + ['superDatePickerstartDatePopoverButton', 'superDatePickerShowDatesButton'], opts - ); + ) + .should('be.visible') + .invoke('attr', 'data-test-subj') + .then((testId) => { + cy.getElementByTestId(testId, opts).should('be.visible').click(opts); + }); + + /* While we surely are in the date selection mode, we don't know if the date selection dialog + * is open or not. Looking for a tab and if it is missing, click on the dialog opener. + */ + cy.whenTestIdNotFound('superDatePickerAbsoluteTab', () => { + cy.getElementByTestId('superDatePickerstartDatePopoverButton', opts) + .should('be.visible') + .click(opts); + }); // Click absolute tab cy.getElementByTestId('superDatePickerAbsoluteTab', opts).click(opts); diff --git a/cypress/utils/index.d.ts b/cypress/utils/index.d.ts index b1f98d309..8b65b8390 100644 --- a/cypress/utils/index.d.ts +++ b/cypress/utils/index.d.ts @@ -2,7 +2,25 @@ /// declare namespace Cypress { - interface Chainable { + interface Chainable { /** + * Call a function when an element with a test id cannot be found + * @example + * cy.whenTestIdNotFound(['query', 'puery'], () => {...}) + */ + whenTestIdNotFound( + testIds: string | string[], + callbackFn: void, + options?: Partial + ): Chainable; + /** + * Get elements by their test ids + * @example + * cy.getElementsByTestIds(['query', 'puery']) + */ + getElementsByTestIds( + testIds: string | string[], + options?: Partial + ): Chainable; /** * Get an element by its test id * @example From 9507738c723fb7e1bca67dd67881c420b97d7017 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Fri, 21 Jul 2023 11:28:01 +0800 Subject: [PATCH 28/46] feat: sync cypress test from notifications-dashboards (#776) (#784) * feat: sync cypress test from notifications-dashboards Signed-off-by: SuZhou-Joe * feat: rename commands Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: add larger wait time Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: add refresh before delete all notifications configs Signed-off-by: SuZhou-Joe --------- Signed-off-by: SuZhou-Joe (cherry picked from commit 81f70c37fde7b468915b173b0e81f4a609ecea84) Signed-off-by: leanne.laceybyrne@eliatra.com --- .../test_chime_channel.json | 11 +++ .../test_email_recipient_group.json | 30 ++++++++ .../test_ses_sender.json | 13 ++++ .../test_slack_channel.json | 11 +++ .../test_smtp_email_channel.json | 17 +++++ .../test_sns_channel.json | 12 ++++ .../test_ssl_smtp_sender.json | 14 ++++ .../test_tls_smtp_sender.json | 15 ++++ .../test_webhook_channel.json | 12 ++++ .../1_email_senders_and_groups.spec.js | 69 ++++++++++++++++--- .../2_channels.spec.js | 32 ++++++++- cypress/support/index.js | 1 + .../notifications-dashboards/commands.js | 45 ++++++++++++ .../notifications-dashboards/constants.js | 4 ++ 14 files changed, 272 insertions(+), 14 deletions(-) create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_chime_channel.json create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_email_recipient_group.json create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_ses_sender.json create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_slack_channel.json create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_smtp_email_channel.json create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_sns_channel.json create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_ssl_smtp_sender.json create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_tls_smtp_sender.json create mode 100644 cypress/fixtures/plugins/notifications-dashboards/test_webhook_channel.json create mode 100644 cypress/utils/plugins/notifications-dashboards/commands.js diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_chime_channel.json b/cypress/fixtures/plugins/notifications-dashboards/test_chime_channel.json new file mode 100644 index 000000000..feb56cc1f --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_chime_channel.json @@ -0,0 +1,11 @@ +{ + "config": { + "name": "Test chime channel", + "description": "A test chime channel", + "config_type": "chime", + "is_enabled": true, + "chime": { + "url": "https://sample-chime-webhook" + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_email_recipient_group.json b/cypress/fixtures/plugins/notifications-dashboards/test_email_recipient_group.json new file mode 100644 index 000000000..6f043b7cb --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_email_recipient_group.json @@ -0,0 +1,30 @@ +{ + "config": { + "name": "Test recipient group", + "description": "A test email recipient group", + "config_type": "email_group", + "is_enabled": true, + "email_group": { + "recipient_list": [ + { + "recipient": "custom.email.1@test.com" + }, + { + "recipient": "custom.email.2@test.com" + }, + { + "recipient": "custom.email.3@test.com" + }, + { + "recipient": "custom.email.4@test.com" + }, + { + "recipient": "custom.email.5@test.com" + }, + { + "recipient": "custom.email.6@test.com" + } + ] + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_ses_sender.json b/cypress/fixtures/plugins/notifications-dashboards/test_ses_sender.json new file mode 100644 index 000000000..1395b9364 --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_ses_sender.json @@ -0,0 +1,13 @@ +{ + "config": { + "name": "test-ses-sender", + "description": "A test SES sender", + "config_type": "ses_account", + "is_enabled": true, + "ses_account": { + "region": "us-east-1", + "role_arn": "arn:aws:iam::012345678912:role/NotificationsSESRole", + "from_address": "test@email.com" + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_slack_channel.json b/cypress/fixtures/plugins/notifications-dashboards/test_slack_channel.json new file mode 100644 index 000000000..7643b2b0d --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_slack_channel.json @@ -0,0 +1,11 @@ +{ + "config": { + "name": "Test slack channel", + "description": "A test slack channel", + "config_type": "slack", + "is_enabled": true, + "slack": { + "url": "https://sample-slack-webhook" + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_smtp_email_channel.json b/cypress/fixtures/plugins/notifications-dashboards/test_smtp_email_channel.json new file mode 100644 index 000000000..f6ddc82a9 --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_smtp_email_channel.json @@ -0,0 +1,17 @@ +{ + "config": { + "name": "Test email channel", + "description": "A test SMTP email channel", + "config_type": "email", + "is_enabled": true, + "email": { + "email_account_id": "test_smtp_sender_id", + "recipient_list": [ + { + "recipient": "custom.email@test.com" + } + ], + "email_group_id_list": [] + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_sns_channel.json b/cypress/fixtures/plugins/notifications-dashboards/test_sns_channel.json new file mode 100644 index 000000000..579eabd61 --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_sns_channel.json @@ -0,0 +1,12 @@ +{ + "config": { + "name": "test-sns-channel", + "description": "A test SNS channel", + "config_type": "sns", + "is_enabled": true, + "sns": { + "topic_arn": "arn:aws:sns:us-west-2:123456789012:notifications-test", + "role_arn": "arn:aws:iam::012345678901:role/NotificationsSNSRole" + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_ssl_smtp_sender.json b/cypress/fixtures/plugins/notifications-dashboards/test_ssl_smtp_sender.json new file mode 100644 index 000000000..977369a03 --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_ssl_smtp_sender.json @@ -0,0 +1,14 @@ +{ + "config": { + "name": "test-ssl-sender", + "description": "A test SSL SMTP sender", + "config_type": "smtp_account", + "is_enabled": true, + "smtp_account": { + "host": "test-host.com", + "port": 123, + "method": "ssl", + "from_address": "test@email.com" + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_tls_smtp_sender.json b/cypress/fixtures/plugins/notifications-dashboards/test_tls_smtp_sender.json new file mode 100644 index 000000000..cfa9870e4 --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_tls_smtp_sender.json @@ -0,0 +1,15 @@ +{ + "config_id": "test_smtp_sender_id", + "config": { + "name": "test-tls-sender", + "description": "A test TLS SMTP sender", + "config_type": "smtp_account", + "is_enabled": true, + "smtp_account": { + "host": "test-host.com", + "port": 123, + "method": "start_tls", + "from_address": "test@email.com" + } + } +} \ No newline at end of file diff --git a/cypress/fixtures/plugins/notifications-dashboards/test_webhook_channel.json b/cypress/fixtures/plugins/notifications-dashboards/test_webhook_channel.json new file mode 100644 index 000000000..86d6ec102 --- /dev/null +++ b/cypress/fixtures/plugins/notifications-dashboards/test_webhook_channel.json @@ -0,0 +1,12 @@ +{ + "config": { + "name": "Test webhook channel", + "description": "A test webhook channel", + "config_type": "webhook", + "is_enabled": true, + "webhook": { + "url": "https://custom-webhook-test-url.com:8888/test-path?params1=value1¶ms2=value2¶ms3=value3¶ms4=value4¶ms5=values5¶ms6=values6¶ms7=values7" + } + } +} + diff --git a/cypress/integration/plugins/notifications-dashboards/1_email_senders_and_groups.spec.js b/cypress/integration/plugins/notifications-dashboards/1_email_senders_and_groups.spec.js index 25c4e1adf..2bd28c9af 100644 --- a/cypress/integration/plugins/notifications-dashboards/1_email_senders_and_groups.spec.js +++ b/cypress/integration/plugins/notifications-dashboards/1_email_senders_and_groups.spec.js @@ -11,10 +11,21 @@ import { NOTIFICATIONS_PLUGIN_NAME, } from '../../../utils/constants'; +import testSslSmtpSender from '../../../fixtures/plugins/notifications-dashboards/test_ssl_smtp_sender.json'; +import testTlsSmtpSender from '../../../fixtures/plugins/notifications-dashboards/test_tls_smtp_sender.json'; +import testSesSender from '../../../fixtures/plugins/notifications-dashboards/test_ses_sender.json'; +import testEmailRecipientGroup from '../../../fixtures/plugins/notifications-dashboards/test_email_recipient_group.json'; + describe('Test create email senders', () => { + before(() => { + // Delete all Notification configs + cy.deleteAllNotificationConfigs(); + }); + beforeEach(() => { cy.visit(`${BASE_PATH}/app/${NOTIFICATIONS_PLUGIN_NAME}#email-senders`); - cy.wait(NOTIFICATIONS_DELAY * 3); + cy.reload(true); + cy.wait(NOTIFICATIONS_DELAY * 5); }); it('creates ssl sender', () => { @@ -62,7 +73,7 @@ describe('Test create email senders', () => { cy.get('.euiButton__text').contains('Create').click({ force: true }); cy.contains('successfully created.').should('exist'); - cy.contains('test-ssl-sender').should('exist'); + cy.contains('test-tls-sender').should('exist'); }); it('creates SES sender', () => { @@ -89,16 +100,26 @@ describe('Test create email senders', () => { }); describe('Test edit senders', () => { + before(() => { + // Delete all Notification configs + cy.deleteAllNotificationConfigs(); + + cy.createNotificationConfig(testSslSmtpSender); + cy.createNotificationConfig(testTlsSmtpSender); + cy.createNotificationConfig(testSesSender); + }); + beforeEach(() => { cy.visit(`${BASE_PATH}/app/${NOTIFICATIONS_PLUGIN_NAME}#email-senders`); - cy.wait(NOTIFICATIONS_DELAY * 3); + cy.reload(true); + cy.wait(NOTIFICATIONS_DELAY * 5); }); it('edits sender email address', () => { cy.get('.euiCheckbox__input[aria-label="Select this row"]').eq(0).click(); // ssl sender cy.get('[data-test-subj="senders-table-edit-button"]').click(); cy.get('[data-test-subj="create-sender-form-email-input"]').type( - '{selectall}{backspace}edited.test@email.com' + '{selectall}{backspace}editedtest@email.com' ); cy.wait(NOTIFICATIONS_DELAY); @@ -120,14 +141,26 @@ describe('Test edit senders', () => { }); describe('Test delete senders', () => { + before(() => { + // Delete all Notification configs + cy.deleteAllNotificationConfigs(); + + cy.createNotificationConfig(testSslSmtpSender); + cy.createNotificationConfig(testTlsSmtpSender); + cy.createNotificationConfig(testSesSender); + }); + beforeEach(() => { cy.visit(`${BASE_PATH}/app/${NOTIFICATIONS_PLUGIN_NAME}#email-senders`); - cy.wait(NOTIFICATIONS_DELAY * 3); + cy.reload(true); + cy.wait(NOTIFICATIONS_DELAY * 5); }); it('deletes smtp senders', () => { cy.get('.euiCheckbox__input[aria-label="Select this row"]').eq(0).click(); // ssl sender - cy.get('[data-test-subj="senders-table-delete-button"]').click(); + cy.get('[data-test-subj="senders-table-delete-button"]').click({ + force: true, + }); cy.get('input[placeholder="delete"]').type('delete'); cy.wait(NOTIFICATIONS_DELAY); cy.get('[data-test-subj="delete-sender-modal-delete-button"]').click(); @@ -136,7 +169,10 @@ describe('Test delete senders', () => { it('deletes ses senders', () => { cy.get('.euiCheckbox__input[aria-label="Select this row"]').last().click(); // ses sender - cy.get('[data-test-subj="ses-senders-table-delete-button"]').click(); + cy.wait(NOTIFICATIONS_DELAY); + cy.get('[data-test-subj="ses-senders-table-delete-button"]').click({ + force: true, + }); cy.get('input[placeholder="delete"]').type('delete'); cy.wait(NOTIFICATIONS_DELAY); cy.get('[data-test-subj="delete-sender-modal-delete-button"]').click(); @@ -148,10 +184,16 @@ describe('Test delete senders', () => { describe('Test create, edit and delete recipient group', () => { beforeEach(() => { + // Delete all Notification configs + cy.deleteAllNotificationConfigs(); + + cy.createNotificationConfig(testEmailRecipientGroup); + cy.visit( `${BASE_PATH}/app/${NOTIFICATIONS_PLUGIN_NAME}#email-recipient-groups` ); - cy.wait(NOTIFICATIONS_DELAY * 3); + cy.reload(true); + cy.wait(NOTIFICATIONS_DELAY * 5); }); it('creates recipient group', () => { @@ -210,13 +252,18 @@ describe('Test create, edit and delete recipient group', () => { }); it('opens email addresses popup', () => { - cy.get('.euiLink').contains('1 more').click({ force: true }); + cy.get('.euiTableCellContent--overflowingContent .euiLink') + .contains('1 more') + .click({ force: true }); cy.contains('custom.email.6@test.com').should('exist'); }); it('deletes recipient groups', () => { - cy.get('[data-test-subj="checkboxSelectAll"]').last().click(); - cy.get('[data-test-subj="recipient-groups-table-delete-button"]').click(); + cy.get('[data-test-subj="checkboxSelectAll"]').click({ force: true }); + cy.wait(NOTIFICATIONS_DELAY); + cy.get('[data-test-subj="recipient-groups-table-delete-button"]').click({ + force: true, + }); cy.get('input[placeholder="delete"]').type('delete'); cy.wait(NOTIFICATIONS_DELAY); cy.get( diff --git a/cypress/integration/plugins/notifications-dashboards/2_channels.spec.js b/cypress/integration/plugins/notifications-dashboards/2_channels.spec.js index 6e2dbad1f..bd5766e86 100644 --- a/cypress/integration/plugins/notifications-dashboards/2_channels.spec.js +++ b/cypress/integration/plugins/notifications-dashboards/2_channels.spec.js @@ -11,7 +11,19 @@ import { NOTIFICATIONS_PLUGIN_NAME, } from '../../../utils/constants'; +import testSlackChannel from '../../../fixtures/plugins/notifications-dashboards/test_slack_channel.json'; +import testChimeChannel from '../../../fixtures/plugins/notifications-dashboards/test_chime_channel.json'; +import testWebhookChannel from '../../../fixtures/plugins/notifications-dashboards/test_webhook_channel.json'; +import testTlsSmtpSender from '../../../fixtures/plugins/notifications-dashboards/test_tls_smtp_sender.json'; + describe('Test create channels', () => { + before(() => { + // Delete all Notification configs + cy.deleteAllNotificationConfigs(); + + cy.createNotificationConfig(testTlsSmtpSender); + }); + beforeEach(() => { cy.visit(`${BASE_PATH}/app/${NOTIFICATIONS_PLUGIN_NAME}#create-channel`); cy.wait(NOTIFICATIONS_DELAY * 3); @@ -58,7 +70,7 @@ describe('Test create channels', () => { cy.contains('successfully created.').should('exist'); }); - it('creates a email channel', () => { + it('creates an email channel', () => { cy.get('[placeholder="Enter channel name"]').type('Test email channel'); cy.get('.euiSuperSelectControl').contains('Slack').click({ force: true }); @@ -96,7 +108,7 @@ describe('Test create channels', () => { cy.contains('successfully created.').should('exist'); }); - it('creates a email channel with ses sender', () => { + it('creates an email channel with ses sender', () => { cy.get('[placeholder="Enter channel name"]').type( 'Test email channel with ses' ); @@ -156,7 +168,7 @@ describe('Test create channels', () => { cy.contains('successfully created.').should('exist'); }); - it('creates a sns channel', () => { + it('creates an sns channel', () => { cy.get('[placeholder="Enter channel name"]').type('test-sns-channel'); cy.get('.euiSuperSelectControl').contains('Slack').click({ force: true }); @@ -179,6 +191,17 @@ describe('Test create channels', () => { }); describe('Test channels table', () => { + before(() => { + // Delete all Notification configs + cy.deleteAllNotificationConfigs(); + + // Create test channels + cy.createNotificationConfig(testSlackChannel); + cy.createNotificationConfig(testChimeChannel); + cy.createNotificationConfig(testWebhookChannel); + cy.createTestEmailChannel(); + }); + beforeEach(() => { cy.visit(`${BASE_PATH}/app/${NOTIFICATIONS_PLUGIN_NAME}#channels`); cy.wait(NOTIFICATIONS_DELAY * 3); @@ -260,6 +283,9 @@ describe('Test channel details', () => { // cy.get( // '[data-test-subj="create-channel-description-input"]' // ).type('{selectall}{backspace}Updated custom webhook description'); + cy.get('[data-test-subj="create-channel-description-input"]').type( + '{selectall}{backspace}Updated custom webhook description' + ); cy.get('.euiTextArea').type( '{selectall}{backspace}Updated custom webhook description' ); diff --git a/cypress/support/index.js b/cypress/support/index.js index f7ef56c70..6b13ed838 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -29,6 +29,7 @@ import '../utils/plugins/security-dashboards-plugin/commands'; import '../utils/plugins/alerting-dashboards-plugin/commands'; import '../utils/plugins/ml-commons-dashboards/commands'; import '../utils/plugins/security-analytics-dashboards-plugin/commands'; +import '../utils/plugins/notifications-dashboards/commands'; import 'cypress-real-events'; diff --git a/cypress/utils/plugins/notifications-dashboards/commands.js b/cypress/utils/plugins/notifications-dashboards/commands.js new file mode 100644 index 000000000..f5cfae36e --- /dev/null +++ b/cypress/utils/plugins/notifications-dashboards/commands.js @@ -0,0 +1,45 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import testTlsSmtpSender from '../../../fixtures/plugins/notifications-dashboards/test_tls_smtp_sender.json'; +import testSmtpEmailChannel from '../../../fixtures/plugins/notifications-dashboards/test_smtp_email_channel.json'; +import { API } from './constants'; + +Cypress.Commands.add('deleteAllNotificationConfigs', () => { + cy.request({ + method: 'POST', + url: `${Cypress.env('openSearchUrl')}/_refresh`, + }); + + cy.request({ + method: 'GET', + url: `${Cypress.env('openSearchUrl')}${API.CONFIGS_BASE}`, + }).then((response) => { + if (response.status === 200) { + for (let i = 0; i < response.body.total_hits; i++) { + cy.request( + 'DELETE', + `${Cypress.env('openSearchUrl')}${API.CONFIGS_BASE}/${ + response.body.config_list[i].config_id + }` + ); + } + } else { + cy.log('Failed to get configs.', response); + } + }); +}); + +Cypress.Commands.add('createNotificationConfig', (notificationConfigJSON) => { + cy.request( + 'POST', + `${Cypress.env('openSearchUrl')}${API.CONFIGS_BASE}`, + notificationConfigJSON + ); +}); + +Cypress.Commands.add('createTestEmailChannel', () => { + cy.createNotificationConfig(testTlsSmtpSender); + cy.createNotificationConfig(testSmtpEmailChannel); +}); diff --git a/cypress/utils/plugins/notifications-dashboards/constants.js b/cypress/utils/plugins/notifications-dashboards/constants.js index fd6258f70..76da025b8 100644 --- a/cypress/utils/plugins/notifications-dashboards/constants.js +++ b/cypress/utils/plugins/notifications-dashboards/constants.js @@ -5,3 +5,7 @@ export const NOTIFICATIONS_PLUGIN_NAME = 'notifications-dashboards'; export const NOTIFICATIONS_DELAY = 1000; +export const NOTIFICATIONS_API_ROUTE_PREFIX = '/_plugins/_notifications'; +export const API = { + CONFIGS_BASE: `${NOTIFICATIONS_API_ROUTE_PREFIX}/configs`, +}; From bda3d9eff71588e64b7789ad8ba6beae23f31b30 Mon Sep 17 00:00:00 2001 From: Sirazh Gabdullin Date: Fri, 21 Jul 2023 23:13:51 +0600 Subject: [PATCH 29/46] [Table Visualizations] Tests cleanup (#785) Signed-off-by: Sirazh Gabdullin Signed-off-by: leanne.laceybyrne@eliatra.com --- .../apps/vis_builder/vis_types/table.spec.js | 2 +- .../opensearch-dashboards/apps/vis_type_table/split.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js index d04accb72..786ddb5b0 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js @@ -87,7 +87,7 @@ export const removeBucket = (bucket) => { export const testSplitTables = (num) => { cy.getElementByTestId('visTable') - .should('have.class', `visTable`) + .should('have.class', 'visTable') .find('[class="visTable__group"]') .should(($tables) => { // should have found specified number of tables diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_type_table/split.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_type_table/split.spec.js index 41717a39f..ec4c10dc8 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_type_table/split.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_type_table/split.spec.js @@ -311,7 +311,7 @@ describe.skip('Split table', () => { cy.tbSplitTablesInColumns(); cy.tbSetupTermsAggregation('age', 'Descending', '2', 2); cy.waitForLoader(); - cy.get('[class="visTable visTable__groupInColumns"]').should('exist'); + cy.get('[class="visTable"]').should('exist'); cy.tbGetAllTableDataFromVisualization(2).then((data) => { expect(data).to.deep.eq(expectData); }); From 5917744e3afc224ddd7285087e781f6ab82b7d97 Mon Sep 17 00:00:00 2001 From: Miki Date: Mon, 24 Jul 2023 19:51:19 -0700 Subject: [PATCH 30/46] Bump dependency on opensearch-dashboards-test-library (#790) Signed-off-by: Miki Signed-off-by: leanne.laceybyrne@eliatra.com --- package-lock.json | 12 ++++++------ package.json | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73a89e5ec..03919ea9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "ISC", "dependencies": { "@cypress/skip-test": "^2.6.1", - "@opensearch-dashboards-test/opensearch-dashboards-test-library": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.4.tar.gz", + "@opensearch-dashboards-test/opensearch-dashboards-test-library": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.5.tar.gz", "brace": "^0.11.1", "prettier": "^2.5.1" }, @@ -273,9 +273,9 @@ "dev": true }, "node_modules/@opensearch-dashboards-test/opensearch-dashboards-test-library": { - "version": "1.0.4", - "resolved": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.4.tar.gz", - "integrity": "sha512-IGxw7GVFRyKgjVs+ubTDynrOEvqlI7gTua+Lf2LbDL9g+f6qQ64gnCyMQWeu3mr+w38r+rvN6Uq0AGCcbQ9mlA==", + "version": "1.0.5", + "resolved": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.5.tar.gz", + "integrity": "sha512-1TIUBDKuAlhBUQg1XqYvsOs0x4R02r/437bDRQxEV21Bz3kP5djPG6hHDtuB7lbJkPE2zTgNO+VeQd50uUr9rQ==", "license": "ISC" }, "node_modules/@types/json5": { @@ -4261,8 +4261,8 @@ "dev": true }, "@opensearch-dashboards-test/opensearch-dashboards-test-library": { - "version": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.4.tar.gz", - "integrity": "sha512-IGxw7GVFRyKgjVs+ubTDynrOEvqlI7gTua+Lf2LbDL9g+f6qQ64gnCyMQWeu3mr+w38r+rvN6Uq0AGCcbQ9mlA==" + "version": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.5.tar.gz", + "integrity": "sha512-1TIUBDKuAlhBUQg1XqYvsOs0x4R02r/437bDRQxEV21Bz3kP5djPG6hHDtuB7lbJkPE2zTgNO+VeQd50uUr9rQ==" }, "@types/json5": { "version": "0.0.29", diff --git a/package.json b/package.json index 92fbf3cbc..4dae0d436 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "homepage": "https://github.com/opensearch-project/opensearch-dashboards-functional-test", "dependencies": { "@cypress/skip-test": "^2.6.1", - "@opensearch-dashboards-test/opensearch-dashboards-test-library": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.4.tar.gz", + "@opensearch-dashboards-test/opensearch-dashboards-test-library": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.5.tar.gz", "brace": "^0.11.1", "prettier": "^2.5.1" }, @@ -62,4 +62,4 @@ "tough-cookie": "^4.1.3", "optionator": "^0.9.3" } -} +} \ No newline at end of file From e9ac430baf0b625d3d166b1a7019597559cfa8ee Mon Sep 17 00:00:00 2001 From: Sirazh Gabdullin Date: Tue, 25 Jul 2023 22:47:28 +0600 Subject: [PATCH 31/46] [Table Visualizations] Test Update (#787) * fix-test * simplify selector --------- Signed-off-by: Sirazh Gabdullin Signed-off-by: leanne.laceybyrne@eliatra.com --- .../apps/vis_builder/vis_types/table.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js index 786ddb5b0..4abc9d100 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/vis_types/table.spec.js @@ -88,7 +88,7 @@ export const removeBucket = (bucket) => { export const testSplitTables = (num) => { cy.getElementByTestId('visTable') .should('have.class', 'visTable') - .find('[class="visTable__group"]') + .find('.visTable__group') .should(($tables) => { // should have found specified number of tables expect($tables).to.have.length(num); From e3379ffdb87cf832bb0c8b5f02d59e5b5af08b80 Mon Sep 17 00:00:00 2001 From: "Qingyang(Abby) Hu" Date: Wed, 30 Aug 2023 12:39:58 -0700 Subject: [PATCH 32/46] fix discover (#803) Signed-off-by: abbyhu2000 Signed-off-by: leanne.laceybyrne@eliatra.com --- cypress/integration/common/dashboard_sample_data_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/common/dashboard_sample_data_spec.js b/cypress/integration/common/dashboard_sample_data_spec.js index db2f225a1..5e76f001b 100644 --- a/cypress/integration/common/dashboard_sample_data_spec.js +++ b/cypress/integration/common/dashboard_sample_data_spec.js @@ -248,7 +248,7 @@ export function dashboardSanityTests() { describe('checking discover', () => { before(() => { // Go to the Discover page - miscUtils.visitPage('app/discover#/'); + miscUtils.visitPage('app/discoverLegacy#/'); }); after(() => {}); From b37c0b1a08ddd97ee6a49fb3f426b26a1b6413ff Mon Sep 17 00:00:00 2001 From: Kavitha Conjeevaram Mohan Date: Wed, 30 Aug 2023 12:44:09 -0700 Subject: [PATCH 33/46] Change to toast message in Reports (#578) * Change to toast message in Reports download spec Signed-off-by: Kavitha Conjeevaram Mohan * Update test Signed-off-by: Kavitha Conjeevaram Mohan * Fix lint Signed-off-by: Kavitha Conjeevaram Mohan --------- Signed-off-by: Kavitha Conjeevaram Mohan Signed-off-by: leanne.laceybyrne@eliatra.com --- .../plugins/reports-dashboards/04-download.spec.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cypress/integration/plugins/reports-dashboards/04-download.spec.js b/cypress/integration/plugins/reports-dashboards/04-download.spec.js index 381fb6ce9..ff2d0c60b 100644 --- a/cypress/integration/plugins/reports-dashboards/04-download.spec.js +++ b/cypress/integration/plugins/reports-dashboards/04-download.spec.js @@ -40,13 +40,9 @@ describe('Cypress', () => { cy.get('[id="landingPageOnDemandDownload"]') .contains('PDF') .click({ force: true }); - cy.get('body').then(($body) => { - if ($body.find('#downloadInProgressLoadingModal').length > 0) { - return; - } else { - assert(false); - } - }); + cy.get('.euiToastHeader__title') + .contains('Successfully generated report') + .should('exist'); }); it('Download pdf from in-context menu', () => { @@ -137,6 +133,8 @@ describe('Cypress', () => { cy.get('#generateReportFromDetailsFileFormat').click({ force: true }); - cy.get('#downloadInProgressLoadingModal'); + cy.get('.euiToastHeader__title') + .contains('Successfully generated report') + .should('exist'); }); }); From c9990e79923f5cdbeddaa3c5832fe67db7c47dd0 Mon Sep 17 00:00:00 2001 From: "Qingyang(Abby) Hu" Date: Wed, 30 Aug 2023 17:41:38 -0700 Subject: [PATCH 34/46] fix discover (#807) Signed-off-by: Qingyang(Abby) Hu Signed-off-by: leanne.laceybyrne@eliatra.com --- cypress/integration/common/dashboard_sample_data_spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/integration/common/dashboard_sample_data_spec.js b/cypress/integration/common/dashboard_sample_data_spec.js index 5e76f001b..d6ee6f0c7 100644 --- a/cypress/integration/common/dashboard_sample_data_spec.js +++ b/cypress/integration/common/dashboard_sample_data_spec.js @@ -247,8 +247,9 @@ export function dashboardSanityTests() { describe('checking discover', () => { before(() => { + cy.setAdvancedSetting({ 'discover:v2': false }); // Go to the Discover page - miscUtils.visitPage('app/discoverLegacy#/'); + miscUtils.visitPage('app/discover#/'); }); after(() => {}); From 99a4a59235daea0ff42423165704b25c01a0c175 Mon Sep 17 00:00:00 2001 From: Lin Wang Date: Mon, 4 Sep 2023 15:41:02 +0800 Subject: [PATCH 35/46] feat: add test cases for remote models (#813) Signed-off-by: Lin Wang Signed-off-by: leanne.laceybyrne@eliatra.com --- .../ml-commons-dashboards/overview_spec.js | 318 ++++++++++++------ .../plugins/ml-commons-dashboards/commands.js | 48 ++- .../ml-commons-dashboards/constants.js | 3 + 3 files changed, 259 insertions(+), 110 deletions(-) diff --git a/cypress/integration/plugins/ml-commons-dashboards/overview_spec.js b/cypress/integration/plugins/ml-commons-dashboards/overview_spec.js index 9210ae37f..1b0c129d4 100644 --- a/cypress/integration/plugins/ml-commons-dashboards/overview_spec.js +++ b/cypress/integration/plugins/ml-commons-dashboards/overview_spec.js @@ -6,134 +6,236 @@ import { MLC_URL, MLC_DASHBOARD_API } from '../../../utils/constants'; if (Cypress.env('ML_COMMONS_DASHBOARDS_ENABLED')) { describe('MLC Overview page', () => { - let uploadedModelId; - let uploadedModelLoadedError; - const uploadModelName = `traced_small_model-${new Date() - .getTime() - .toString(34)}`; before(() => { // Disable only_run_on_ml_node to avoid model upload error in case of cluster no ml nodes cy.disableOnlyRunOnMLNode(); - cy.disableNativeMemoryCircuitBreaker(); - cy.enableRegisterModelViaURL(); - cy.wait(1000); - - cy.registerModelGroup({ - name: 'model-group', - }) - .then(({ model_group_id }) => - cy.uploadModelByUrl({ - name: uploadModelName, - version: '1.0.0', - model_format: 'TORCH_SCRIPT', - model_task_type: 'text_embedding', - model_group_id, - model_content_hash_value: - 'e13b74006290a9d0f58c1376f9629d4ebc05a0f9385f40db837452b167ae9021', - model_config: { - model_type: 'bert', - embedding_dimension: 768, - framework_type: 'sentence_transformers', - all_config: - '{"architectures":["BertModel"],"max_position_embeddings":512,"model_type":"bert","num_attention_heads":12,"num_hidden_layers":6}', - }, - url: 'https://github.com/opensearch-project/ml-commons/blob/2.x/ml-algorithms/src/test/resources/org/opensearch/ml/engine/algorithms/text_embedding/traced_small_model.zip?raw=true', - }) - ) - .then(({ task_id: taskId }) => - cy.cyclingCheckTask({ - taskId, - }) - ) - .then(({ model_id: modelId }) => { - uploadedModelId = modelId; - return cy.loadMLCommonsModel(modelId); - }) - .then(({ task_id: taskId }) => - cy.cyclingCheckTask({ - taskId, - rejectOnError: false, - }) - ) - .then(({ error }) => { - if (error) { - uploadedModelLoadedError = error; - } - }); - }); - - after(() => { - if (uploadedModelId) { - cy.unloadMLCommonsModel(uploadedModelId); - cy.deleteMLCommonsModel(uploadedModelId); - } }); - it('should return to monitoring page when visit root', () => { cy.visit(MLC_URL.ROOT); cy.url().should('include', MLC_URL.OVERVIEW); }); - it('should display page header and deployed model name, status and id', () => { - cy.visit(MLC_URL.OVERVIEW); - - cy.contains('h1', 'Overview'); - cy.contains('h2', 'Deployed models'); + describe('custom upload model', () => { + let uploadedModelId; + let uploadedModelLoadedError; + const uploadModelName = `traced_small_model-${new Date() + .getTime() + .toString(34)}`; + before(() => { + cy.disableNativeMemoryCircuitBreaker(); + cy.enableRegisterModelViaURL(); + cy.wait(1000); + + cy.registerModelGroup({ + name: 'model-group', + }) + .then(({ model_group_id }) => + cy.uploadModelByUrl({ + name: uploadModelName, + version: '1.0.0', + model_format: 'TORCH_SCRIPT', + model_task_type: 'text_embedding', + model_group_id, + model_content_hash_value: + 'e13b74006290a9d0f58c1376f9629d4ebc05a0f9385f40db837452b167ae9021', + model_config: { + model_type: 'bert', + embedding_dimension: 768, + framework_type: 'sentence_transformers', + all_config: + '{"architectures":["BertModel"],"max_position_embeddings":512,"model_type":"bert","num_attention_heads":12,"num_hidden_layers":6}', + }, + url: 'https://github.com/opensearch-project/ml-commons/blob/2.x/ml-algorithms/src/test/resources/org/opensearch/ml/engine/algorithms/text_embedding/traced_small_model.zip?raw=true', + }) + ) + .then(({ task_id: taskId }) => + cy.cyclingCheckTask({ + taskId, + }) + ) + .then(({ model_id: modelId }) => { + uploadedModelId = modelId; + return cy.loadMLCommonsModel(modelId); + }) + .then(({ task_id: taskId }) => + cy.cyclingCheckTask({ + taskId, + rejectOnError: false, + }) + ) + .then(({ error }) => { + if (error) { + uploadedModelLoadedError = error; + } + }); + }); - cy.get('[aria-label="Search by name or ID"]').type(uploadModelName); + after(() => { + if (uploadedModelId) { + cy.unloadMLCommonsModel(uploadedModelId); + cy.deleteMLCommonsModel(uploadedModelId); + } + }); - cy.contains(uploadedModelId) - .closest('tr') - .contains(uploadedModelLoadedError ? 'Not responding' : 'Responding') - .closest('tr') - .contains(uploadModelName); + it('should display page header and deployed model name, status and source', () => { + cy.visit(MLC_URL.OVERVIEW); - cy.contains('h1', 'Overview'); - cy.contains('h2', 'Deployed models'); - }); + cy.contains('h1', 'Overview'); + cy.contains('h2', 'Models'); - it('should open preview panel after view detail button click', () => { - cy.visit(MLC_URL.OVERVIEW); - - cy.get('[aria-label="Search by name or ID"]').type(uploadModelName); + cy.get('[aria-label="Search by model name or ID"]').type( + uploadedModelId + ); - cy.contains(uploadedModelId) - .closest('tr') - .find('[aria-label="view detail"]') - .click(); + cy.contains(uploadModelName) + .closest('tr') + .contains(uploadedModelLoadedError ? 'Not responding' : 'Responding') + .closest('tr') + .contains('Local'); + }); - cy.contains('.euiFlyoutHeader > h3', uploadModelName); - cy.contains('.euiFlyoutBody', uploadedModelId); - }); + it('should open preview panel after view detail button click', () => { + cy.visit(MLC_URL.OVERVIEW); - it('should show empty nodes when deployed model profiling loading', () => { - cy.intercept( - 'GET', - MLC_DASHBOARD_API.DEPLOYED_MODEL_PROFILE.replace( - ':modelID', + cy.get('[aria-label="Search by model name or ID"]').type( uploadedModelId - ), - (req) => { - req.on('response', (res) => { - res.setDelay(3000); - }); - } - ).as('getDeployedModelProfile'); - - cy.visit(MLC_URL.OVERVIEW); + ); + + cy.contains(uploadModelName) + .closest('tr') + .find('[aria-label="view detail"]') + .click(); + + cy.get('div[role="dialog"]').contains(uploadModelName); + cy.get('div[role="dialog"]').contains(uploadedModelId); + cy.get('div[role="dialog"]').contains('Local'); + cy.get('div[role="dialog"]').contains('Status by node'); + }); + + it('should show empty nodes when deployed model profiling loading', () => { + cy.intercept( + 'GET', + MLC_DASHBOARD_API.DEPLOYED_MODEL_PROFILE.replace( + ':modelID', + uploadedModelId + ), + (req) => { + req.on('response', (res) => { + res.setDelay(3000); + }); + } + ).as('getDeployedModelProfile'); - cy.get('[aria-label="Search by name or ID"]').type(uploadModelName); + cy.visit(MLC_URL.OVERVIEW); - cy.contains(uploadedModelId) - .closest('tr') - .find('[aria-label="view detail"]') - .click(); + cy.get('[aria-label="Search by model name or ID"]').type( + uploadedModelId + ); + + cy.contains(uploadModelName) + .closest('tr') + .find('[aria-label="view detail"]') + .click(); + + cy.get('div[role="dialog"] .ml-nodesTableNodeIdCellText', { + timeout: 0, + }).should('not.exist'); + cy.wait('@getDeployedModelProfile'); + cy.get('div[role="dialog"] .ml-nodesTableNodeIdCellText').should( + 'exist' + ); + }); + }); - cy.get('div[role="dialog"] .ml-nodesTableNodeIdCellText', { - timeout: 0, - }).should('not.exist'); - cy.wait('@getDeployedModelProfile'); - cy.get('div[role="dialog"] .ml-nodesTableNodeIdCellText').should('exist'); + describe('remote model', () => { + let registeredRemoteModelId; + let remoteModelName; + before(() => { + remoteModelName = `remote sagemaker model-${new Date().getTime()}`; + cy.registerModel({ + name: remoteModelName, + function_name: 'remote', + version: '1.0.0', + description: 'test model', + connector: { + name: 'sagemaker: embedding', + description: 'Test connector for Sagemaker embedding model', + version: 1, + protocol: 'aws_sigv4', + credential: { + access_key: '...', + secret_key: '...', + session_token: '...', + }, + parameters: { + region: 'us-west-2', + service_name: 'sagemaker', + }, + actions: [ + { + action_type: 'predict', + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + url: 'https://runtime.sagemaker.us-west-2.amazonaws.com/endpoints/lmi-model-2023-06-24-01-35-32-275/invocations', + request_body: '["${parameters.inputs}"]', + }, + ], + }, + }) + .then(({ task_id: taskId }) => + cy.cyclingCheckTask({ + taskId, + }) + ) + .then(({ model_id: modelId }) => { + registeredRemoteModelId = modelId; + return cy.loadMLCommonsModel(modelId); + }) + .then(({ task_id: taskId }) => + cy.cyclingCheckTask({ + taskId, + rejectOnError: false, + }) + ); + }); + after(() => { + if (registeredRemoteModelId) { + cy.unloadMLCommonsModel(registeredRemoteModelId); + cy.wait(1000); + cy.deleteMLCommonsModel(registeredRemoteModelId); + cy.wait(1000); + } + }); + it('should show remote models with External source', () => { + cy.visit(MLC_URL.OVERVIEW); + + cy.get('[aria-label="Search by model name or ID"]').type( + registeredRemoteModelId + ); + + cy.contains('.euiTableRowCell', remoteModelName).should('exist'); + cy.contains('.euiTableRowCell', 'External').should('exist'); + }); + + it('should show show connector details after status details clicked', () => { + cy.visit(MLC_URL.OVERVIEW); + + cy.get('[aria-label="Search by model name or ID"]').type( + registeredRemoteModelId + ); + cy.contains(remoteModelName) + .closest('tr') + .find('[aria-label="view detail"]') + .click(); + cy.get('div[role="dialog"]').contains('External'); + cy.get('div[role="dialog"]').contains('Connector details'); + cy.get('div[role="dialog"]').contains('sagemaker: embedding'); + cy.get('div[role="dialog"]').contains( + 'Test connector for Sagemaker embedding model' + ); + }); }); }); } diff --git a/cypress/utils/plugins/ml-commons-dashboards/commands.js b/cypress/utils/plugins/ml-commons-dashboards/commands.js index a41797712..13767a377 100644 --- a/cypress/utils/plugins/ml-commons-dashboards/commands.js +++ b/cypress/utils/plugins/ml-commons-dashboards/commands.js @@ -9,7 +9,7 @@ Cypress.Commands.add('cyclingCheckTask', ({ taskId, rejectOnError = true }) => new Cypress.Promise((resolve, reject) => { const checkTask = () => { cy.getMLCommonsTask(taskId).then((payload) => { - if (payload.error) { + if (payload && payload.error) { if (rejectOnError) { reject(new Error(payload.error)); return; @@ -17,7 +17,7 @@ Cypress.Commands.add('cyclingCheckTask', ({ taskId, rejectOnError = true }) => resolve(payload); return; } - if (payload.state === 'COMPLETED') { + if (payload && payload.state === 'COMPLETED') { resolve(payload); return; } @@ -110,3 +110,47 @@ Cypress.Commands.add('enableRegisterModelViaURL', () => { failOnStatusCode: false, }); }); + +Cypress.Commands.add('disableConnectorAccessControl', () => { + cy.request('PUT', `${Cypress.env('openSearchUrl')}/_cluster/settings`, { + transient: { + 'plugins.ml_commons.connector_access_control_enabled': false, + }, + }); +}); + +Cypress.Commands.add( + 'setTrustedConnectorEndpointsRegex', + (trustedConnectorEndpointsRegex) => { + cy.request('PUT', `${Cypress.env('openSearchUrl')}/_cluster/settings`, { + transient: { + 'plugins.ml_commons.trusted_connector_endpoints_regex': + trustedConnectorEndpointsRegex, + }, + }); + } +); + +Cypress.Commands.add('createModelConnector', (body) => + cy + .request({ + method: 'POST', + url: MLC_API.CONNECTOR_CREATE, + body, + }) + .then(({ body }) => body) +); + +Cypress.Commands.add('registerModel', (body) => + cy + .request({ + method: 'POST', + url: MLC_API.MODEL_REGISTER, + body, + }) + .then(({ body }) => body) +); + +Cypress.Commands.add('deleteModelConnector', (connectorId) => + cy.request('DELETE', `${MLC_API.CONNECTOR_BASE}/${connectorId}`) +); diff --git a/cypress/utils/plugins/ml-commons-dashboards/constants.js b/cypress/utils/plugins/ml-commons-dashboards/constants.js index edb2b52bb..ac4fac3eb 100644 --- a/cypress/utils/plugins/ml-commons-dashboards/constants.js +++ b/cypress/utils/plugins/ml-commons-dashboards/constants.js @@ -25,6 +25,9 @@ export const MLC_API = { MODEL_UPLOAD: `${MLC_API_BASE}/models/_upload`, MODEL_GROUP_REGISTER: `${MLC_API_BASE}/model_groups/_register`, TASK_BASE: `${MLC_API_BASE}/tasks`, + CONNECTOR_BASE: `${MLC_API_BASE}/connectors`, + CONNECTOR_CREATE: `${MLC_API_BASE}/connectors/_create`, + MODEL_REGISTER: `${MLC_API_BASE}/models/_register`, }; const BASE_MLC_DASHBOARD_API = BASE_PATH + '/api/ml-commons'; From 852831203856ec7d52a79072226b508718412d94 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 13:43:54 -0400 Subject: [PATCH 36/46] fix workbench download text and csv schema (#814) (#817) Signed-off-by: Joshua Li (cherry picked from commit 46c0882627b5c8732b48564caf161719c6c3c0c8) Co-authored-by: Joshua Li Signed-off-by: leanne.laceybyrne@eliatra.com --- .../plugins/query-workbench-dashboards/ui.spec.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cypress/integration/plugins/query-workbench-dashboards/ui.spec.js b/cypress/integration/plugins/query-workbench-dashboards/ui.spec.js index dbb40967c..0187b6d52 100644 --- a/cypress/integration/plugins/query-workbench-dashboards/ui.spec.js +++ b/cypress/integration/plugins/query-workbench-dashboards/ui.spec.js @@ -207,14 +207,7 @@ describe('Test and verify SQL downloads', () => { 'select * from accounts where balance > 49500 order by account_number', }, }).then((response) => { - if ( - title === 'Download and verify CSV' || - title === 'Download and verify Text' - ) { - expect(response.body.data.body).to.have.string(files[file]); - } else { - expect(response.body.data.resp).to.have.string(files[file]); - } + expect(response.body.data.resp).to.have.string(files[file]); }); }); }); From 80eb97284ea1ece02493c1873ee129bacea8cce3 Mon Sep 17 00:00:00 2001 From: Derek Ho Date: Wed, 6 Sep 2023 17:02:56 -0400 Subject: [PATCH 37/46] update trace analytics cypress tests according to observability changes (#775) * update ftr Signed-off-by: Derek Ho * revert file Signed-off-by: Derek Ho * update with trace analytics accordian changes Signed-off-by: Derek Ho --------- Signed-off-by: Derek Ho Signed-off-by: leanne.laceybyrne@eliatra.com --- .../1_trace_analytics_dashboard.spec.js | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/cypress/integration/plugins/observability-dashboards/1_trace_analytics_dashboard.spec.js b/cypress/integration/plugins/observability-dashboards/1_trace_analytics_dashboard.spec.js index 83d0e1971..c67828db9 100644 --- a/cypress/integration/plugins/observability-dashboards/1_trace_analytics_dashboard.spec.js +++ b/cypress/integration/plugins/observability-dashboards/1_trace_analytics_dashboard.spec.js @@ -15,6 +15,9 @@ describe('Testing dashboard table empty state', () => { }, }); cy.wait(delayTime * 3); + cy.get( + '[data-test-subj="trace-groups-service-operation-accordian"]' + ).click(); }); it('Renders empty state', () => { @@ -31,6 +34,9 @@ describe('Testing dashboard table', () => { }, }); setTimeFilter(); + cy.get( + '[data-test-subj="trace-groups-service-operation-accordian"]' + ).click(); }); it('Renders the dashboard table', () => { @@ -73,11 +79,9 @@ describe('Testing dashboard table', () => { }); it('Redirects to traces table with filter', () => { - cy.wait(delayTime); cy.get('.euiLink').contains('13').click(); cy.wait(delayTime); - cy.get('h2.euiTitle').contains('Traces').should('exist'); cy.contains(' (13)').should('exist'); cy.contains('client_create_order').should('exist'); @@ -96,6 +100,9 @@ describe('Testing plots', () => { }, }); setTimeFilter(); + cy.get( + '[data-test-subj="trace-groups-service-operation-accordian"]' + ).click(); }); it('Renders service map', () => { @@ -104,13 +111,13 @@ describe('Testing plots', () => { cy.get('text[data-unformatted="200"]').should('exist'); cy.get('.vis-network').should('exist'); - cy.get('.euiButton__text[title="Error rate"]').click(); - cy.get('text.ytitle[data-unformatted="Error rate"]').should('exist'); - cy.get('text[data-unformatted="10%"]').should('exist'); + cy.get('.euiButton__text[title="Errors"]').click(); + cy.get('text.ytitle[data-unformatted="Error rate (%)"]').should('exist'); - cy.get('.euiButton__text[title="Throughput"]').click(); - cy.get('text.ytitle[data-unformatted="Throughput"]').should('exist'); - cy.get('text[data-unformatted="50"]').should('exist'); + cy.get('.euiButton__text[title="Request Rate"]').click(); + cy.get('text.ytitle[data-unformatted="Request rate (spans)"]').should( + 'exist' + ); cy.get('input[type="search"]').eq(1).focus().type('payment{enter}'); cy.wait(delayTime); From f6081d9bb8278a3dd6d5da3fc17973698af33089 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Sep 2023 08:09:41 +0800 Subject: [PATCH 38/46] Bump semver from 7.3.7 to 7.5.4 (#822) Bumps [semver](https://github.com/npm/node-semver) from 7.3.7 to 7.5.4. - [Release notes](https://github.com/npm/node-semver/releases) - [Changelog](https://github.com/npm/node-semver/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-semver/compare/v7.3.7...v7.5.4) --- updated-dependencies: - dependency-name: semver dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: leanne.laceybyrne@eliatra.com --- package-lock.json | 142 ++-------------------------------------------- 1 file changed, 6 insertions(+), 136 deletions(-) diff --git a/package-lock.json b/package-lock.json index 03919ea9c..e08940455 100644 --- a/package-lock.json +++ b/package-lock.json @@ -686,18 +686,6 @@ "node": ">=10" } }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -1401,18 +1389,6 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/eslint-plugin-import/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1509,18 +1485,6 @@ "node": ">= 0.8.0" } }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -3448,9 +3412,9 @@ "dev": true }, "node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3569,18 +3533,6 @@ "node": ">=8" } }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/string.prototype.trimend": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", @@ -3691,18 +3643,6 @@ "uri-js": "^4.2.2" } }, - "node_modules/table/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/table/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -3723,18 +3663,6 @@ "node": ">=10" } }, - "node_modules/table/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4604,17 +4532,6 @@ "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - } } }, "charenc": { @@ -5097,15 +5014,6 @@ "prelude-ls": "^1.2.1", "type-check": "^0.4.0" } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } } } }, @@ -5220,15 +5128,6 @@ "esutils": "^2.0.2" } }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6819,9 +6718,9 @@ "dev": true }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -6912,17 +6811,6 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "string.prototype.trimend": { @@ -7014,15 +6902,6 @@ "uri-js": "^4.2.2" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -7039,15 +6918,6 @@ "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } } } }, From ac153a5ab0e36013ff030cf89d68d420f9fd6a0a Mon Sep 17 00:00:00 2001 From: Kawika Avilla Date: Thu, 7 Sep 2023 12:37:12 -0700 Subject: [PATCH 39/46] [Dashboard] Retry visbuilder dashboard test (#823) Seeing it pass in video replays but occassionally doesn't pass. However, it doesn't pass with security enabled so I'd imagine it could be related to the refreshing of the session. Issue: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/4947 Signed-off-by: Kawika Avilla Signed-off-by: leanne.laceybyrne@eliatra.com --- .../apps/vis_builder/dashboard.spec.js | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/dashboard.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/dashboard.spec.js index 72bd3a35a..92aee997a 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/dashboard.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/dashboard.spec.js @@ -108,38 +108,42 @@ if (Cypress.env('VISBUILDER_ENABLED')) { cy.deleteSavedObjectByType(VB_SO_TYPE, `vb${cleanupKey}`); }); - it('Should be able to edit a visualization', () => { - // Navigate to vis builder - cy.getElementByTestId('dashboardEditMode').click(); - cy.getElementByTestId( - `embeddablePanelHeading-${toTestId(VB_METRIC_VIS_TITLE, '')}` - ) - .find('[data-test-subj="embeddablePanelToggleMenuIcon"]') - .click(); - cy.getElementByTestId('embeddablePanelAction-editPanel').click(); - cy.getElementByTestId('visualizationLoader') - .find('.mtrVis__value') - .should('contain.text', VB_INDEX_DOC_COUNT); - - // Edit visualization - const newLabel = 'Editied Label'; - cy.getElementByTestId('dropBoxField-metric-0').click(); - cy.vbEditAgg([ - { - testSubj: 'visEditorStringInput1customLabel', - type: 'input', - value: newLabel, - }, - ]); - - // Save and return - cy.getElementByTestId('visBuilderSaveAndReturnButton').click(); - - cy.getElementByTestId('visualizationLoader').should( - 'contain.text', - newLabel - ); - }); + it( + 'Should be able to edit a visualization', + { retries: { runMode: 2 } }, + () => { + // Navigate to vis builder + cy.getElementByTestId('dashboardEditMode').click(); + cy.getElementByTestId( + `embeddablePanelHeading-${toTestId(VB_METRIC_VIS_TITLE, '')}` + ) + .find('[data-test-subj="embeddablePanelToggleMenuIcon"]') + .click(); + cy.getElementByTestId('embeddablePanelAction-editPanel').click(); + cy.getElementByTestId('visualizationLoader') + .find('.mtrVis__value') + .should('contain.text', VB_INDEX_DOC_COUNT); + + // Edit visualization + const newLabel = 'Editied Label'; + cy.getElementByTestId('dropBoxField-metric-0').click(); + cy.vbEditAgg([ + { + testSubj: 'visEditorStringInput1customLabel', + type: 'input', + value: newLabel, + }, + ]); + + // Save and return + cy.getElementByTestId('visBuilderSaveAndReturnButton').click(); + + cy.getElementByTestId('visualizationLoader').should( + 'contain.text', + newLabel + ); + } + ); after(() => { cy.deleteIndex(VB_INDEX_ID); From d6201f911e61317a3ee8026cc5c239f60d8d0155 Mon Sep 17 00:00:00 2001 From: Ashish Agrawal Date: Fri, 8 Sep 2023 13:05:54 -0700 Subject: [PATCH 40/46] Add new tests for alerting dashboards (#832) Signed-off-by: Ashish Agrawal Signed-off-by: leanne.laceybyrne@eliatra.com --- ...sample_cluster_metrics_health_monitor.json | 58 ++++ .../sample_cluster_metrics_stats_monitor.json | 73 +++++ .../sample_composite_level_monitor.json | 274 ++++++++++++++++++ .../cluster_metrics_monitor_spec.js | 26 +- .../composite_level_monitor_spec.js | 191 ++++++++++++ .../monitors_dashboard_spec.js | 152 ++++++++++ .../query_level_monitor_spec.js | 11 +- .../alerting-dashboards-plugin/commands.js | 79 +++-- .../alerting-dashboards-plugin/constants.js | 1 + 9 files changed, 836 insertions(+), 29 deletions(-) create mode 100644 cypress/fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_health_monitor.json create mode 100644 cypress/fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_stats_monitor.json create mode 100644 cypress/fixtures/plugins/alerting-dashboards-plugin/sample_composite_level_monitor.json create mode 100644 cypress/integration/plugins/alerting-dashboards-plugin/composite_level_monitor_spec.js create mode 100644 cypress/integration/plugins/alerting-dashboards-plugin/monitors_dashboard_spec.js diff --git a/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_health_monitor.json b/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_health_monitor.json new file mode 100644 index 000000000..52ea935ed --- /dev/null +++ b/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_health_monitor.json @@ -0,0 +1,58 @@ +{ + "name": "sample_cluster_metrics_health_monitor", + "type": "monitor", + "monitor_type": "cluster_metrics_monitor", + "enabled": true, + "schedule": { + "period": { + "unit": "MINUTES", + "interval": 1 + } + }, + "inputs": [ + { + "uri": { + "api_type": "CLUSTER_HEALTH", + "path": "_cluster/health/", + "path_params": "", + "url": "http://localhost:9200/_cluster/health/" + } + } + ], + "triggers": [], + "ui_metadata": { + "schedule": { + "timezone": null, + "frequency": "interval", + "period": { + "unit": "MINUTES", + "interval": 1 + }, + "daily": 0, + "weekly": { + "tue": false, + "wed": false, + "thur": false, + "sat": false, + "fri": false, + "mon": false, + "sun": false + }, + "monthly": { + "type": "day", + "day": 1 + }, + "cronExpression": "0 */1 * * *" + }, + "search": { + "searchType": "clusterMetrics", + "timeField": "", + "aggregations": [], + "groupBy": [], + "bucketValue": 1, + "bucketUnitOfTime": "h", + "filters": [] + }, + "monitor_type": "cluster_metrics_monitor" + } +} diff --git a/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_stats_monitor.json b/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_stats_monitor.json new file mode 100644 index 000000000..a258b0586 --- /dev/null +++ b/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_stats_monitor.json @@ -0,0 +1,73 @@ +{ + "name": "sample_cluster_metrics_stats_monitor", + "type": "monitor", + "monitor_type": "cluster_metrics_monitor", + "enabled": true, + "schedule": { + "period": { + "unit": "MINUTES", + "interval": 1 + } + }, + "inputs": [ + { + "uri": { + "api_type": "CLUSTER_STATS", + "path": "_cluster/stats/", + "path_params": "", + "url": "http://localhost:9200/_cluster/stats/" + } + } + ], + "triggers": [ + { + "query_level_trigger": { + "id": "Y5mmA4kBIezNcMbMJnEy", + "name": "sample_cluster_metrics_stats_monitor-trigger1", + "severity": "1", + "condition": { + "script": { + "source": "ctx.results[0].indices.count >= 0", + "lang": "painless" + } + }, + "actions": [] + } + } + ], + "ui_metadata": { + "schedule": { + "timezone": null, + "frequency": "interval", + "period": { + "unit": "MINUTES", + "interval": 1 + }, + "daily": 0, + "weekly": { + "tue": false, + "wed": false, + "thur": false, + "sat": false, + "fri": false, + "mon": false, + "sun": false + }, + "monthly": { + "type": "day", + "day": 1 + }, + "cronExpression": "0 */1 * * *" + }, + "search": { + "searchType": "clusterMetrics", + "timeField": "", + "aggregations": [], + "groupBy": [], + "bucketValue": 1, + "bucketUnitOfTime": "h", + "filters": [] + }, + "monitor_type": "cluster_metrics_monitor" + } +} diff --git a/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_composite_level_monitor.json b/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_composite_level_monitor.json new file mode 100644 index 000000000..9b1f67bf4 --- /dev/null +++ b/cypress/fixtures/plugins/alerting-dashboards-plugin/sample_composite_level_monitor.json @@ -0,0 +1,274 @@ +{ + "sample_composite_monitor": { + "type": "workflow", + "schema_version": 0, + "name": "sampleComponentLevelMonitor", + "workflow_type": "composite", + "enabled": true, + "enabled_time": 1686908176848, + "schedule": { + "period": { + "interval": 1, + "unit": "MINUTES" + } + }, + "inputs": [ + { + "composite_input": { + "sequence": { + "delegates": [ + { + "order": 1, + "monitor_id": "qdYBw4gB2qeAWe54jQyZ" + }, + { + "order": 2, + "monitor_id": "rtYBw4gB2qeAWe54wAx5" + } + ] + } + } + } + ], + "triggers": [ + { + "chained_alert_trigger": { + "id": "pNaQw4gB2qeAWe54Fg2U", + "name": "sample_trigger", + "severity": "1", + "condition": { + "script": { + "source": "(monitor[id=qdYBw4gB2qeAWe54jQyZ]) && (monitor[id=rtYBw4gB2qeAWe54wAx5])", + "lang": "painless" + } + }, + "actions": [ + { + "id": "pdaQw4gB2qeAWe54Fg2U", + "name": "sample_channel", + "destination_id": "6dYFw4gB2qeAWe54NgyL", + "message_template": { + "source": "Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue.\n - Trigger: {{ctx.trigger.name}}\n - Severity: {{ctx.trigger.severity}}\n - Period start: {{ctx.periodStart}}\n - Period end: {{ctx.periodEnd}}", + "lang": "mustache" + }, + "throttle_enabled": false, + "subject_template": { + "source": "Monitor {{ctx.monitor.name}} triggered an alert {{ctx.trigger.name}}", + "lang": "mustache" + } + } + ] + } + } + ], + "last_update_time": 1686908180116, + "owner": "alerting", + "monitor_type": "composite" + }, + "sample_composite_index": { + "mappings": { + "properties": { + "audit_category": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "audit_node_host_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "audit_node_id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + } + }, + "sample_composite_associated_monitor_1": { + "name": "monitorOne", + "type": "monitor", + "monitor_type": "doc_level_monitor", + "enabled": false, + "schedule": { + "period": { + "unit": "MINUTES", + "interval": 1 + } + }, + "inputs": [ + { + "doc_level_input": { + "description": "", + "indices": ["sample_index_1"], + "queries": [ + { + "id": "monitor_1_query_1", + "name": "monitor_1_query_1", + "query": "NOT (audit_category:\"sample_text\")", + "tags": [] + }, + { + "id": "monitor_1_query_2", + "name": "monitor_1_query_2", + "query": "NOT (audit_node_host_name:\"sample_text\")", + "tags": [] + }, + { + "id": "monitor_1_query_3", + "name": "monitor_1_query_3", + "query": "NOT (audit_node_id:\"sample_text\")", + "tags": [] + } + ] + } + } + ], + "triggers": [ + { + "document_level_trigger": { + "id": "sample_trigger_id_1", + "name": "monitor_1_query_2", + "severity": "1", + "condition": { + "script": { + "source": "query[name=monitor_1_query_1] || query[name=monitor_1_query_2] && query[name=monitor_1_query_3]", + "lang": "painless" + } + }, + "actions": [] + } + } + ] + }, + "sample_composite_associated_monitor_2": { + "name": "monitorTwo", + "type": "monitor", + "monitor_type": "doc_level_monitor", + "enabled": false, + "schedule": { + "period": { + "unit": "MINUTES", + "interval": 1 + } + }, + "inputs": [ + { + "doc_level_input": { + "description": "", + "indices": ["sample_index_2"], + "queries": [ + { + "id": "monitor_2_query_1", + "name": "monitor_2_query_1", + "query": "NOT (audit_category:\"sample_text\")", + "tags": [] + }, + { + "id": "monitor_2_query_2", + "name": "monitor_2_query_2", + "query": "NOT (audit_node_host_name:\"sample_text\")", + "tags": [] + }, + { + "id": "monitor_2_query_3", + "name": "monitor_2_query_3", + "query": "NOT (audit_node_id:\"sample_text\")", + "tags": [] + } + ] + } + } + ], + "triggers": [ + { + "document_level_trigger": { + "id": "sample_trigger_2", + "name": "monitor_2_query_2", + "severity": "1", + "condition": { + "script": { + "source": "query[name=monitor_2_query_1] || query[name=monitor_2_query_2] && query[name=monitor_2_query_3]", + "lang": "painless" + } + }, + "actions": [] + } + } + ] + }, + "sample_composite_associated_monitor_3": { + "name": "monitorThree", + "type": "monitor", + "monitor_type": "doc_level_monitor", + "enabled": false, + "schedule": { + "period": { + "unit": "MINUTES", + "interval": 1 + } + }, + "inputs": [ + { + "doc_level_input": { + "description": "", + "indices": ["sample_index_2"], + "queries": [ + { + "id": "monitor_2_query_1", + "name": "monitor_2_query_1", + "query": "NOT (audit_category:\"sample_text\")", + "tags": [] + }, + { + "id": "monitor_2_query_2", + "name": "monitor_2_query_2", + "query": "NOT (audit_node_host_name:\"sample_text\")", + "tags": [] + }, + { + "id": "monitor_2_query_3", + "name": "monitor_2_query_3", + "query": "NOT (audit_node_id:\"sample_text\")", + "tags": [] + } + ] + } + } + ], + "triggers": [ + { + "document_level_trigger": { + "id": "sample_trigger_2", + "name": "monitor_2_query_2", + "severity": "1", + "condition": { + "script": { + "source": "query[name=monitor_2_query_1] || query[name=monitor_2_query_2] && query[name=monitor_2_query_3]", + "lang": "painless" + } + }, + "actions": [] + } + } + ] + }, + "sample_composite_associated_index_document": { + "audit_category": "FAILED_LOGIN", + "audit_node_host_name": "127.0.0.1", + "audit_node_id": "sample_node_id" + } +} diff --git a/cypress/integration/plugins/alerting-dashboards-plugin/cluster_metrics_monitor_spec.js b/cypress/integration/plugins/alerting-dashboards-plugin/cluster_metrics_monitor_spec.js index be3075ff4..9d6c165ec 100644 --- a/cypress/integration/plugins/alerting-dashboards-plugin/cluster_metrics_monitor_spec.js +++ b/cypress/integration/plugins/alerting-dashboards-plugin/cluster_metrics_monitor_spec.js @@ -105,10 +105,12 @@ describe('ClusterMetricsMonitor', () => { cy.contains('There are no existing monitors'); // Go to create monitor page - cy.contains('Create monitor').click(); + cy.contains('Create monitor', { timeout: 20000 }).click({ force: true }); // Select ClusterMetrics radio card - cy.get('[data-test-subj="clusterMetricsMonitorRadioCard"]').click(); + cy.get('[data-test-subj="clusterMetricsMonitorRadioCard"]').click({ + force: true, + }); // Wait for input to load and then type in the monitor name cy.get('input[name="name"]').type(SAMPLE_CLUSTER_METRICS_HEALTH_MONITOR); @@ -119,7 +121,7 @@ describe('ClusterMetricsMonitor', () => { ); // Confirm the Query parameters field is present and described as "optional" - cy.contains('Query parameters - optional'); + cy.contains('Path parameters - optional'); cy.get('[data-test-subj="clusterMetricsParamsFieldText"]'); // Press the 'Run for response' button @@ -144,7 +146,7 @@ describe('ClusterMetricsMonitor', () => { // .type('{downarrow}{enter}'); // Click the create button - cy.get('button').contains('Create').click(); + cy.get('button').contains('Create').click({ force: true }); // Confirm we can see only one row in the trigger list by checking element cy.contains('This table contains 1 row'); @@ -153,7 +155,7 @@ describe('ClusterMetricsMonitor', () => { cy.contains(SAMPLE_TRIGGER); // Go back to the Monitors list - cy.get('a').contains('Monitors').click(); + cy.get('a').contains('Monitors').click({ force: true }); // Confirm we can see the created monitor in the list cy.contains(SAMPLE_CLUSTER_METRICS_HEALTH_MONITOR); @@ -223,7 +225,7 @@ describe('ClusterMetricsMonitor', () => { }); }); - describe('displays Query parameters field appropriately', () => { + describe('displays Path parameters field appropriately', () => { beforeEach(() => { cy.deleteAllMonitors(); cy.reload(); @@ -234,10 +236,12 @@ describe('ClusterMetricsMonitor', () => { cy.contains('There are no existing monitors'); // Go to create monitor page - cy.contains('Create monitor').click(); + cy.contains('Create monitor', { timeout: 20000 }).click({ force: true }); // Select ClusterMetrics radio card - cy.get('[data-test-subj="clusterMetricsMonitorRadioCard"]').click(); + cy.get('[data-test-subj="clusterMetricsMonitorRadioCard"]').click({ + force: true, + }); // Wait for input to load and then type in the monitor name cy.get('input[name="name"]').type( @@ -249,9 +253,9 @@ describe('ClusterMetricsMonitor', () => { 'list snapshots{enter}' ); - // Confirm the Query parameters field is present and is not described as "optional" - cy.contains('Query parameters - optional').should('not.exist'); - cy.contains('Query parameters'); + // Confirm the Path parameters field is present and is not described as "optional" + cy.contains('Path parameters - optional').should('not.exist'); + cy.contains('Path parameters'); cy.get('[data-test-subj="clusterMetricsParamsFieldText"]'); }); }); diff --git a/cypress/integration/plugins/alerting-dashboards-plugin/composite_level_monitor_spec.js b/cypress/integration/plugins/alerting-dashboards-plugin/composite_level_monitor_spec.js new file mode 100644 index 000000000..fe1f47d87 --- /dev/null +++ b/cypress/integration/plugins/alerting-dashboards-plugin/composite_level_monitor_spec.js @@ -0,0 +1,191 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + ALERTING_API, + ALERTING_PLUGIN_NAME, +} from '../../../utils/plugins/alerting-dashboards-plugin/constants'; +import sampleCompositeJson from '../../../fixtures/plugins/alerting-dashboards-plugin/sample_composite_level_monitor.json'; +import * as _ from 'lodash'; +import { BASE_PATH } from '../../../utils/base_constants'; + +const sample_index_1 = 'sample_index_1'; +const sample_index_2 = 'sample_index_2'; +const SAMPLE_VISUAL_EDITOR_MONITOR = + 'sample_visual_editor_composite_level_monitor'; + +const clearAll = () => { + cy.deleteIndexByName(sample_index_1); + cy.deleteIndexByName(sample_index_2); + + cy.deleteAllAlerts(); + cy.deleteAllMonitors(); +}; + +describe('CompositeLevelMonitor', () => { + before(() => { + clearAll(); + + // Create indices + cy.createIndexByName( + sample_index_1, + sampleCompositeJson.sample_composite_index + ); + cy.createIndexByName( + sample_index_2, + sampleCompositeJson.sample_composite_index + ); + + // Create associated monitors + cy.createMonitor(sampleCompositeJson.sample_composite_associated_monitor_1); + cy.createMonitor(sampleCompositeJson.sample_composite_associated_monitor_2); + cy.createMonitor(sampleCompositeJson.sample_composite_associated_monitor_3); + }); + + beforeEach(() => { + // Set welcome screen tracking to false + localStorage.setItem('home:welcome:show', 'false'); + }); + + describe('can be created', () => { + beforeEach(() => { + // Visit Alerting OpenSearch Dashboards + cy.visit(`${BASE_PATH}/app/${ALERTING_PLUGIN_NAME}#/monitors`); + + // Common text to wait for to confirm page loaded, give up to 20 seconds for initial load + cy.contains('Create monitor', { timeout: 20000 }); + + // Go to create monitor page + cy.contains('Create monitor').click({ force: true }); + + // Select the Composite-Level Monitor type + cy.get('[data-test-subj="compositeLevelMonitorRadioCard"]').click({ + force: true, + }); + }); + + it('by visual editor', () => { + // Select visual editor for method of definition + cy.get('[data-test-subj="visualEditorRadioCard"]').click({ force: true }); + + // Wait for input to load and then type in the monitor name + cy.get('input[name="name"]').type(SAMPLE_VISUAL_EDITOR_MONITOR); + + // Select associated monitors + cy.get('[data-test-subj="monitors_list_0"]').type('monitorOne', { + delay: 50, + }); + cy.get('[title="monitorOne"]').click({ force: true }); + + cy.get('[data-test-subj="monitors_list_1"]').type('monitorTwo', { + delay: 50, + }); + cy.get('[title="monitorTwo"]').click({ force: true }); + + cy.get('button').contains('Add trigger').click({ force: true }); + + // Type trigger name + cy.get('[data-test-subj="composite-trigger-name"]') + .type('{selectall}') + .type('{backspace}') + .type('Composite trigger'); + + cy.intercept('api/alerting/workflows').as('createMonitorRequest'); + cy.intercept(`api/alerting/monitors?*`).as('getMonitorsRequest'); + cy.get('button').contains('Create').click({ force: true }); + + // Wait for monitor to be created + cy.wait('@createMonitorRequest').then(() => { + // Verify the monitor name on details page + cy.contains(SAMPLE_VISUAL_EDITOR_MONITOR); + + // Go back to the Monitors list + cy.get('a').contains('Monitors').click({ force: true }); + + cy.contains(SAMPLE_VISUAL_EDITOR_MONITOR); + }); + }); + }); + + describe('can be edited', () => { + beforeEach(() => { + const body = { + size: 200, + query: { + match_all: {}, + }, + }; + cy.request({ + method: 'GET', + url: `${Cypress.env('openSearchUrl')}${ + ALERTING_API.MONITOR_BASE + }/_search`, + failOnStatusCode: false, // In case there is no alerting config index in cluster, where the status code is 404 + body, + }).then((response) => { + if (response.status === 200) { + const monitors = response.body.hits.hits; + const createdMonitor = _.find( + monitors, + (monitor) => monitor._source.name === SAMPLE_VISUAL_EDITOR_MONITOR + ); + if (createdMonitor) { + cy.visit( + `${BASE_PATH}/app/${ALERTING_PLUGIN_NAME}#/monitors/${createdMonitor._id}?action=update-monitor&type=workflow` + ); + } else { + cy.log( + 'Failed to get created monitor ', + SAMPLE_VISUAL_EDITOR_MONITOR + ); + throw new Error( + `Failed to get created monitor ${SAMPLE_VISUAL_EDITOR_MONITOR}` + ); + } + } else { + cy.log('Failed to get all monitors.', response); + } + }); + }); + + it('by visual editor', () => { + // Verify edit page + cy.contains('Edit monitor', { timeout: 20000 }); + cy.get('input[name="name"]').type('_edited'); + + cy.get('label').contains('Visual editor').click({ force: true }); + + cy.get('button').contains('Add another monitor').click({ force: true }); + + cy.get('[data-test-subj="monitors_list_2"]').type('monitorThree', { + delay: 50, + }); + cy.get('[title="monitorThree"]').click({ force: true }); + + cy.get('button').contains('Composite trigger').click({ force: true }); + + cy.get('[data-test-subj="condition-add-options-btn_0"]').click({ + force: true, + }); + cy.get('[data-test-subj="select-expression_0_2"]').click({ force: true }); + cy.wait(1000); + cy.get('[data-test-subj="monitors-combobox-0-2"]') + .type('monitorThree', { delay: 50 }) + .type('{enter}'); + + cy.intercept('api/alerting/workflows/*').as('updateMonitorRequest'); + cy.get('button').contains('Update').click({ force: true }); + + // Wait for monitor to be created + cy.wait('@updateMonitorRequest').then(() => { + cy.get('.euiTitle--large').contains( + `${SAMPLE_VISUAL_EDITOR_MONITOR}_edited` + ); + }); + }); + }); + + after(() => clearAll()); +}); diff --git a/cypress/integration/plugins/alerting-dashboards-plugin/monitors_dashboard_spec.js b/cypress/integration/plugins/alerting-dashboards-plugin/monitors_dashboard_spec.js new file mode 100644 index 000000000..ba8898ee6 --- /dev/null +++ b/cypress/integration/plugins/alerting-dashboards-plugin/monitors_dashboard_spec.js @@ -0,0 +1,152 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + ALERTING_INDEX, + ALERTING_PLUGIN_NAME, +} from '../../../utils/plugins/alerting-dashboards-plugin/constants'; +import sampleAlertsFlyoutBucketMonitor from '../../../fixtures/plugins/alerting-dashboards-plugin/sample_alerts_flyout_bucket_level_monitor.json'; +import sampleAlertsFlyoutQueryMonitor from '../../../fixtures/plugins/alerting-dashboards-plugin/sample_alerts_flyout_query_level_monitor.json'; +import sampleClusterMetricsHealthMonitor from '../../../fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_health_monitor.json'; +import sampleClusterMetricsStatsMonitor from '../../../fixtures/plugins/alerting-dashboards-plugin/sample_cluster_metrics_stats_monitor.json'; +import { BASE_PATH } from '../../../utils/base_constants'; + +const queryMonitor = { + ...sampleAlertsFlyoutQueryMonitor, + id: 'monitors_dashboard_cypress_query_level', + name: 'monitors_dashboard_cypress_query_level', + enabled: false, +}; +const bucketMonitor = { + ...sampleAlertsFlyoutBucketMonitor, + id: 'monitors_dashboard_cypress_bucket_level', + name: 'monitors_dashboard_cypress_bucket_level', + enabled: false, +}; +const clusterHealthMonitor = { + ...sampleClusterMetricsHealthMonitor, + id: 'monitors_dashboard_cypress_cluster_health', + name: 'monitors_dashboard_cypress_cluster_health', + enabled: false, + triggers: [ + { + query_level_trigger: { + id: 'WJmlA4kBIezNcMbMwnFg', + name: 'sample_cluster_metrics_health_monitor-trigger1', + severity: '1', + condition: { + script: { + source: 'ctx.results[0].status != "green"', + lang: 'painless', + }, + }, + actions: [], + }, + }, + ], +}; +const clusterStatsMonitor = { + ...sampleClusterMetricsStatsMonitor, + enabled: false, + id: 'monitors_dashboard_cypress_cluster_stats', + name: 'monitors_dashboard_cypress_cluster_stats', +}; +const testMonitors = [ + { + monitor: queryMonitor, + expectedAlertsCount: 1, + triggerName: queryMonitor.triggers[0].query_level_trigger.name, + }, + { + monitor: bucketMonitor, + expectedAlertsCount: 46, + triggerName: bucketMonitor.triggers[0].bucket_level_trigger.name, + }, + { + monitor: clusterHealthMonitor, + expectedAlertsCount: 1, + triggerName: clusterHealthMonitor.triggers[0].query_level_trigger.name, + }, + { + monitor: clusterStatsMonitor, + expectedAlertsCount: 1, + triggerName: clusterStatsMonitor.triggers[0].query_level_trigger.name, + }, +]; + +describe('Monitors dashboard page', () => { + before(() => { + // Delete any existing monitors + cy.deleteAllMonitors() + .then(() => { + // Load sample data + cy.loadSampleEcommerceData(); + }) + .then(() => { + // Short wait to reduce flakiness while ecommerce data is loaded + cy.wait(5000); + + // Create the test monitors + testMonitors.forEach((entry) => + cy.createAndExecuteMonitor(entry.monitor) + ); + }); + + // Visit Alerting OpenSearch Dashboards + cy.visit(`${BASE_PATH}/app/${ALERTING_PLUGIN_NAME}#/monitors`); + }); + + beforeEach(() => { + // Refresh Alerting OpenSearch Dashboards + cy.visit(`${BASE_PATH}/app/${ALERTING_PLUGIN_NAME}#/monitors`); + + // Common text to wait for to confirm page loaded, give up to 20 seconds for initial load + cy.contains('Create monitor', { timeout: 20000 }); + }); + + it('Displays expected number of alerts', () => { + // Ensure the 'Monitor name' column is sorted in ascending order by sorting another column first + cy.contains('Last updated by').click({ force: true }); + cy.contains('Monitor name').click({ force: true }); + + testMonitors.forEach((entry) => { + cy.get('tbody > tr') + .filter(`:contains(${entry.monitor.name})`, { timeout: 20000 }) + .within(() => { + cy.get('[class="euiTableRowCell"]') + .filter(':contains(Latest alert)', { timeout: 20000 }) + .should('contain', entry.triggerName); + + cy.get('[class="euiTableRowCell"]') + .filter(':contains(State)', { timeout: 20000 }) + .should('contain', 'Disabled'); + + cy.get('[class="euiTableRowCell"]') + .filter(':contains(Active)', { timeout: 20000 }) + .should('contain', entry.expectedAlertsCount); + + cy.get('[class="euiTableRowCell"]') + .filter(':contains(Acknowledged)', { timeout: 20000 }) + .should('contain', 0); + + cy.get('[class="euiTableRowCell"]') + .filter(':contains(Errors)', { timeout: 20000 }) + .should('contain', 0); + + cy.get('[class="euiTableRowCell"]') + .filter(':contains(Ignored)', { timeout: 20000 }) + .should('contain', 0); + }); + }); + }); + + after(() => { + // Delete all monitors + cy.deleteAllMonitors(); + + // Delete sample data + cy.deleteIndexByName(ALERTING_INDEX.SAMPLE_DATA_ECOMMERCE); + }); +}); diff --git a/cypress/integration/plugins/alerting-dashboards-plugin/query_level_monitor_spec.js b/cypress/integration/plugins/alerting-dashboards-plugin/query_level_monitor_spec.js index 9ff1aac74..5a7a9d858 100644 --- a/cypress/integration/plugins/alerting-dashboards-plugin/query_level_monitor_spec.js +++ b/cypress/integration/plugins/alerting-dashboards-plugin/query_level_monitor_spec.js @@ -34,7 +34,12 @@ const addVisualQueryLevelTrigger = ( thresholdValue ) => { // Click 'Add trigger' button - cy.contains('Add trigger', { timeout: 20000 }).click({ force: true }); + if (triggerIndex === 0) + cy.contains('Add trigger', { timeout: 20000 }).click({ force: true }); + else + cy.contains('Add another trigger', { timeout: 20000 }).click({ + force: true, + }); if (isEdit) { // TODO: Passing button props in EUI accordion was added in newer versions (31.7.0+). @@ -244,6 +249,10 @@ describe('Query-Level Monitors', () => { // Click the Delete button cy.contains('Delete').click({ force: true }); + cy.wait(1000); + cy.get('[data-test-subj="confirmModalConfirmButton"]').click({ + force: true, + }); // Confirm we can see an empty monitor list cy.contains('There are no existing monitors'); diff --git a/cypress/utils/plugins/alerting-dashboards-plugin/commands.js b/cypress/utils/plugins/alerting-dashboards-plugin/commands.js index 962566271..c0a49307a 100644 --- a/cypress/utils/plugins/alerting-dashboards-plugin/commands.js +++ b/cypress/utils/plugins/alerting-dashboards-plugin/commands.js @@ -36,7 +36,7 @@ Cypress.Commands.add('createMonitor', (monitorJSON) => { 'POST', `${Cypress.env('openSearchUrl')}${ALERTING_API.MONITOR_BASE}`, monitorJSON - ); + ).then(({ body }) => body); }); Cypress.Commands.add('createAndExecuteMonitor', (monitorJSON) => { @@ -50,10 +50,43 @@ Cypress.Commands.add('createAndExecuteMonitor', (monitorJSON) => { `${Cypress.env('openSearchUrl')}${ALERTING_API.MONITOR_BASE}/${ response.body._id }/_execute` - ); + ).then(({ body }) => body); }); }); +Cypress.Commands.add('executeMonitor', (monitorID) => { + cy.request( + 'POST', + `${Cypress.env('openSearchUrl')}${ + ALERTING_API.MONITOR_BASE + }/${monitorID}/_execute` + ).then(({ body }) => body); +}); + +Cypress.Commands.add('executeCompositeMonitor', (monitorID) => { + cy.request( + 'POST', + `${Cypress.env('openSearchUrl')}${ + ALERTING_API.WORKFLOW_BASE + }/${monitorID}/_execute` + ).then(({ body }) => body); +}); + +Cypress.Commands.add('deleteAllAlerts', () => { + cy.request({ + method: 'POST', + url: `${Cypress.env( + 'openSearchUrl' + )}/.opendistro-alerting-alert*/_delete_by_query`, + body: { + query: { + match_all: {}, + }, + }, + failOnStatusCode: false, + }).then(({ body }) => body); +}); + Cypress.Commands.add('deleteMonitorByName', (monitorName) => { const body = { query: { @@ -75,7 +108,7 @@ Cypress.Commands.add('deleteMonitorByName', (monitorName) => { `${Cypress.env('openSearchUrl')}${ALERTING_API.MONITOR_BASE}/${ response.body.hits.hits[0]._id }` - ); + ).then(({ body }) => body); }); }); @@ -83,9 +116,7 @@ Cypress.Commands.add('deleteAllMonitors', () => { const body = { size: 200, query: { - exists: { - field: 'monitor', - }, + match_all: {}, }, }; cy.request({ @@ -95,13 +126,21 @@ Cypress.Commands.add('deleteAllMonitors', () => { body, }).then((response) => { if (response.status === 200) { - for (let i = 0; i < response.body.hits.total.value; i++) { - cy.request( - 'DELETE', - `${Cypress.env('openSearchUrl')}${ALERTING_API.MONITOR_BASE}/${ - response.body.hits.hits[i]._id - }` - ); + const monitors = response.body.hits.hits.sort((monitor) => + monitor._source.type === 'workflow' ? -1 : 1 + ); + for (let i = 0; i < monitors.length; i++) { + if (monitors[i]._id) { + cy.request({ + method: 'DELETE', + url: `${Cypress.env('openSearchUrl')}${ + monitors[i]._source.type === 'workflow' + ? ALERTING_API.WORKFLOW_BASE + : ALERTING_API.MONITOR_BASE + }/${monitors[i]._id}`, + failOnStatusCode: false, + }).then(({ body }) => body); + } } } else { cy.log('Failed to get all monitors.', response); @@ -110,11 +149,17 @@ Cypress.Commands.add('deleteAllMonitors', () => { }); Cypress.Commands.add('createIndexByName', (indexName) => { - cy.request('PUT', `${Cypress.env('openSearchUrl')}/${indexName}`); + cy.request('PUT', `${Cypress.env('openSearchUrl')}/${indexName}`).then( + ({ body }) => body + ); }); Cypress.Commands.add('deleteIndexByName', (indexName) => { - cy.request('DELETE', `${Cypress.env('openSearchUrl')}/${indexName}`); + cy.request({ + method: 'DELETE', + url: `${Cypress.env('openSearchUrl')}/${indexName}`, + failOnStatusCode: false, + }).then(({ body }) => body); }); Cypress.Commands.add( @@ -124,7 +169,7 @@ Cypress.Commands.add( 'POST', `${Cypress.env('openSearchUrl')}/${indexName}/_doc/${documentId}`, documentBody - ); + ).then(({ body }) => body); } ); @@ -133,5 +178,5 @@ Cypress.Commands.add('loadSampleEcommerceData', () => { method: 'POST', headers: { 'osd-xsrf': 'opensearch-dashboards' }, url: `${BASE_PATH}/api/sample_data/ecommerce`, - }); + }).then(({ body }) => body); }); diff --git a/cypress/utils/plugins/alerting-dashboards-plugin/constants.js b/cypress/utils/plugins/alerting-dashboards-plugin/constants.js index f4a1aad82..f2df921ac 100644 --- a/cypress/utils/plugins/alerting-dashboards-plugin/constants.js +++ b/cypress/utils/plugins/alerting-dashboards-plugin/constants.js @@ -12,6 +12,7 @@ export const ALERTING_INDEX = { export const ALERTING_API = { MONITOR_BASE: `${API_ROUTE_PREFIX}/monitors`, + WORKFLOW_BASE: `${API_ROUTE_PREFIX}/workflows`, DESTINATION_BASE: `${API_ROUTE_PREFIX}/destinations`, }; From 1489306544c44fed50f54733c4197686ebebf438 Mon Sep 17 00:00:00 2001 From: Junqiu Lei Date: Wed, 13 Sep 2023 14:39:22 -0500 Subject: [PATCH 41/46] Update source element in import_vector_map_tab.spec.js (#844) * Update source element in import_vector_map_tab.spec.js Signed-off-by: Junqiu Lei * Update source element in import_vector_map_tab.spec.js Signed-off-by: Junqiu Lei --------- Signed-off-by: Junqiu Lei Signed-off-by: leanne.laceybyrne@eliatra.com --- .../import_vector_map_tab.spec.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cypress/integration/plugins/custom-import-map-dashboards/import_vector_map_tab.spec.js b/cypress/integration/plugins/custom-import-map-dashboards/import_vector_map_tab.spec.js index 415c8b970..900955272 100644 --- a/cypress/integration/plugins/custom-import-map-dashboards/import_vector_map_tab.spec.js +++ b/cypress/integration/plugins/custom-import-map-dashboards/import_vector_map_tab.spec.js @@ -22,8 +22,10 @@ describe('Verify the presence of import custom map tab in region map plugin', () // Click on "Region Map" icon cy.contains('Region Map').click({ force: true }); - // Select index source - [Flights] Flight Log - cy.contains('[Flights] Flight Log').click({ force: true }); + // Select index source - opensearch_dashboards_sample_data_flights + cy.contains('opensearch_dashboards_sample_data_flights').click({ + force: true, + }); }); it('checks import custom map tab is present', () => { From 3a3063a72c3155a4d0f94a793f5122df879c4915 Mon Sep 17 00:00:00 2001 From: Junqiu Lei Date: Thu, 14 Sep 2023 12:04:58 -0500 Subject: [PATCH 42/46] Use index pattern id to find page in import_vector_map_tab.spec.js (#847) Signed-off-by: Junqiu Lei Signed-off-by: leanne.laceybyrne@eliatra.com --- .../import_vector_map_tab.spec.js | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/cypress/integration/plugins/custom-import-map-dashboards/import_vector_map_tab.spec.js b/cypress/integration/plugins/custom-import-map-dashboards/import_vector_map_tab.spec.js index 900955272..9fb5bb5be 100644 --- a/cypress/integration/plugins/custom-import-map-dashboards/import_vector_map_tab.spec.js +++ b/cypress/integration/plugins/custom-import-map-dashboards/import_vector_map_tab.spec.js @@ -14,18 +14,14 @@ describe('Verify the presence of import custom map tab in region map plugin', () cy.deleteAllIndices(); miscUtils.addSampleData(); - cy.visit(`${BASE_PATH}/app/visualize#/`); - - // Click on "Create Visualization" tab - cy.contains('Create visualization').click({ force: true }); - - // Click on "Region Map" icon - cy.contains('Region Map').click({ force: true }); - - // Select index source - opensearch_dashboards_sample_data_flights - cy.contains('opensearch_dashboards_sample_data_flights').click({ - force: true, - }); + // Load region map visualization with sample data opensearch_dashboards_sample_data_flights + cy.visit( + `${BASE_PATH}/app/visualize#/create?type=region_map&indexPattern=d3d7af60-4c81-11e8-b3d7-01146121b73d`, + { + retryOnStatusCodeFailure: true, + timeout: 60000, + } + ); }); it('checks import custom map tab is present', () => { From b3e2b740d349906c425cf993bb675ad75f37b0af Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 10:56:58 -0700 Subject: [PATCH 43/46] Buffer `waitForLoader` before checking for icon (#857) (#859) Some async calls occur after navigating and sometimes navigating too quickly prevents the calls from having correct data and therefore the homeIcon is in a bad state. This adds the ability to buffer before checking for the homeIcon. Defaulted to 0 ms, but for `yarn cypress:run-with-security` configures WAIT_FOR_LOADER_BUFFER_MS to be 500 ms Issue resolved: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5028 Signed-off-by: Kawika Avilla (cherry picked from commit e7c7e59abefa8d17b9d5ee22b9d7c933fdf7b1ad) Co-authored-by: Kawika Avilla Signed-off-by: leanne.laceybyrne@eliatra.com --- cypress.json | 3 ++- cypress/utils/dashboards/commands.js | 2 +- package.json | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cypress.json b/cypress.json index f048c8e2f..f79ebf340 100644 --- a/cypress.json +++ b/cypress.json @@ -19,6 +19,7 @@ "MANAGED_SERVICE_ENDPOINT": false, "VISBUILDER_ENABLED": true, "DATASOURCE_MANAGEMENT_ENABLED": false, - "ML_COMMONS_DASHBOARDS_ENABLED": true + "ML_COMMONS_DASHBOARDS_ENABLED": true, + "WAIT_FOR_LOADER_BUFFER_MS": 0 } } diff --git a/cypress/utils/dashboards/commands.js b/cypress/utils/dashboards/commands.js index 1d2188c4f..dd1f5d024 100644 --- a/cypress/utils/dashboards/commands.js +++ b/cypress/utils/dashboards/commands.js @@ -15,7 +15,7 @@ Cypress.Commands.add('waitForLoader', () => { displayName: 'wait', message: 'page load', }); - + cy.wait(Cypress.env('WAIT_FOR_LOADER_BUFFER_MS')); cy.getElementByTestId('homeIcon', opts); // Update to `homeLoader` once useExpandedHeader is enabled }); diff --git a/package.json b/package.json index 4dae0d436..14d84624b 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "test": "echo \"Error: no test specified\" && exit 1", "cypress:open": "cypress open", "cypress:run-without-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=false", - "cypress:run-with-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200", - "cypress:run-with-security-and-aggregation-view": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200,AGGREGATION_VIEW=true", + "cypress:run-with-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200,WAIT_FOR_LOADER_BUFFER_MS=500", + "cypress:run-with-security-and-aggregation-view": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200,AGGREGATION_VIEW=true,WAIT_FOR_LOADER_BUFFER_MS=500", "cypress:run-plugin-tests-without-security": "yarn cypress:run-without-security --spec 'cypress/integration/plugins/*/*.js'", "cypress:run-plugin-tests-with-security": "yarn cypress:run-with-security --spec 'cypress/integration/plugins/*/*.js'", "cypress:release-chrome": "yarn cypress:run-with-security --browser chrome --spec 'cypress/integration/core-opensearch-dashboards/opensearch-dashboards/*.js,cypress/integration/plugins/*/*'", From df07263b3291dc674dd7cc4d52357bddba81ed33 Mon Sep 17 00:00:00 2001 From: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Date: Fri, 29 Sep 2023 16:51:28 +0100 Subject: [PATCH 44/46] Overwrite removed and moved to after in unit test Signed-off-by: leanneeliatra Signed-off-by: leanne.laceybyrne@eliatra.com --- .../tenancy_change_on_shortlink.js | 14 ++++++++++++++ cypress/utils/commands.js | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js index 958746740..eddc3cb94 100644 --- a/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js +++ b/cypress/integration/plugins/security-dashboards-plugin/tenancy_change_on_shortlink.js @@ -130,5 +130,19 @@ if (Cypress.env('SECURITY_ENABLED')) { ); }); }); + after(() => { + cy.deleteIndexPattern('index-pattern1', { + headers: { + securitytenant: ['global'], + 'osd-xsrf': true, + }, + }); + cy.deleteIndexPattern('index-pattern2', { + headers: { + securitytenant: ['private'], + 'osd-xsrf': true, + }, + }); + }); }); } diff --git a/cypress/utils/commands.js b/cypress/utils/commands.js index 90127014f..2c9076263 100644 --- a/cypress/utils/commands.js +++ b/cypress/utils/commands.js @@ -356,7 +356,7 @@ Cypress.Commands.add('deleteSavedObjectByType', (type, search) => { Cypress.Commands.add('createIndexPattern', (id, attributes, header = {}) => { const url = `${ Cypress.config().baseUrl - }/api/saved_objects/index-pattern/${id}?overwrite=true`; // When running tests locally if ran multiple times the tests fail. The fix is to set Overwrite to true. + }/api/saved_objects/index-pattern/${id}`; cy.request({ method: 'POST', From 53b38e3183f97c6c68b27c255c9977fc569d3c55 Mon Sep 17 00:00:00 2001 From: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:16:14 +0100 Subject: [PATCH 45/46] Update dashboard.spec.js Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> --- .../opensearch-dashboards/apps/vis_builder/dashboard.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/dashboard.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/dashboard.spec.js index ae961ea56..234d4138a 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/dashboard.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/apps/vis_builder/dashboard.spec.js @@ -138,7 +138,6 @@ if (Cypress.env('VISBUILDER_ENABLED')) { // Save and return cy.getElementByTestId('visBuilderSaveAndReturnButton').click(); - cy.getElementByTestId('visBuilderLoader').should( 'contain.text', newLabel From 2731dbf40f86f028dcf959e374a32a0431e1ccc3 Mon Sep 17 00:00:00 2001 From: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:23:51 +0100 Subject: [PATCH 46/46] Update dashboard_sample_data_spec.js remove unneeded change Signed-off-by: leanneeliatra <131779422+leanneeliatra@users.noreply.github.com> --- cypress/integration/common/dashboard_sample_data_spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/cypress/integration/common/dashboard_sample_data_spec.js b/cypress/integration/common/dashboard_sample_data_spec.js index a5890094f..7d7afa310 100644 --- a/cypress/integration/common/dashboard_sample_data_spec.js +++ b/cypress/integration/common/dashboard_sample_data_spec.js @@ -247,7 +247,6 @@ export function dashboardSanityTests() { describe('checking discover', () => { before(() => { - cy.setAdvancedSetting({ 'discover:v2': false }); // Go to the Discover page miscUtils.visitPage('app/data-explorer/discover#/'); });