diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/DragAndDrop.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/DragAndDrop.ts deleted file mode 100644 index 81e8ed8cb01c..000000000000 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/DragAndDrop.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024 Collate. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const dataTransfer = new DataTransfer(); - -export const dragAndDropElement = ( - dragElement: string, - dropTarget: string, - isHeader?: boolean -) => { - cy.get(`[data-row-key="${Cypress.$.escapeSelector(dragElement)}"]`) - .invoke('attr', 'draggable') - .should('contain', 'true'); - - cy.get(`[data-row-key="${Cypress.$.escapeSelector(dragElement)}"]`).trigger( - 'dragstart', - { - dataTransfer, - } - ); - - cy.get( - isHeader - ? dropTarget - : `[data-row-key="${Cypress.$.escapeSelector(dropTarget)}"]` - ) - .trigger('drop', { dataTransfer }) - .trigger('dragend', { force: true }); -}; - -export const openDragDropDropdown = (name: string) => { - cy.get( - `[data-row-key=${name}] > .whitespace-nowrap > [data-testid="expand-icon"] > svg` - ).click(); -}; diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/TeamsDragAndDrop.spec.ts b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/TeamsDragAndDrop.spec.ts deleted file mode 100644 index 96a438d2f78c..000000000000 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/TeamsDragAndDrop.spec.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2024 Collate. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { interceptURL, toastNotification } from '../../common/common'; -import { - dragAndDropElement, - openDragDropDropdown, -} from '../../common/Utils/DragAndDrop'; -import { - addTeam, - commonTeamDetails, - confirmationDragAndDropTeam, - deleteTeamPermanently, -} from '../../common/Utils/Teams'; -import { uuid } from '../../constants/constants'; -import { GlobalSettingOptions } from '../../constants/settings.constant'; - -const teamNameGroup = `team-ct-test-${uuid()}`; -const teamNameBusiness = `team-ct-test-${uuid()}`; -const teamNameDivision = `team-ct-test-${uuid()}`; -const teamNameDepartment = `team-ct-test-${uuid()}`; - -const TEAM_TYPE_BY_NAME = { - [teamNameBusiness]: 'BusinessUnit', - [teamNameDivision]: 'Division', - [teamNameDepartment]: 'Department', - [teamNameGroup]: 'Group', -}; - -const DRAG_AND_DROP_TEAM_DETAILS = [ - { - name: teamNameBusiness, - updatedName: `${teamNameBusiness}-updated`, - teamType: 'BusinessUnit', - description: `This is ${teamNameBusiness} description`, - ...commonTeamDetails, - }, - { - name: teamNameDivision, - updatedName: `${teamNameDivision}-updated`, - teamType: 'Division', - description: `This is ${teamNameDivision} description`, - ...commonTeamDetails, - }, - { - name: teamNameDepartment, - updatedName: `${teamNameDepartment}-updated`, - teamType: 'Department', - description: `This is ${teamNameDepartment} description`, - ...commonTeamDetails, - }, - { - name: teamNameGroup, - updatedName: `${teamNameGroup}-updated`, - teamType: 'Group', - description: `This is ${teamNameGroup} description`, - ...commonTeamDetails, - }, -]; - -describe( - 'Teams drag and drop should work properly', - { tags: 'Settings' }, - () => { - beforeEach(() => { - interceptURL('GET', `/api/v1/users?fields=*`, 'getUserDetails'); - interceptURL('GET', `/api/v1/permissions/team/name/*`, 'permissions'); - cy.login(); - - // Clicking on teams - cy.settingClick(GlobalSettingOptions.TEAMS); - }); - - before(() => { - cy.login(); - // Clicking on teams - cy.settingClick(GlobalSettingOptions.TEAMS); - - DRAG_AND_DROP_TEAM_DETAILS.map((team) => { - addTeam(team); - cy.reload(); - // asserting the added values - cy.get(`[data-row-key="${team.name}"]`) - .scrollIntoView() - .should('be.visible'); - cy.get(`[data-row-key="${team.name}"]`).should( - 'contain', - team.description - ); - }); - }); - - after(() => { - cy.login(); - // Clicking on teams - cy.settingClick(GlobalSettingOptions.TEAMS); - - [ - teamNameBusiness, - teamNameDivision, - teamNameDepartment, - teamNameGroup, - ].map((teamName) => { - deleteTeamPermanently(teamName); - }); - }); - - it('Should fail when drop team type is Group', () => { - [teamNameBusiness, teamNameDepartment, teamNameDivision].map((team) => { - dragAndDropElement(team, teamNameGroup); - toastNotification( - `You cannot move to this team as Team Type ${TEAM_TYPE_BY_NAME[team]} can't be Group children` - ); - - cy.get('.Toastify__toast-body', { timeout: 10000 }).should('not.exist'); - }); - }); - - it('Should fail when droppable team type is Department', () => { - [teamNameBusiness, teamNameDivision].map((team) => { - dragAndDropElement(team, teamNameDepartment); - toastNotification( - `You cannot move to this team as Team Type ${TEAM_TYPE_BY_NAME[team]} can't be Department children` - ); - cy.get('.Toastify__toast-body', { timeout: 10000 }).should('not.exist'); - }); - }); - - it('Should fail when draggable team type is BusinessUnit and droppable team type is Division', () => { - dragAndDropElement(teamNameBusiness, teamNameDivision); - toastNotification( - `You cannot move to this team as Team Type BusinessUnit can't be Division children` - ); - }); - - [teamNameBusiness, teamNameDivision, teamNameDepartment].map( - (droppableTeamName, index) => { - it(`Should drag and drop on ${TEAM_TYPE_BY_NAME[droppableTeamName]} team type`, () => { - // nested team will be shown once anything is moved under it - if (index !== 0) { - openDragDropDropdown( - [teamNameBusiness, teamNameDivision, teamNameDepartment][ - index - 1 - ] - ); - } - - dragAndDropElement(teamNameGroup, droppableTeamName); - - confirmationDragAndDropTeam(teamNameGroup, droppableTeamName); - - // verify the team is moved under the business team - openDragDropDropdown(droppableTeamName); - cy.get( - `.ant-table-row-level-1[data-row-key="${teamNameGroup}"]` - ).should('be.visible'); - }); - } - ); - - it(`Should drag and drop team on table level`, () => { - // open department team dropdown as it is moved under it from last test - openDragDropDropdown(teamNameDepartment); - - dragAndDropElement(teamNameGroup, '.ant-table-thead > tr', true); - confirmationDragAndDropTeam(teamNameGroup, 'Organization'); - - // verify the team is moved under the table level - cy.get(`.ant-table-row-level-0[data-row-key="${teamNameGroup}"]`) - .scrollIntoView() - .should('be.visible'); - }); - } -); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TeamsDragAndDrop.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TeamsDragAndDrop.spec.ts new file mode 100644 index 000000000000..d93e6c7ea849 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/TeamsDragAndDrop.spec.ts @@ -0,0 +1,213 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, test } from '@playwright/test'; +import { GlobalSettingOptions } from '../../constant/settings'; +import { + redirectToHomePage, + toastNotification, + uuid, +} from '../../utils/common'; +import { + confirmationDragAndDropTeam, + dragAndDropElement, + openDragDropDropdown, +} from '../../utils/dragDrop'; +import { settingClick } from '../../utils/sidebar'; +import { addTeamHierarchy, hardDeleteTeam } from '../../utils/team'; + +// use the admin user to login +test.use({ storageState: 'playwright/.auth/admin.json' }); + +test.describe.configure({ mode: 'serial' }); + +const commonTeamDetails = { + username: 'Aaron Johnson', + userId: 'aaron_johnson0', + assetname: 'dim_address', + email: 'team1@gmail.com', + updatedEmail: 'updatedemail@gmail.com', +}; + +const teamNameGroup = `team-ct-test-${uuid()}`; +const teamNameBusiness = `team-ct-test-${uuid()}`; +const teamNameDivision = `team-ct-test-${uuid()}`; +const teamNameDepartment = `team-ct-test-${uuid()}`; + +const TEAM_TYPE_BY_NAME = { + [teamNameBusiness]: 'BusinessUnit', + [teamNameDivision]: 'Division', + [teamNameDepartment]: 'Department', + [teamNameGroup]: 'Group', +}; + +const teams = [teamNameBusiness, teamNameDivision, teamNameDepartment]; + +const DRAG_AND_DROP_TEAM_DETAILS = [ + { + name: teamNameBusiness, + updatedName: `${teamNameBusiness}-updated`, + teamType: 'BusinessUnit', + description: `This is ${teamNameBusiness} description`, + ...commonTeamDetails, + }, + { + name: teamNameDivision, + updatedName: `${teamNameDivision}-updated`, + teamType: 'Division', + description: `This is ${teamNameDivision} description`, + ...commonTeamDetails, + }, + { + name: teamNameDepartment, + updatedName: `${teamNameDepartment}-updated`, + teamType: 'Department', + description: `This is ${teamNameDepartment} description`, + ...commonTeamDetails, + }, + { + name: teamNameGroup, + updatedName: `${teamNameGroup}-updated`, + teamType: 'Group', + description: `This is ${teamNameGroup} description`, + ...commonTeamDetails, + }, +]; + +test.describe('Teams drag and drop should work properly', () => { + test.beforeEach(async ({ page }) => { + await redirectToHomePage(page); + + const getOrganizationResponse = page.waitForResponse( + '/api/v1/teams/name/*' + ); + const permissionResponse = page.waitForResponse( + '/api/v1/permissions/team/name/*' + ); + + await settingClick(page, GlobalSettingOptions.TEAMS); + await permissionResponse; + await getOrganizationResponse; + }); + + test('Add teams in hierarchy', async ({ page }) => { + for (const teamDetails of DRAG_AND_DROP_TEAM_DETAILS) { + const getOrganizationResponse = page.waitForResponse( + '/api/v1/teams/name/*' + ); + await addTeamHierarchy(page, teamDetails); + await getOrganizationResponse; + + expect( + page.locator(`[data-row-key="${teamDetails.name}"]`) + ).toContainText(teamDetails.description); + } + }); + + test('Should fail when drop team type is Group', async ({ page }) => { + for (const team of teams) { + await dragAndDropElement(page, team, teamNameGroup); + await toastNotification( + page, + `You cannot move to this team as Team Type ${TEAM_TYPE_BY_NAME[team]} can't be Group children` + ); + } + }); + + test('Should fail when droppable team type is Department', async ({ + page, + }) => { + const teams = [teamNameBusiness, teamNameDivision]; + + for (const team of teams) { + await dragAndDropElement(page, team, teamNameDepartment); + await toastNotification( + page, + `You cannot move to this team as Team Type ${TEAM_TYPE_BY_NAME[team]} can't be Department children` + ); + } + }); + + test('Should fail when draggable team type is BusinessUnit and droppable team type is Division', async ({ + page, + }) => { + await dragAndDropElement(page, teamNameBusiness, teamNameDivision); + await toastNotification( + page, + "You cannot move to this team as Team Type BusinessUnit can't be Division children" + ); + }); + + for (const [index, droppableTeamName] of teams.entries()) { + test(`Should drag and drop on ${TEAM_TYPE_BY_NAME[droppableTeamName]} team type`, async ({ + page, + }) => { + // Nested team will be shown once anything is moved under it + if (index !== 0) { + await openDragDropDropdown(page, teams[index - 1]); + } + + await dragAndDropElement(page, teamNameGroup, droppableTeamName); + await confirmationDragAndDropTeam(page, teamNameGroup, droppableTeamName); + + // Verify the team is moved under the business team + await openDragDropDropdown(page, droppableTeamName); + const movedTeam = page.locator( + `.ant-table-row-level-1[data-row-key="${teamNameGroup}"]` + ); + + await expect(movedTeam).toBeVisible(); + }); + } + + test(`Should drag and drop team on table level`, async ({ page }) => { + // Open department team dropdown as it is moved under it from last test + await openDragDropDropdown(page, teamNameDepartment); + + await dragAndDropElement( + page, + teamNameGroup, + '.ant-table-thead > tr', + true + ); + await confirmationDragAndDropTeam(page, teamNameGroup, 'Organization'); + + // Verify the team is moved under the table level + const movedTeam = page.locator( + `.ant-table-row-level-0[data-row-key="${teamNameGroup}"]` + ); + await movedTeam.scrollIntoViewIfNeeded(); + + await expect(movedTeam).toBeVisible(); + }); + + test('Delete Teams', async ({ page }) => { + for (const teamName of [ + teamNameBusiness, + teamNameDivision, + teamNameDepartment, + teamNameGroup, + ]) { + const getTeamResponse = page.waitForResponse( + `/api/v1/teams/name/${teamName}*` + ); + + await page.getByRole('link', { name: teamName }).click(); + await getTeamResponse; + + await hardDeleteTeam(page); + + // Validate the deleted team + await expect(page.locator('table')).not.toContainText(teamName); + } + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/dragDrop.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/dragDrop.ts new file mode 100644 index 000000000000..8fc97411c1b3 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/dragDrop.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, Page } from '@playwright/test'; +import { toastNotification } from './common'; + +export const dragAndDropElement = async ( + page: Page, + dragElement: string, + dropTarget: string, + isHeader?: boolean +) => { + const dragElementLocator = page.locator(`[data-row-key="${dragElement}"]`); + const dropTargetLocator = isHeader + ? page.locator(dropTarget) + : page.locator(`[data-row-key="${dropTarget}"]`); + + // Ensure the element is draggable + const draggable = await dragElementLocator.getAttribute('draggable'); + if (draggable !== 'true') { + throw new Error('Element is not draggable'); + } + + // Perform drag and drop + await dragElementLocator.dispatchEvent('dragstart'); + await dropTargetLocator.dispatchEvent('drop'); + await dragElementLocator.dispatchEvent('dragend'); +}; + +export const openDragDropDropdown = async (page: Page, name: string) => { + const dropdownIcon = page.locator( + `[data-row-key=${name}] > .whitespace-nowrap > [data-testid="expand-icon"] > svg` + ); + await dropdownIcon.click(); +}; + +export const confirmationDragAndDropTeam = async ( + page: Page, + dragTeam: string, + dropTeam: string +) => { + // Confirmation message before the transfer + await expect( + page.locator('[data-testid="confirmation-modal"] .ant-modal-body') + ).toContainText( + `Click on Confirm if you’d like to move ${dragTeam} team under ${dropTeam} team.` + ); + + const patchResponse = page.waitForResponse('/api/v1/teams/*'); + await page.locator('.ant-modal-footer > .ant-btn-primary').click(); + await patchResponse; + + await toastNotification(page, 'Team moved successfully!'); +}; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts index 10cf7db398e5..ce6546d2a592 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/team.ts @@ -11,7 +11,7 @@ * limitations under the License. */ import { expect, Page } from '@playwright/test'; -import { descriptionBox, uuid } from './common'; +import { descriptionBox, toastNotification, uuid } from './common'; import { validateFormNameFieldInput } from './form'; const TEAM_TYPES = ['Department', 'Division', 'Group']; @@ -75,11 +75,7 @@ export const hardDeleteTeam = async (page: Page) => { await deleteResponse; - await expect(page.locator('.Toastify__toast-body')).toHaveText( - /deleted successfully!/ - ); - - await page.click('.Toastify__close-button'); + await toastNotification(page, /deleted successfully!/); }; export const getNewTeamDetails = (teamName: string) => {