From bd7a18f65f5467a079218e73b02d836fa6d8cbdc Mon Sep 17 00:00:00 2001 From: Hailong Cui Date: Mon, 18 Nov 2024 11:09:35 +0800 Subject: [PATCH 1/5] [workspace]Add cypress for breadcrumbs (#1642) * imporve link verification to support version Signed-off-by: Hailong Cui * imporve link verification to support version Signed-off-by: Hailong Cui * add breadcrumbs test Signed-off-by: Hailong Cui * add breadcrumbs test Signed-off-by: Hailong Cui * fix link with version Signed-off-by: Hailong Cui * fix test failure Signed-off-by: Hailong Cui * fix lint Signed-off-by: Hailong Cui * fix test Signed-off-by: Hailong Cui * wait for page load complete Signed-off-by: Hailong Cui --------- Signed-off-by: Hailong Cui --- .../mds_workspace_analytics_overviews.spec.js | 44 ++--- .../mds_workspace_breadcrumbs.spec.js | 157 ++++++++++++++++++ .../mds_workspace_essential_overviews.spec.js | 33 ++-- .../mds_workspace_initial.spec.js | 14 +- 4 files changed, 209 insertions(+), 39 deletions(-) create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_breadcrumbs.spec.js diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_analytics_overviews.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_analytics_overviews.spec.js index 2f8241a74..67dd454d1 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_analytics_overviews.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_analytics_overviews.spec.js @@ -118,34 +118,38 @@ if (Cypress.env('WORKSPACE_ENABLED')) { cy.get('.euiLink') .contains('Quickstart guide') .should('be.visible') - .and( - 'have.attr', - 'href', - 'https://opensearch.org/docs/latest/dashboards/quickstart/' - ); + .and('have.attr', 'href') + .and((href) => { + expect(href).to.match( + /https:\/\/opensearch.org\/docs\/(latest|(\d.)+)\/dashboards\/quickstart\/$/ + ); + }); cy.get('.euiLink') .contains('Building data visualizations') .should('be.visible') - .and( - 'have.attr', - 'href', - 'https://opensearch.org/docs/latest/dashboards/visualize/viz-index/' - ); + .and('have.attr', 'href') + .and((href) => { + expect(href).to.match( + /https:\/\/opensearch.org\/docs\/(latest|(\d.)+)\/dashboards\/visualize\/viz-index\/$/ + ); + }); cy.get('.euiLink') .contains('Creating dashboards') .should('be.visible') - .and( - 'have.attr', - 'href', - 'https://opensearch.org/docs/latest/dashboards/dashboard/index/' - ); + .and('have.attr', 'href') + .and((href) => { + expect(href).to.match( + /https:\/\/opensearch.org\/docs\/(latest|(\d.)+)\/dashboards\/dashboard\/index\/$/ + ); + }); cy.contains('Learn more in Documentation') .should('be.visible') - .and( - 'have.attr', - 'href', - 'https://opensearch.org/docs/latest/dashboards/index/' - ); + .and('have.attr', 'href') + .and((href) => { + expect(href).to.match( + /https:\/\/opensearch.org\/docs\/(latest|(\d.)+)\/dashboards\/index\/$/ + ); + }); }); }); } diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_breadcrumbs.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_breadcrumbs.spec.js new file mode 100644 index 000000000..8f1ca5002 --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_breadcrumbs.spec.js @@ -0,0 +1,157 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MiscUtils } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; + +const miscUtils = new MiscUtils(cy); +const workspaceName = `test_workspace_analytics_${Math.random() + .toString(36) + .substring(7)}`; +let workspaceDescription = 'This is a analytics workspace description.'; +let workspaceId; +let workspaceFeatures = ['use-case-all']; + +if (Cypress.env('WORKSPACE_ENABLED')) { + const createWorkspace = () => { + cy.createWorkspace({ + name: workspaceName, + description: workspaceDescription, + features: workspaceFeatures, + settings: { + permissions: { + library_write: { users: ['%me%'] }, + write: { users: ['%me%'] }, + }, + }, + }).then((value) => { + workspaceId = value; + }); + }; + + describe('Breadcrumbs in workspace', () => { + before(() => { + cy.deleteWorkspaceByName(workspaceName); + createWorkspace(); + }); + + after(() => { + if (workspaceId) { + cy.deleteWorkspaceById(workspaceId); + } + }); + + it('should show workspace name in breadcrumbs', () => { + miscUtils.visitPage(`w/${workspaceId}/app/objects`); + + // get div with class newTopNavHeader + cy.get('.newTopNavHeader').within(() => { + cy.getElementByTestId('breadcrumb first') + .should('exist') + .within(() => { + // Check for the icon + cy.getElementByTestId(`${workspaceId}-icon`).should('exist'); + // Check for the workspace name + cy.contains(workspaceName).should('exist').click(); + // click on breadcrumbs goes to overview page + cy.url().should('include', `w/${workspaceId}/app/all_overview`); + }); + }); + }); + + it('should show breadcrumbs in recent popover', () => { + miscUtils.visitPage(`w/${workspaceId}/app/workspace_detail`); + // wait for page load + cy.contains('h1', 'Workspace details'); + // waiting for page load completely + cy.getElementByTestId('recentItemsSectionButton').should( + 'not.have.class', + 'headerRecentItemsButton--loadingIndicator', + { + timeout: 10000, + } + ); + cy.wait(1000); + cy.getElementByTestId('recentItemsSectionButton').click({ force: true }); + + cy.get('.euiPopover__panel').within(() => { + cy.getElementByTestId('breadcrumbs').within(() => { + // Check for the icon + cy.getElementByTestId(`${workspaceId}-icon`).should('exist'); + // Check for the workspace name + cy.contains(workspaceName).should('exist'); + // page title exists in breadcrumbs + cy.contains('Workspace details'); + }); + }); + }); + }); + + describe('Breadcrumbs out of workspace', () => { + it('breadcrumbs display correctly in settings and setup', () => { + miscUtils.visitPage('app/workspace_list'); + cy.contains('h1', 'Workspaces'); + + cy.get('.newTopNavHeader').within(() => { + cy.getElementByTestId('breadcrumb first') + .should('exist') + .within(() => { + // Check for the use case name + cy.contains('Settings and setup').click(); + cy.url().should('include', 'app/settings_landing'); + }); + }); + + // check breadcrumbs in recent popover + cy.getElementByTestId('recentItemsSectionButton').should( + 'not.have.class', + 'headerRecentItemsButton--loadingIndicator', + { + timeout: 10000, + } + ); + cy.wait(1000); + cy.getElementByTestId('recentItemsSectionButton').click({ force: true }); + + cy.get('.euiPopover__panel').within(() => { + cy.getElementByTestId('breadcrumbs').within(() => { + cy.contains('Settings and setup'); + cy.contains('Settings and setup overview'); + }); + }); + }); + + it('breadcrumbs display correctly in data administration', () => { + miscUtils.visitPage('app/data_administration_landing'); + cy.contains('h1', 'Data administration overview'); + + cy.get('.newTopNavHeader').within(() => { + cy.getElementByTestId('breadcrumb first') + .should('exist') + .within(() => { + // Check for the use case name + cy.contains('Data administration'); + }); + }); + + // check breadcrumbs in recent popover + cy.getElementByTestId('recentItemsSectionButton').should( + 'not.have.class', + 'headerRecentItemsButton--loadingIndicator', + { + timeout: 10000, + } + ); + cy.wait(1000); + cy.getElementByTestId('recentItemsSectionButton').click({ force: true }); + + cy.get('.euiPopover__panel').within(() => { + cy.getElementByTestId('breadcrumbs').within(() => { + cy.contains('Data administration'); + cy.contains('Data administration overview'); + }); + }); + }); + }); +} diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_essential_overviews.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_essential_overviews.spec.js index 8d24abaf6..45423f564 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_essential_overviews.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_essential_overviews.spec.js @@ -127,27 +127,30 @@ if (Cypress.env('WORKSPACE_ENABLED')) { // get a link with text as Quickstart guide cy.get('a') .contains('Quickstart guide') - .should( - 'have.attr', - 'href', - 'https://opensearch.org/docs/latest/dashboards/quickstart/' - ); + .should('have.attr', 'href') + .and((href) => { + expect(href).to.match( + /^https:\/\/opensearch.org\/docs\/(latest|(\d.)+)\/dashboards\/quickstart\/$/ + ); + }); cy.get('a') .contains('Building data visualizations') - .should( - 'have.attr', - 'href', - 'https://opensearch.org/docs/latest/dashboards/visualize/viz-index/' - ); + .should('have.attr', 'href') + .and((href) => { + expect(href).to.match( + /^https:\/\/opensearch.org\/docs\/(latest|(\d.)+)\/dashboards\/visualize\/viz-index\/$/ + ); + }); cy.get('a') .contains('Creating dashboards') - .should( - 'have.attr', - 'href', - 'https://opensearch.org/docs/latest/dashboards/dashboard/index/' - ); + .should('have.attr', 'href') + .and((href) => { + expect(href).to.match( + /^https:\/\/opensearch.org\/docs\/(latest|(\d.)+)\/dashboards\/dashboard\/index\/$/ + ); + }); }); }); } diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_initial.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_initial.spec.js index 48d7a95ba..be728a0ff 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_initial.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_initial.spec.js @@ -70,10 +70,16 @@ if (Cypress.env('WORKSPACE_ENABLED')) { ).should('not.exist'); // Contain correct link - cy.contains('a', 'Learn more from documentation').should('exist'); - cy.get( - 'a[href="https://opensearch.org/docs/latest/opensearch/index/"]' - ).should('have.attr', 'target', '_blank'); + cy.contains('a', 'Learn more from documentation') + .should('exist') + .should('have.attr', 'target', '_blank') + .and('have.attr', 'href') + .and((href) => { + expect(href).to.match( + /https:\/\/opensearch.org\/docs\/(latest|(\d.)+)\/opensearch\/index\/$/ + ); + }); + cy.contains( 'a', 'Explore live demo environment at playground.opensearch.org' From 976f2ba78b5259fe04a4e12a5f0826b26bda6d41 Mon Sep 17 00:00:00 2001 From: Qxisylolo Date: Mon, 18 Nov 2024 14:17:21 +0800 Subject: [PATCH 2/5] Add tests for workspace deletion (#1631) * add tests for deletion Signed-off-by: Qxisylolo * resolve comments Signed-off-by: Qxisylolo * new update Signed-off-by: Qxisylolo * new update Signed-off-by: Qxisylolo * revert lines Signed-off-by: Qxisylolo --------- Signed-off-by: Qxisylolo --- .../mds_workspace_delete.spec.js | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_delete.spec.js diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_delete.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_delete.spec.js new file mode 100644 index 000000000..0358f9b20 --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_delete.spec.js @@ -0,0 +1,182 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MiscUtils } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; +const miscUtils = new MiscUtils(cy); +const workspace1Name = 'test_workspace_320sdfouAz'; +let workspace1Description = 'This is a workspace1 description.'; +const workspace2Name = 'test_workspace_321sdfouAz'; +let workspace2Description = 'This is a workspace2 description.'; + +let workspace1Id; +let workspace2Id; + +if (Cypress.env('WORKSPACE_ENABLED')) { + describe('Delete Workspace(s) in 2 ways in workspace list page', () => { + before(() => { + cy.deleteWorkspaceByName(workspace1Name); + cy.deleteWorkspaceByName(workspace2Name); + }); + beforeEach(() => { + cy.createWorkspace({ + name: workspace1Name, + description: workspace1Description, + features: ['use-case-observability'], + settings: { + permissions: { + library_write: { users: ['%me%'] }, + write: { users: ['%me%'] }, + }, + }, + }).then((value) => { + workspace1Id = value; + cy.intercept('DELETE', `/api/workspaces/${workspace1Id}`).as( + 'deleteWorkspace1Request' + ); + }); + + cy.createWorkspace({ + name: workspace2Name, + description: workspace2Description, + features: ['use-case-search'], + settings: { + permissions: { + library_write: { users: ['%me%'] }, + write: { users: ['%me%'] }, + }, + }, + }).then((value) => { + workspace2Id = value; + cy.intercept('DELETE', `/api/workspaces/${workspace2Id}`).as( + 'deleteWorkspace2Request' + ); + }); + // Visit workspace list page + miscUtils.visitPage(`/app/workspace_list`); + }); + + afterEach(() => { + cy.deleteWorkspaceByName(workspace1Name); + cy.deleteWorkspaceByName(workspace2Name); + }); + + describe('delete a workspace successfully using action buttons', () => { + it('should successfully load delete button and show delete modal when clicking action button', () => { + cy.contains(workspace1Name).should('be.visible'); + cy.getElementByTestId(`checkboxSelectRow-${workspace1Id}`) + .parents('tr') + .within(() => { + cy.getElementByTestId('euiCollapsedItemActionsButton').click(); + }); + cy.getElementByTestId('workspace-list-delete-icon').should( + 'be.visible' + ); + cy.getElementByTestId('workspace-list-delete-icon').click(); + cy.contains('Delete workspace').should('be.visible'); + cy.contains( + 'The following workspace will be permanently deleted. This action cannot be undone' + ).should('be.visible'); + cy.contains(workspace1Name).should('be.visible'); + cy.getElementByTestId('delete-workspace-modal-input').type('delete'); + cy.getElementByTestId('delete-workspace-modal-confirm').click(); + cy.wait('@deleteWorkspace1Request').then((interception) => { + expect(interception.response.statusCode).to.equal(200); + }); + cy.location('pathname').should('include', 'app/workspace_list'); + cy.contains('Delete workspace successfully').should('be.visible'); + cy.contains(workspace1Name).should('not.exist'); + }); + }); + + describe('delete workspace(s) successfully using multi-deletion button', () => { + it('should successfully show multi-deletion button and perform deletion when choosing one workspace', () => { + cy.contains(workspace1Name).should('be.visible'); + cy.getElementByTestId(`checkboxSelectRow-${workspace1Id}`).click(); + cy.getElementByTestId('multi-deletion-button').should('be.visible'); + cy.getElementByTestId('multi-deletion-button').click(); + cy.contains('Delete workspace').should('be.visible'); + cy.contains( + 'The following workspace will be permanently deleted. This action cannot be undone' + ).should('be.visible'); + cy.contains(workspace1Name).should('be.visible'); + cy.getElementByTestId('delete-workspace-modal-input').type('delete'); + cy.getElementByTestId('delete-workspace-modal-confirm').click(); + cy.wait('@deleteWorkspace1Request').then((interception) => { + expect(interception.response.statusCode).to.equal(200); + }); + cy.location('pathname').should('include', 'app/workspace_list'); + cy.contains('Delete workspace successfully').should('be.visible'); + cy.contains(workspace1Name).should('not.exist'); + }); + + it('should successfully delete all', () => { + cy.contains(workspace1Name).should('be.visible'); + cy.contains(workspace2Name).should('be.visible'); + cy.getElementByTestId('checkboxSelectAll').click(); + cy.getElementByTestId('multi-deletion-button').should('be.visible'); + cy.getElementByTestId('multi-deletion-button').click(); + cy.contains('Delete workspace').should('be.visible'); + cy.contains( + 'The following workspace will be permanently deleted. This action cannot be undone' + ).should('be.visible'); + cy.contains(workspace1Name).should('be.visible'); + cy.contains(workspace2Name).should('be.visible'); + cy.getElementByTestId('delete-workspace-modal-input').type('delete'); + cy.getElementByTestId('delete-workspace-modal-confirm').click(); + cy.wait('@deleteWorkspace1Request').then((interception) => { + expect(interception.response.statusCode).to.equal(200); + }); + cy.location('pathname').should('include', 'app/workspace_list'); + cy.contains('Delete workspace successfully').should('be.visible'); + cy.contains(workspace1Name).should('not.exist'); + cy.contains(workspace2Name).should('not.exist'); + }); + }); + }); + + describe('Workspace deletion in workspace detail page', () => { + before(() => { + cy.deleteWorkspaceByName(workspace1Name); + cy.createWorkspace({ + name: workspace1Name, + description: workspace1Description, + features: ['use-case-observability'], + settings: { + permissions: { + library_write: { users: ['%me%'] }, + write: { users: ['%me%'] }, + }, + }, + }).then((value) => { + workspace1Id = value; + }); + }); + + beforeEach(() => { + cy.intercept( + 'DELETE', + `/w/${workspace1Id}/api/workspaces/${workspace1Id}` + ).as('deleteWorkspace1Request'); + // Visit workspace detail page + miscUtils.visitPage(`w/${workspace1Id}/app/workspace_detail`); + }); + + it('should delete workspace in workspace detail page', () => { + cy.getElementByTestId('workspace-detail-delete-button').click(); + cy.contains('Delete workspace').should('be.visible'); + cy.contains(workspace1Name).should('be.visible'); + cy.getElementByTestId('delete-workspace-modal-input').type( + workspace1Name + ); + cy.getElementByTestId('delete-workspace-modal-confirm').click(); + cy.wait('@deleteWorkspace1Request').then((interception) => { + expect(interception.response.statusCode).to.equal(200); + }); + cy.contains('Delete workspace successfully').should('be.visible'); + cy.location('pathname').should('include', 'app/workspace_list'); + cy.contains(workspace1Name).should('not.exist'); + }); + }); +} From 3047f2553bfb4b7088171da739c973288cdac0aa Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 18 Nov 2024 17:35:07 +0800 Subject: [PATCH 3/5] [Workspace] Add ACL related test cases (#1644) * feat: add acl related test cases Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: remove useless resources Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe * feat: use admin user to clean up all the resources Signed-off-by: SuZhou-Joe --------- Signed-off-by: SuZhou-Joe --- .../mds_workspace_acl.spec.js | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js new file mode 100644 index 000000000..86c03c4e6 --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js @@ -0,0 +1,232 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BASE_PATH } from '../../../../utils/base_constants'; +import { ADMIN_AUTH } from '../../../../utils/commands'; +import workspaceTestUser from '../../../../fixtures/dashboard/opensearch_dashboards/workspace/workspaceTestUser.json'; +import workspaceTestRole from '../../../../fixtures/dashboard/opensearch_dashboards/workspace/workspaceTestRole.json'; +import { WORKSPACE_API_PREFIX } from '../../../../utils/dashboards/workspace-plugin/constants'; + +let noPermissionWorkspaceName = 'acl_no_permission_workspace'; +let readOnlyWorkspaceName = 'acl_readonly_workspace'; +let libraryWriteWorkspaceName = 'acl_library_write_workspace'; +let ownerWorkspaceName = 'acl_owner_workspace'; + +let noPermissionWorkspaceId = ''; +let readOnlyWorkspaceId = ''; +let libraryWriteWorkspaceId = ''; +let ownerWorkspaceId = ''; +let datasourceId = ''; + +const getPolicy = (permission, userName) => ({ + [permission]: { + users: [userName], + }, +}); + +const NON_DASHBOARDS_ADMIN_USERNAME = 'workspace-acl-test'; +const WORKSPACE_TEST_ROLE_NAME = 'workspace-acl-test-role'; + +const ACLPolicyMap = { + [noPermissionWorkspaceName]: {}, + [readOnlyWorkspaceName]: { + ...getPolicy('read', NON_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_read', NON_DASHBOARDS_ADMIN_USERNAME), + }, + [libraryWriteWorkspaceName]: { + ...getPolicy('read', NON_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_write', NON_DASHBOARDS_ADMIN_USERNAME), + }, + [ownerWorkspaceName]: { + ...getPolicy('write', NON_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_write', NON_DASHBOARDS_ADMIN_USERNAME), + }, +}; + +const setupWorkspace = (workspaceName, datasourceId) => { + return cy + .createWorkspace({ + name: workspaceName, + settings: { + ...(datasourceId ? { dataSources: [datasourceId] } : {}), + permissions: ACLPolicyMap[workspaceName], + }, + }) + .then((value) => { + // load sample data + cy.loadSampleDataForWorkspace('ecommerce', value, datasourceId); + cy.wrap(value); + }); +}; + +const setupAllTheWorkspaces = () => { + setupWorkspace(noPermissionWorkspaceName, datasourceId).then( + (value) => (noPermissionWorkspaceId = value) + ); + setupWorkspace(readOnlyWorkspaceName, datasourceId).then( + (value) => (readOnlyWorkspaceId = value) + ); + setupWorkspace(libraryWriteWorkspaceName, datasourceId).then( + (value) => (libraryWriteWorkspaceId = value) + ); + setupWorkspace(ownerWorkspaceName, datasourceId).then( + (value) => (ownerWorkspaceId = value) + ); +}; + +if ( + Cypress.env('WORKSPACE_ENABLED') && + Cypress.env('SAVED_OBJECTS_PERMISSION_ENABLED') && + Cypress.env('SECURITY_ENABLED') +) { + describe('Workspace ACL', () => { + const originalUser = ADMIN_AUTH.username; + const originalPassword = ADMIN_AUTH.password; + before(() => { + cy.deleteWorkspaceByName(noPermissionWorkspaceName); + cy.deleteWorkspaceByName(readOnlyWorkspaceName); + cy.deleteWorkspaceByName(libraryWriteWorkspaceName); + cy.deleteWorkspaceByName(ownerWorkspaceName); + + cy.createInternalUser(NON_DASHBOARDS_ADMIN_USERNAME, workspaceTestUser); + cy.createRole(WORKSPACE_TEST_ROLE_NAME, workspaceTestRole); + cy.createRoleMapping(WORKSPACE_TEST_ROLE_NAME, { + users: [NON_DASHBOARDS_ADMIN_USERNAME], + }); + + if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { + cy.createDataSourceNoAuth().then((result) => { + datasourceId = result[0]; + expect(datasourceId).to.be.a('string').that.is.not.empty; + setupAllTheWorkspaces(datasourceId); + }); + } else { + setupAllTheWorkspaces(datasourceId); + } + }); + + after(() => { + ADMIN_AUTH.newUser = originalUser; + ADMIN_AUTH.newPassword = originalPassword; + cy.removeSampleDataForWorkspace( + 'ecommerce', + ownerWorkspaceId, + datasourceId + ); + cy.deleteWorkspaceByName(noPermissionWorkspaceName); + cy.deleteWorkspaceByName(readOnlyWorkspaceName); + cy.deleteWorkspaceByName(libraryWriteWorkspaceName); + cy.deleteWorkspaceByName(ownerWorkspaceName); + if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { + cy.deleteDataSource(datasourceId); + } + readOnlyWorkspaceId = ''; + libraryWriteWorkspaceId = ''; + + cy.deleteRoleMapping(WORKSPACE_TEST_ROLE_NAME); + cy.deleteInternalUser(NON_DASHBOARDS_ADMIN_USERNAME); + cy.deleteRole(WORKSPACE_TEST_ROLE_NAME); + }); + + describe('Normal user', () => { + beforeEach(() => { + ADMIN_AUTH.newUser = NON_DASHBOARDS_ADMIN_USERNAME; + ADMIN_AUTH.newPassword = workspaceTestUser.password; + }); + + it('Normal user should not be able to create workspace', () => { + cy.request({ + method: 'POST', + url: `${BASE_PATH}${WORKSPACE_API_PREFIX}`, + headers: { + 'osd-xsrf': true, + }, + body: { + attributes: { + name: 'test_workspace', + features: ['use-case-observability'], + description: 'test_description', + }, + }, + failOnStatusCode: false, + }).then((resp) => + cy + .wrap(resp.body.error) + .should('equal', 'Invalid permission, please contact OSD admin') + ); + }); + + it('Normal users should only see the workspaces they have permission with', () => { + cy.visit(`${BASE_PATH}/app/home`); + cy.contains(readOnlyWorkspaceName); + cy.contains(noPermissionWorkspaceName).should('not.exist'); + }); + + it('Readonly users should not be allowed to update dashboards/visualizations within the workspace', () => { + cy.visit(`${BASE_PATH}/w/${readOnlyWorkspaceId}/app/visualize`); + cy.contains(/\[eCommerce\] Markdown/).click(); + cy.getElementByTestId('visualizeSaveButton').click(); + cy.getElementByTestId('confirmSaveSavedObjectButton').click(); + cy.contains('Forbidden'); + }); + + it('Normal users should not be allowed to visit workspace he/she has no permission', () => { + cy.visit(`${BASE_PATH}/w/${noPermissionWorkspaceId}/app/objects`); + cy.contains('Invalid saved objects permission'); + }); + + it('Normal users should only see the workspaces he has library_write permission in the target workspaces list of duplicate modal', () => { + cy.visit(`${BASE_PATH}/w/${readOnlyWorkspaceId}/app/objects`); + cy.getElementByTestId('savedObjectsTableRowTitle').should('exist'); + cy.getElementByTestId('duplicateObjects') + .click() + .getElementByTestId('savedObjectsDuplicateModal') + .find('[data-test-subj="comboBoxInput"]') + .click(); + + cy.contains(libraryWriteWorkspaceName); + cy.contains(ownerWorkspaceName); + cy.contains(noPermissionWorkspaceName).should('not.exist'); + }); + + it('Users should not be able to update default index pattern / default data source if he/she is not the workspace owner', () => { + cy.visit(`${BASE_PATH}/w/${libraryWriteWorkspaceId}/app/indexPatterns`); + cy.contains('opensearch_dashboards_sample_data_ecommerce').click(); + cy.getElementByTestId('setDefaultIndexPatternButton').click(); + cy.contains('Unable to update UI setting'); + }); + + it('Normal users should not be able to find objects from other workspaces when inside a workspace', () => { + cy.visit(`${BASE_PATH}/w/${readOnlyWorkspaceId}/app/objects`); + cy.getElementByTestId('savedObjectsTableRowTitle').should('exist'); + cy.getElementByTestId( + 'savedObjectsTableColumn-workspace_column' + ).should('not.exist'); + cy.contains('opensearch_dashboards_sample_data_ecommerce'); + // Electron old version may not support search event, so we manually trigger a search event + cy.getElementByTestId('savedObjectSearchBar') + .type('opensearch_dashboards_sample_data_ecommerce{enter}') + .trigger('search'); + cy.getElementByTestId('savedObjectsTableRowTitle').should( + 'have.length', + 1 + ); + }); + + if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { + it('Normal users should not be able to associate / dissociate data sources from workspace.', () => { + cy.visit(`${BASE_PATH}/w/${ownerWorkspaceId}/app/dataSources`); + cy.contains('Data sources'); + cy.getElementByTestId('workspaceAssociateDataSourceButton').should( + 'not.exist' + ); + cy.getElementByTestId( + 'dataSourcesManagement-dataSourceTable-dissociateButton' + ).should('not.exist'); + }); + } + }); + }); +} From 110d2704128f4cb8abe058b5aa102f837cb696e1 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Tue, 19 Nov 2024 08:24:36 +0800 Subject: [PATCH 4/5] feat: add yubonluo as maintain (#1649) Signed-off-by: SuZhou-Joe --- .github/CODEOWNERS | 2 +- MAINTAINERS.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0c3995a23..63584694c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @tianleh @kavilla @ohltyler @CCongWang @ashwin-pc @peterzhuamazon @ananzh @prudhvigodithi @xluo-aws @Hailong-am @SuZhou-Joe @ruanyl @wanglam @raintygao @zhongnansu @yujin-emma +* @tianleh @kavilla @ohltyler @CCongWang @ashwin-pc @peterzhuamazon @ananzh @prudhvigodithi @xluo-aws @Hailong-am @SuZhou-Joe @ruanyl @wanglam @raintygao @zhongnansu @yujin-emma @yubonluo diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 53ba438fd..530bfef69 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -22,6 +22,7 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Tianyu Gao | [raintygao](https://github.com/raintygao) | Amazon | | Zhongnan Su | [zhongnansu](https://github.com/zhongnansu) | Amazon | | Emma Jin | [yujin-emma](https://github.com/yujin-emma) | Amazon | +| Yubo Luo | [yubonluo](https://github.com/yubonluo) | Amazon | ## Emeritus From 700389564efec83787cc59d1ddc73ef6e69128b5 Mon Sep 17 00:00:00 2001 From: Tianyu Gao Date: Tue, 19 Nov 2024 16:15:06 +0800 Subject: [PATCH 5/5] [Workspace] add tests for data source association and dissociation (#1646) * add tests for data source association and dissociation Signed-off-by: tygao * remove security verification Signed-off-by: tygao --------- Signed-off-by: tygao --- .../mds_workspace_association.spec.js | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_association.spec.js diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_association.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_association.spec.js new file mode 100644 index 000000000..453a5ab3d --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_association.spec.js @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MiscUtils } from '@opensearch-dashboards-test/opensearch-dashboards-test-library'; +const miscUtils = new MiscUtils(cy); +const workspaceName = 'test_workspace_collaborators'; +let workspaceId; +let dataSourceTitle1 = 'no_auth_data_source_title_1'; +let dataSourceTitle2 = 'no_auth_data_source_title_2'; +let dataSourceId1; +let dataSourceId2; +if ( + Cypress.env('WORKSPACE_ENABLED') && + Cypress.env('DATASOURCE_MANAGEMENT_ENABLED') +) { + describe('Workspace association data source', () => { + before(() => { + cy.createDataSourceNoAuth({ title: dataSourceTitle1 }).then((result) => { + dataSourceId1 = result[0]; + }); + cy.createDataSourceNoAuth({ title: dataSourceTitle2 }).then((result) => { + dataSourceId2 = result[0]; + }); + }); + beforeEach(() => { + cy.deleteWorkspaceByName(workspaceName); + //Create a workspace before each test + cy.createWorkspace({ + name: workspaceName, + features: ['use-case-observability'], + settings: { + permissions: { + library_write: { users: ['%me%'] }, + write: { users: ['%me%'] }, + }, + }, + }).then((value) => { + workspaceId = value; + }); + }); + + after(() => { + cy.deleteDataSource(dataSourceId1); + cy.deleteDataSource(dataSourceId2); + }); + afterEach(() => { + cy.deleteWorkspaceById(workspaceId); + }); + + it('should associate and dissociate data source successfully', () => { + miscUtils.visitPage(`w/${workspaceId}/app/dataSources`); + + cy.getElementByTestId('workspaceAssociateDataSourceButton').click(); + cy.contains('OpenSearch data sources').click(); + cy.contains(dataSourceTitle1, { + withinSubject: parent.document.body, + }).click({ force: true }); + cy.contains(dataSourceTitle2, { + withinSubject: parent.document.body, + }).click({ force: true }); + cy.getElementByTestId( + 'workspace-detail-dataSources-associateModal-save-button' + ).click(); + + // The table is updated after successful association + cy.contains('table', dataSourceTitle1); + cy.contains('table', dataSourceTitle2); + + // The table is updated after successful dissociation + cy.getElementByTestId('checkboxSelectAll').check(); + cy.getElementByTestId('dissociateSelectedDataSources').click(); + cy.getElementByTestId('confirmModalConfirmButton').click(); + cy.contains('table', dataSourceTitle1).should('not.exist'); + cy.contains('table', dataSourceTitle2).should('not.exist'); + }); + }); +}