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 new file mode 100644 index 00000000000..81e8ed8cb01 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/DragAndDrop.ts @@ -0,0 +1,45 @@ +/* + * 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/common/Utils/Glossary.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Glossary.ts index 6721fda6056..255ec9799b8 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Glossary.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Glossary.ts @@ -96,3 +96,25 @@ export const removeGlossaryTerm = ( '[data-testid="entity-right-panel"] [data-testid="glossary-container"] [data-testid="add-tag"]' ).should('be.visible'); }; + +export const confirmationDragAndDropGlossary = ( + dragElement: string, + dropElement: string, + isHeader?: boolean +) => { + interceptURL('PATCH', `/api/v1/glossaryTerms/*`, 'patchGlossaryTerm'); + + // confirmation message before the transfer + cy.get('[data-testid="confirmation-modal"] .ant-modal-body').contains( + `Click on Confirm if you’d like to move ${ + isHeader + ? `${dragElement} under ${dropElement} .` + : `${dragElement} term under ${dropElement} term.` + }` + ); + + // click on submit modal button to confirm the transfer + cy.get('.ant-modal-footer > .ant-btn-primary').click(); + + verifyResponseStatusCode('@patchGlossaryTerm', 200); +}; diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Teams.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Teams.ts new file mode 100644 index 00000000000..90461466b8f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Teams.ts @@ -0,0 +1,87 @@ +/* + * 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, + verifyResponseStatusCode, +} from '../common'; + +export const commonTeamDetails = { + username: 'Aaron Johnson', + userId: 'aaron_johnson0', + assetname: 'dim_address', + email: 'team1@gmail.com', + updatedEmail: 'updatedemail@gmail.com', +}; +export const confirmationDragAndDropTeam = ( + dragTeam: string, + dropTeam: string +) => { + interceptURL('PUT', `/api/v1/teams`, 'putTeam'); + + // confirmation message before the transfer + cy.get('[data-testid="confirmation-modal"] .ant-modal-body') + .contains( + `Click on Confirm if you’d like to move ${dragTeam} team under ${dropTeam} team.` + ) + .should('be.visible'); + + // click on submit modal button to confirm the transfer + cy.get('.ant-modal-footer > .ant-btn-primary').click(); + + verifyResponseStatusCode('@putTeam', 200); + + toastNotification('Team moved successfully!'); +}; + +export const deleteTeamPermanently = (teamName: string) => { + interceptURL('GET', `/api/v1/teams/name/${teamName}*`, 'getSelectedTeam'); + // Click on created team + cy.get(`[data-row-key="${teamName}"]`).contains(teamName).click(); + + verifyResponseStatusCode('@getSelectedTeam', 200); + cy.get( + '[data-testid="team-detail-header"] [data-testid="manage-button"]' + ).click(); + + cy.get('[data-menu-id*="delete-button"]').should('be.visible'); + + cy.get('[data-testid="delete-button-title"]').click(); + + cy.get('[data-testid="confirm-button"]') + .should('exist') + .should('be.disabled'); + + // Check if soft delete option is present + cy.get('[data-testid="soft-delete-option"]').should('contain', teamName); + + // Click on permanent delete option + cy.get('[data-testid="hard-delete-option"]') + .should('contain', teamName) + .click(); + + cy.get('[data-testid="confirmation-text-input"]').type('DELETE'); + + interceptURL('DELETE', '/api/v1/teams/*', 'deleteTeam'); + cy.get('[data-testid="confirm-button"]').click(); + + verifyResponseStatusCode('@deleteTeam', 200); + + // Verify the toast message + toastNotification(`"${teamName}" deleted successfully!`); + + // Validating the deleted team + + cy.get('table').should('not.contain', teamName); +}; diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/TeamsDragAndDrop.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/TeamsDragAndDrop.spec.js new file mode 100644 index 00000000000..b4e23a332ba --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/TeamsDragAndDrop.spec.js @@ -0,0 +1,168 @@ +/* + * 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 { addTeam, interceptURL, toastNotification } from '../../common/common'; +import { + dragAndDropElement, + openDragDropDropdown, +} from '../../common/Utils/DragAndDrop'; +import { + commonTeamDetails, + confirmationDragAndDropTeam, + deleteTeamPermanently, +} from '../../common/Utils/Teams'; +import { uuid } from '../../constants/constants'; +import { SidebarItem } from '../../constants/Entity.interface'; +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', () => { + beforeEach(() => { + interceptURL('GET', `/api/v1/users?fields=*`, 'getUserDetails'); + interceptURL('GET', `/api/v1/permissions/team/name/*`, 'permissions'); + cy.login(); + + cy.sidebarClick(SidebarItem.SETTINGS); + + // Clicking on teams + cy.settingClick(GlobalSettingOptions.TEAMS); + }); + + it('Add new team for drag and drop', () => { + 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 + ); + }); + }); + + 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}"]`).should( + 'be.visible' + ); + }); + + it('Permanently deleting a team for drag and drop', () => { + [teamNameBusiness, teamNameDivision, teamNameDepartment, teamNameGroup].map( + (teamName) => { + deleteTeamPermanently(teamName); + } + ); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataInsightSettings.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataInsightSettings.spec.js index df4769313b5..958e7af0b8a 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataInsightSettings.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataInsightSettings.spec.js @@ -28,26 +28,26 @@ describe( verifyResponseStatusCode('@getApplications', 200); }); - it('Edit data insight application', () => { - interceptURL( - 'GET', - '/api/v1/apps/name/DataInsightsApplication?fields=*', - 'getDataInsightDetails' - ); - interceptURL('PATCH', '/api/v1/apps/*', 'updateApplication'); - cy.get( - '[data-testid="data-insights-application-card"] [data-testid="config-btn"]' - ).click(); - verifyResponseStatusCode('@getDataInsightDetails', 200); - cy.get('[data-testid="edit-button"]').click(); - cy.get('[data-testid="cron-type"]').click(); - cy.get('.rc-virtual-list [title="Day"]').click(); - cy.get('[data-testid="hour-options"]').click(); - cy.get('[title="01"]').click(); - cy.get('.ant-modal-body [data-testid="deploy-button"]').click(); - verifyResponseStatusCode('@updateApplication', 200); - cy.get('[data-testid="cron-string"]').should('contain', 'At 01:00 AM'); - }); + it('Edit data insight application', () => { + interceptURL( + 'GET', + '/api/v1/apps/name/DataInsightsApplication?fields=*', + 'getDataInsightDetails' + ); + interceptURL('PATCH', '/api/v1/apps/*', 'updateApplication'); + cy.get( + '[data-testid="data-insights-application-card"] [data-testid="config-btn"]' + ).click(); + verifyResponseStatusCode('@getDataInsightDetails', 200); + cy.get('[data-testid="edit-button"]').click(); + cy.get('[data-testid="cron-type"]').click(); + cy.get('.rc-virtual-list [title="Day"]').click(); + cy.get('[data-testid="hour-options"]').click(); + cy.get('[title="01"]').click(); + cy.get('.ant-modal-body [data-testid="deploy-button"]').click(); + verifyResponseStatusCode('@updateApplication', 200); + cy.get('[data-testid="cron-string"]').should('contain', 'At 01:00 AM'); + }); it('Uninstall application', () => { interceptURL( @@ -75,27 +75,26 @@ describe( ); }); - it('Install application', () => { - interceptURL('GET', '/api/v1/apps/marketplace?limit=*', 'getMarketPlace'); - interceptURL('POST', '/api/v1/apps', 'installApplication'); - cy.get('[data-testid="add-application"]').click(); - verifyResponseStatusCode('@getMarketPlace', 200); - cy.get( - '[data-testid="data-insights-application-card"] [data-testid="config-btn"]' - ).click(); - cy.get('[data-testid="install-application"]').click(); - cy.get('[data-testid="save-button"]').click(); - cy.get('[data-testid="cron-type"]').click(); - cy.get('.rc-virtual-list [title="Day"]').click(); - cy.get('[data-testid="cron-type"]').should('contain', 'Day'); - cy.get('[data-testid="deploy-button"]').click(); - verifyResponseStatusCode('@installApplication', 201); - verifyResponseStatusCode('@getApplications', 200); - cy.get('[data-testid="data-insights-application-card"]').should( - 'be.visible' - ); - }); - + it('Install application', () => { + interceptURL('GET', '/api/v1/apps/marketplace?limit=*', 'getMarketPlace'); + interceptURL('POST', '/api/v1/apps', 'installApplication'); + cy.get('[data-testid="add-application"]').click(); + verifyResponseStatusCode('@getMarketPlace', 200); + cy.get( + '[data-testid="data-insights-application-card"] [data-testid="config-btn"]' + ).click(); + cy.get('[data-testid="install-application"]').click(); + cy.get('[data-testid="save-button"]').click(); + cy.get('[data-testid="cron-type"]').click(); + cy.get('.rc-virtual-list [title="Day"]').click(); + cy.get('[data-testid="cron-type"]').should('contain', 'Day'); + cy.get('[data-testid="deploy-button"]').click(); + verifyResponseStatusCode('@installApplication', 201); + verifyResponseStatusCode('@getApplications', 200); + cy.get('[data-testid="data-insights-application-card"]').should( + 'be.visible' + ); + }); it('Deploy & run application', () => { interceptURL( diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js index 6393eeb1a86..ef467843b41 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js @@ -25,7 +25,9 @@ import { verifyResponseStatusCode, } from '../../common/common'; import { deleteGlossary } from '../../common/GlossaryUtils'; +import { dragAndDropElement } from '../../common/Utils/DragAndDrop'; import { visitEntityDetailsPage } from '../../common/Utils/Entity'; +import { confirmationDragAndDropGlossary } from '../../common/Utils/Glossary'; import { addOwner, removeOwner } from '../../common/Utils/Owner'; import { COLUMN_NAME_FOR_APPLY_GLOSSARY_TERM, @@ -1223,6 +1225,50 @@ describe('Glossary page should work properly', { tags: 'Glossary' }, () => { }); }); + it('Drag and Drop should work properly for glossary term', () => { + selectActiveGlossary(NEW_GLOSSARY.name); + + dragAndDropElement( + NEW_GLOSSARY_TERMS.term_2.fullyQualifiedName, + NEW_GLOSSARY_TERMS.term_1.fullyQualifiedName + ); + + confirmationDragAndDropGlossary( + NEW_GLOSSARY_TERMS.term_2.name, + NEW_GLOSSARY_TERMS.term_1.name + ); + + // verify the term is moved under the parent term + cy.get( + `.ant-table-row-level-1[data-row-key="${Cypress.$.escapeSelector( + NEW_GLOSSARY_TERMS.term_1.fullyQualifiedName + )}.${NEW_GLOSSARY_TERMS.term_2.name}"]` + ).should('be.visible'); + }); + + it('Drag and Drop should work properly for glossary term at table level', () => { + selectActiveGlossary(NEW_GLOSSARY.name); + + dragAndDropElement( + `${NEW_GLOSSARY_TERMS.term_1.fullyQualifiedName}.${NEW_GLOSSARY_TERMS.term_2.name}`, + '.ant-table-thead > tr', + true + ); + + confirmationDragAndDropGlossary( + NEW_GLOSSARY_TERMS.term_2.name, + NEW_GLOSSARY.name, + true + ); + + // verify the term is moved under the parent term + cy.get( + `.ant-table-row-level-0[data-row-key="${Cypress.$.escapeSelector( + NEW_GLOSSARY_TERMS.term_2.fullyQualifiedName + )}"]` + ).should('be.visible'); + }); + it('Delete glossary term should work properly', () => { const terms = Object.values(NEW_GLOSSARY_TERMS); selectActiveGlossary(NEW_GLOSSARY.name); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Teams.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Teams.spec.js index 3dc9543f3d2..f0dcd2c1ead 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Teams.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Teams.spec.js @@ -23,6 +23,7 @@ import { uuid, verifyResponseStatusCode, } from '../../common/common'; +import { deleteTeamPermanently } from '../../common/Utils/Teams'; import { SidebarItem } from '../../constants/Entity.interface'; import { GlobalSettingOptions } from '../../constants/settings.constant'; @@ -394,55 +395,6 @@ describe('Teams flow should work properly', () => { it('Permanently deleting a team without soft deleting should work properly', () => { // Add a new team addTeam(HARD_DELETE_TEAM_DETAILS); - - interceptURL( - 'GET', - `/api/v1/teams/name/${HARD_DELETE_TEAM_DETAILS.name}*`, - 'getSelectedTeam' - ); - // Click on created team - cy.get(`[data-row-key="${HARD_DELETE_TEAM_DETAILS.name}"]`) - .contains(HARD_DELETE_TEAM_DETAILS.name) - .click(); - - verifyResponseStatusCode('@getSelectedTeam', 200); - cy.get( - '[data-testid="team-detail-header"] [data-testid="manage-button"]' - ).click(); - - cy.get('[data-menu-id*="delete-button"]').should('be.visible'); - - cy.get('[data-testid="delete-button-title"]').click(); - - cy.get('[data-testid="confirm-button"]') - .should('exist') - .should('be.disabled'); - - // Check if soft delete option is present - cy.get('[data-testid="soft-delete-option"]').should( - 'contain', - HARD_DELETE_TEAM_DETAILS.name - ); - - // Click on permanent delete option - cy.get('[data-testid="hard-delete-option"]') - .should('contain', HARD_DELETE_TEAM_DETAILS.name) - .click(); - - cy.get('[data-testid="confirmation-text-input"]').type('DELETE'); - - interceptURL('DELETE', '/api/v1/teams/*', 'deleteTeam'); - cy.get('[data-testid="confirm-button"]').click(); - - verifyResponseStatusCode('@deleteTeam', 200); - - // Verify the toast message - toastNotification( - `"${HARD_DELETE_TEAM_DETAILS.name}" deleted successfully!` - ); - - // Validating the deleted team - - cy.get('table').should('not.contain', HARD_DELETE_TEAM_DETAILS.name); + deleteTeamPermanently(HARD_DELETE_TEAM_DETAILS.name); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Service/ServiceIngestion.spec.ts b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Service/ServiceIngestion.spec.ts index 727ae6e261d..70391209bcb 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Service/ServiceIngestion.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Service/ServiceIngestion.spec.ts @@ -21,6 +21,7 @@ import RedshiftWithDBTIngestionClass from '../../common/Services/RedshiftWithDBT import S3IngestionClass from '../../common/Services/S3IngestionClass'; import SnowflakeIngestionClass from '../../common/Services/SnowflakeIngestionClass'; import SupersetIngestionClass from '../../common/Services/SupersetIngestionClass'; +import { goToServiceListingPage } from '../../common/Utils/Services'; const services = [ new S3IngestionClass(), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.test.tsx index 3a95b6f17f2..66a47d8015e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.test.tsx @@ -39,6 +39,7 @@ jest.mock('react-router-dom', () => ({ jest.mock('../../../../utils/TeamUtils', () => ({ getMovedTeamData: jest.fn().mockReturnValue([]), + isDropRestricted: jest.fn().mockReturnValue(false), })); jest.mock('../../../../rest/teamsAPI', () => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx index b137ee8a38b..a501a28cf92 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/TeamHierarchy.tsx @@ -12,10 +12,11 @@ */ import { Modal, Skeleton, Typography } from 'antd'; -import { ColumnsType } from 'antd/lib/table'; +import { ColumnsType, TableProps } from 'antd/lib/table'; import { ExpandableConfig } from 'antd/lib/table/interface'; import { AxiosError } from 'axios'; -import { isEmpty } from 'lodash'; +import classNames from 'classnames'; +import { isEmpty, isUndefined } from 'lodash'; import React, { FC, useCallback, useMemo, useState } from 'react'; import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; @@ -26,7 +27,6 @@ import { NO_DATA_PLACEHOLDER, } from '../../../../constants/constants'; import { TABLE_CONSTANTS } from '../../../../constants/Teams.constants'; -import { TeamType } from '../../../../generated/api/teams/createTeam'; import { Team } from '../../../../generated/entity/teams/team'; import { Include } from '../../../../generated/type/include'; import { getTeamByName, updateTeam } from '../../../../rest/teamsAPI'; @@ -34,7 +34,10 @@ import { Transi18next } from '../../../../utils/CommonUtils'; import { getEntityName } from '../../../../utils/EntityUtils'; import { getTeamsWithFqnPath } from '../../../../utils/RouterUtils'; import { getTableExpandableConfig } from '../../../../utils/TableUtils'; -import { getMovedTeamData } from '../../../../utils/TeamUtils'; +import { + getMovedTeamData, + isDropRestricted, +} from '../../../../utils/TeamUtils'; import { showErrorToast, showSuccessToast } from '../../../../utils/ToastUtils'; import { DraggableBodyRowProps } from '../../../common/Draggable/DraggableBodyRowProps.interface'; import FilterTablePlaceHolder from '../../../common/ErrorWithPlaceholder/FilterTablePlaceHolder'; @@ -53,6 +56,7 @@ const TeamHierarchy: FC = ({ const [isModalOpen, setIsModalOpen] = useState(false); const [isTableLoading, setIsTableLoading] = useState(false); const [movedTeam, setMovedTeam] = useState(); + const [isTableHovered, setIsTableHovered] = useState(false); const columns: ColumnsType = useMemo(() => { return [ @@ -143,14 +147,27 @@ const TeamHierarchy: FC = ({ ]; }, [data, isFetchingAllTeamAdvancedDetails, onTeamExpand]); + const handleTableHover = useCallback( + (value: boolean) => setIsTableHovered(value), + [] + ); + const handleMoveRow = useCallback( - async (dragRecord: Team, dropRecord: Team) => { - if (dragRecord.id === dropRecord.id) { + async (dragRecord: Team, dropRecord?: Team) => { + if (dragRecord.id === dropRecord?.id) { return; } - if (dropRecord.teamType === TeamType.Group) { - showErrorToast(t('message.error-team-transfer-message')); + if ( + !isUndefined(dropRecord) && + isDropRestricted(dragRecord.teamType, dropRecord?.teamType) + ) { + showErrorToast( + t('message.error-team-transfer-message', { + dragTeam: dragRecord.teamType, + dropTeam: dropRecord.teamType, + }) + ); return; } @@ -167,11 +184,14 @@ const TeamHierarchy: FC = ({ if (movedTeam) { setIsTableLoading(true); try { + const dropTeam = movedTeam.to?.id; const data = await getTeamByName(movedTeam.from.name, { fields: 'users, defaultRoles, policies, owner, parents, children', include: Include.All, }); - await updateTeam(getMovedTeamData(data, [movedTeam.to.id])); + await updateTeam( + getMovedTeamData(data, dropTeam ? [dropTeam] : undefined) + ); onTeamExpand(true, currentTeam?.name); showSuccessToast(t('message.team-moved-success')); } catch (error) { @@ -179,12 +199,29 @@ const TeamHierarchy: FC = ({ } finally { setIsTableLoading(false); setIsModalOpen(false); + setIsTableHovered(false); } } }; const onTableRow = (record: Team, index?: number) => - ({ index, handleMoveRow, record } as DraggableBodyRowProps); + ({ + index, + handleMoveRow, + handleTableHover, + record, + } as DraggableBodyRowProps); + + const onTableHeader: TableProps['onHeaderRow'] = () => + ({ + handleMoveRow, + handleTableHover, + } as DraggableBodyRowProps); + + const onDragConfirmationModalClose = useCallback(() => { + setIsModalOpen(false); + setIsTableHovered(false); + }, []); const expandableConfig: ExpandableConfig = useMemo( () => ({ @@ -203,7 +240,9 @@ const TeamHierarchy: FC = ({ = ({ pagination={false} rowKey="name" size="small" + onHeaderRow={onTableHeader} onRow={onTableRow} /> @@ -230,14 +270,14 @@ const TeamHierarchy: FC = ({ okText={t('label.confirm')} open={isModalOpen} title={t('label.move-the-entity', { entity: t('label.team') })} - onCancel={() => setIsModalOpen(false)} + onCancel={onDragConfirmationModalClose} onOk={handleChangeTeam}> } values={{ - from: movedTeam?.from?.name, - to: movedTeam?.to?.name, + from: movedTeam?.from.name, + to: movedTeam?.to?.name ?? getEntityName(currentTeam), entity: t('label.team-lowercase'), }} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/team.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/team.interface.ts index 229f7ff0cde..0944a98ebc1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/team.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Team/TeamDetails/team.interface.ts @@ -30,7 +30,7 @@ export interface TeamHierarchyProps { export interface MovedTeamProps { from: Team; - to: Team; + to?: Team; } export interface TableExpandableDataProps { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Draggable/DraggableBodyRowProps.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/Draggable/DraggableBodyRowProps.interface.ts index 3ee4ea198cb..dcf432f044f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Draggable/DraggableBodyRowProps.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Draggable/DraggableBodyRowProps.interface.ts @@ -20,7 +20,7 @@ export interface DraggableBodyRowProps extends React.HTMLAttributes { index?: number; record?: T; - handleMoveRow: (dragRecord: T, dropRecord?: T) => void; + handleMoveRow: (dragRecord: T, dropRecord?: T) => Promise; handleTableHover?: (value: boolean) => void; } diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index e08473d2486..e920c0e4424 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "{{entity}} Größe muss zwischen {{min}} und {{max}} liegen.", "entity-size-must-be-between-2-and-64": "{{entity}} Größe muss zwischen 2 und 64 liegen.", "entity-transfer-message": "Klicken Sie auf Bestätigen, wenn Sie {{entity}} von <0>{{from}} unter <0>{{to}} {{entity}} verschieben möchten.", - "error-team-transfer-message": "Sie können nicht zu diesem Team wechseln, da Teamtyp 'Gruppe' keine untergeordneten Teams haben kann.", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "Fehler beim Abrufen des Zugriffstokens.", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "Laden Sie alle Ihre {{entity}} als CSV-Datei herunter und teilen Sie sie mit Ihrem Team.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index f25530aa41c..31eea16ae5b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "{{entity}} size must be between {{min}} and {{max}}", "entity-size-must-be-between-2-and-64": "{{entity}} size must be between 2 and 64", "entity-transfer-message": "Click on Confirm if you’d like to move <0>{{from}} {{entity}} under <0>{{to}} {{entity}}.", - "error-team-transfer-message": "You cannot move to this team as Team Type Group cannot have children", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "Error while fetching access token.", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "Download all your {{entity}} as a CSV file, and share with your team.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index 65173db7971..31608b964ce 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "El tamaño de {{entity}} debe estar entre {{min}} y {{max}}", "entity-size-must-be-between-2-and-64": "El tamaño de {{entity}} debe estar entre 2 y 64", "entity-transfer-message": "Haga clic en Confirmar si desea mover <0>{{from}} {{entity}} debajo de <0>{{to}} {{entity}}.", - "error-team-transfer-message": "No puede mover a este equipo ya que el tipo de equipo \"Grupo\" no puede tener hijos", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "Error al obtener el token de acceso.", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "Download all your {{entity}} as a CSV file, and share with your team.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index 2d7f2d25fed..3f7802f2f5a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "{{entity}} taille doit être de {{min}} et {{max}}", "entity-size-must-be-between-2-and-64": "{{entity}} taille doit être comprise entre 2 et 64", "entity-transfer-message": "Cliquer sur Confirmer si vous souhaitez déplacer <0>{{from}} {{entity}} sous <0>{{to}} {{entity}}.", - "error-team-transfer-message": "Vous ne pouvez pas déplacer cette équipe car le type d'équipe Groupe ne peut pas eêtre changé. Merci de créer une nouvelle équipe avec le type préférentiel.", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "Erreur pendant la récupération du jeton d'accès.", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "Download all your {{entity}} as a CSV file, and share with your team.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json index 6ddda73f836..1cbc2838ba0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "{{entity}} יכול להיות בגודל בין {{min}} ל-{{max}}", "entity-size-must-be-between-2-and-64": "{{entity}} יכול להיות בגודל בין 2 ל-64", "entity-transfer-message": "לחץ על אישור אם ברצונך להעביר <0>{{from}} {{entity}} מתחת ל-<0>{{to}} {{entity}}.", - "error-team-transfer-message": "אין אפשרות להעביר לצוות זה כיוון שצוות מסוג קבוצה לא יכולים לכלול ילדים", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "שגיאה בעת קבלת טוקן גישה.", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "הורד את כל הישויות שלך בפורמט קובץ CSV ושתף עם הצוות שלך.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index a5ec737fdfe..232528bc6dc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "{{entity}}のサイズは{{min}}以上{{max}}以下にしてください", "entity-size-must-be-between-2-and-64": "{{entity}}のサイズは2以上64以下", "entity-transfer-message": "Click on Confirm if you’d like to move <0>{{from}} {{entity}} under <0>{{to}} {{entity}}.", - "error-team-transfer-message": "You cannot move to this team as Team Type Group cannot have children", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "アクセストークンの取得中にエラーが発生しました。", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "Download all your {{entity}} as a CSV file, and share with your team.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json index 0547b85a3b7..68a85e2459e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "{{entity}} grootte moet tussen {{min}} en {{max}} liggen", "entity-size-must-be-between-2-and-64": "{{entity}} grootte moet tussen 2 en 64 liggen", "entity-transfer-message": "Klik op Bevestigen als je <0>{{from}} {{entity}} wilt verplaatsen naar <0>{{to}} {{entity}}.", - "error-team-transfer-message": "Je kunt niet naar dit team verplaatsen, omdat een groep van het teamtype geen kinderen kan hebben", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "Fout bij het ophalen van toegangstoken.", "explore-our-guide-here": "bekijk onze handleiding hier.", "export-entity-help": "Download al je {{entity}} als een CSV-bestand en deel het met je team.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index 7ddaef9228f..93e3d041288 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "O tamanho de {{entity}} deve ser entre {{min}} e {{max}}", "entity-size-must-be-between-2-and-64": "O tamanho de {{entity}} deve ser entre 2 e 64", "entity-transfer-message": "Clique em Confirmar se deseja mover <0>{{from}} {{entity}} para <0>{{to}} {{entity}}.", - "error-team-transfer-message": "Você não pode mover para esta equipe, pois o Tipo de Equipe Grupo não pode ter filhos", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "Erro ao buscar token de acesso.", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "Baixe todos os seus {{entity}} como um arquivo CSV e compartilhe com sua equipe.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index 691ba408168..d04a0fe1cb9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "Размер {{entity}} должен быть между {{min}} и {{max}}", "entity-size-must-be-between-2-and-64": "Размер {{entity}} должен быть от 2 до 64", "entity-transfer-message": "Нажмите «Подтвердить», если вы хотите переместить <0>{{from}} {{entity}} в <0>{{to}} {{entity}}.", - "error-team-transfer-message": "Вы не можете перейти в эту команду, так как в группе данного типа не может быть дочерних элементов", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "Ошибка при получении токена доступа.", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "Загрузите все свои {{entity}} в виде CSV-файла и поделитесь ими со своей командой.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 8c412ef7e57..2c0ba9b182b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -1403,7 +1403,7 @@ "entity-size-in-between": "{{entity}}大小须介于{{min}}和{{max}}之间", "entity-size-must-be-between-2-and-64": "{{entity}}大小必须介于2和64之间", "entity-transfer-message": "如果您想将<0>{{from}} {{entity}} 移动到<0>{{to}} {{entity}}中,请单击确认", - "error-team-transfer-message": "由于团队类型的组不能有子级,因此您无法移动到此团队", + "error-team-transfer-message": "You cannot move to this team as Team Type {{dragTeam}} can't be {{dropTeam}} children", "error-while-fetching-access-token": "获取访问令牌时出现错误", "explore-our-guide-here": "explore our guide here.", "export-entity-help": "以 CSV 文件格式导出下载所有{{entity}},并可与团队共享。", diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/components/table.less b/openmetadata-ui/src/main/resources/ui/src/styles/components/table.less index f9a166e3a25..35c3cd5a121 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/components/table.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/components/table.less @@ -91,7 +91,7 @@ .drop-over-background { .drop-over-child { td { - background: @active-color; + background: @active-color !important; border: 2px dashed @primary-color; border-left: transparent; @@ -113,7 +113,7 @@ border: 2px dashed @primary-color; td { - background: @active-color; + background: @active-color !important; } } } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.test.ts new file mode 100644 index 00000000000..901c8cea446 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.test.ts @@ -0,0 +1,112 @@ +/* + * 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 { TeamType } from '../generated/entity/teams/team'; +import { isDropRestricted } from './TeamUtils'; + +describe('isDropRestricted', () => { + it('should be droppable if on drop team is BusinessUnit', () => { + const groupDragResult = isDropRestricted( + TeamType.Group, + TeamType.BusinessUnit + ); + + expect(groupDragResult).toBe(false); + + const departmentDragResult = isDropRestricted( + TeamType.Department, + TeamType.BusinessUnit + ); + + expect(departmentDragResult).toBe(false); + + const divisionDragResult = isDropRestricted( + TeamType.Division, + TeamType.BusinessUnit + ); + + expect(divisionDragResult).toBe(false); + }); + + it('should not be droppable if on drop team team is Group', () => { + const businessUnitDragResult = isDropRestricted( + TeamType.BusinessUnit, + TeamType.Group + ); + + expect(businessUnitDragResult).toBe(true); + + const departmentDragResult = isDropRestricted( + TeamType.Department, + TeamType.Group + ); + + expect(departmentDragResult).toBe(true); + + const divisionDragResult = isDropRestricted( + TeamType.Division, + TeamType.Group + ); + + expect(divisionDragResult).toBe(true); + }); + + // For Division TeamType + it('should not be droppable if on drop team is Division', () => { + const businessUnitDragResult = isDropRestricted( + TeamType.BusinessUnit, + TeamType.Division + ); + + expect(businessUnitDragResult).toBe(true); + }); + + it('should be droppable if on drop team is Division', () => { + const departmentDragResult = isDropRestricted( + TeamType.Department, + TeamType.Division + ); + + expect(departmentDragResult).toBe(false); + + const groupDragResult = isDropRestricted(TeamType.Group, TeamType.Division); + + expect(groupDragResult).toBe(false); + }); + + // For Department TeamType + it('should not be droppable if on drop team is Department', () => { + const businessUnitDragResult = isDropRestricted( + TeamType.BusinessUnit, + TeamType.Department + ); + + expect(businessUnitDragResult).toBe(true); + + const divisionDragResult = isDropRestricted( + TeamType.Division, + TeamType.Department + ); + + expect(divisionDragResult).toBe(true); + }); + + it('should be droppable if on drop team is Department', () => { + const groupDragResult = isDropRestricted( + TeamType.Group, + TeamType.Department + ); + + expect(groupDragResult).toBe(false); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.ts index abd22fb8f61..784e1a39e77 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TeamUtils.ts @@ -57,7 +57,10 @@ const getEntityValue = (value: EntityReference[] | undefined) => { return undefined; }; -export const getMovedTeamData = (team: Team, parents: string[]): CreateTeam => { +export const getMovedTeamData = ( + team: Team, + parents?: string[] +): CreateTeam => { const userDetails = omit(cloneDeep(team), [ 'id', 'fullyQualifiedName', @@ -84,7 +87,7 @@ export const getMovedTeamData = (team: Team, parents: string[]): CreateTeam => { userDetails.teamType === TeamType.Group ? undefined : getEntityValue(children), - parents: parents, + parents, policies: getEntityValue(policies), users: getEntityValue(users), } as CreateTeam; @@ -126,3 +129,21 @@ export const getTeamOptionsFromType = (parentType: TeamType) => { return [TeamType.Group]; } }; + +/** + * Restricting the drop of team based on the team type + * Group: Can't have any child team + * Division: Can have only Department and Group + * Department: Can have only Group + */ + +export const isDropRestricted = ( + dragTeamType?: TeamType, + dropTeamType?: TeamType +) => + dropTeamType === TeamType.Group || + (dropTeamType === TeamType.Division && + dragTeamType === TeamType.BusinessUnit) || + (dropTeamType === TeamType.Department && + dragTeamType === TeamType.BusinessUnit) || + (dropTeamType === TeamType.Department && dragTeamType === TeamType.Division);