diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.ts b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.ts deleted file mode 100644 index fc73fb6af5e..00000000000 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.ts +++ /dev/null @@ -1,1302 +0,0 @@ -/* - * Copyright 2022 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 { - descriptionBox, - interceptURL, - toastNotification, - verifyMultipleResponseStatusCode, - verifyResponseStatusCode, -} from '../../common/common'; -import { - addOwnerInGlossary, - checkDisplayName, - createGlossary, - createGlossaryTerms, - deleteGlossary, - selectActiveGlossary, - verifyGlossaryDetails, -} from '../../common/GlossaryUtils'; -import { dragAndDropElement } from '../../common/Utils/DragAndDrop'; -import { visitEntityDetailsPage } from '../../common/Utils/Entity'; -import { confirmationDragAndDropGlossary } from '../../common/Utils/Glossary'; -import { getToken } from '../../common/Utils/LocalStorage'; -import { - addOwner, - generateRandomUser, - removeOwner, -} from '../../common/Utils/Owner'; -import { assignTags, removeTags } from '../../common/Utils/Tags'; -import { - COLUMN_NAME_FOR_APPLY_GLOSSARY_TERM, - DELETE_TERM, - SEARCH_ENTITY_TABLE, -} from '../../constants/constants'; -import { EntityType, SidebarItem } from '../../constants/Entity.interface'; -import { - GLOSSARY_1, - GLOSSARY_2, - GLOSSARY_3, - GLOSSARY_OWNER_LINK_TEST_ID, -} from '../../constants/glossary.constant'; -import { GlobalSettingOptions } from '../../constants/settings.constant'; - -const CREDENTIALS = generateRandomUser(); -const userName = `${CREDENTIALS.firstName}${CREDENTIALS.lastName}`; - -const CREDENTIALS_2 = generateRandomUser(); -const userName2 = `${CREDENTIALS_2.firstName}${CREDENTIALS_2.lastName}`; - -let createdUserId = ''; -let createdUserId_2 = ''; - -const visitGlossaryTermPage = ( - termName: string, - fqn: string, - fetchPermission?: boolean -) => { - interceptURL( - 'GET', - `/api/v1/search/query?q=*&from=0&size=*&index=glossary_term_search_index`, - 'getGlossaryTerm' - ); - interceptURL( - 'GET', - '/api/v1/permissions/glossaryTerm/*', - 'waitForTermPermission' - ); - - cy.get(`[data-row-key="${Cypress.$.escapeSelector(fqn)}"]`) - .scrollIntoView() - .should('be.visible') - .contains(termName) - .should('be.visible') - .click(); - - verifyResponseStatusCode('@getGlossaryTerms', 200); - - if (fetchPermission) { - verifyResponseStatusCode('@waitForTermPermission', 200); - } - cy.get('.ant-tabs .glossary-overview-tab').should('be.visible').click(); -}; - -const checkAssetsCount = (assetsCount) => { - cy.get('[data-testid="assets"] [data-testid="filter-count"]') - .scrollIntoView() - .should('have.text', assetsCount); -}; - -const addAssetToGlossaryTerm = (glossaryTerm, glossary) => { - goToGlossaryPage(); - selectActiveGlossary(glossary.name); - goToAssetsTab(glossaryTerm.name, glossaryTerm.fullyQualifiedName, true); - - checkAssetsCount(0); - cy.contains('Adding a new Asset is easy, just give it a spin!').should( - 'be.visible' - ); - - cy.get('[data-testid="glossary-term-add-button-menu"]').click(); - cy.get('.ant-dropdown-menu .ant-dropdown-menu-title-content') - .contains('Assets') - .click(); - - cy.get('[data-testid="asset-selection-modal"] .ant-modal-title').should( - 'contain', - 'Add Assets' - ); - - glossaryTerm.assets.forEach((asset) => { - interceptURL('GET', '/api/v1/search/query*', 'searchAssets'); - cy.get('[data-testid="asset-selection-modal"] [data-testid="searchbar"]') - .click() - .clear() - .type(asset.name); - - verifyResponseStatusCode('@searchAssets', 200); - - cy.get( - `[data-testid="table-data-card_${asset.fullyQualifiedName}"] input[type="checkbox"]` - ).click(); - }); - - cy.get('[data-testid="save-btn"]').click(); - checkAssetsCount(glossaryTerm.assets.length); -}; - -const removeAssetsFromGlossaryTerm = (glossaryTerm, glossary) => { - goToGlossaryPage(); - selectActiveGlossary(glossary.name); - goToAssetsTab(glossaryTerm.name, glossaryTerm.fullyQualifiedName, true); - checkAssetsCount(glossaryTerm.assets.length); - glossaryTerm.assets.forEach((asset, index) => { - interceptURL('GET', '/api/v1/search/query*', 'searchAssets'); - cy.get(`[data-testid="manage-button-${asset.fullyQualifiedName}"]`).click(); - cy.get('[data-testid="delete-button"]').click(); - cy.get("[data-testid='save-button']").click(); - - cy.get('[data-testid="overview"]').click(); - cy.get('[data-testid="assets"]').click(); - - // go assets tab - verifyResponseStatusCode('@searchAssets', 200); - - checkAssetsCount(glossaryTerm.assets.length - (index + 1)); - }); -}; - -const deleteGlossaryTerm = ({ name, fullyQualifiedName }) => { - visitGlossaryTermPage(name, fullyQualifiedName); - - cy.get('[data-testid="manage-button"]') - .scrollIntoView() - .should('be.visible') - .click(); - cy.get('[data-testid="delete-button"]') - .scrollIntoView() - .should('be.visible') - .click(); - - cy.get('[data-testid="delete-confirmation-modal"]') - .should('exist') - .then(() => { - cy.get('[role="dialog"]').should('be.visible'); - cy.get('[data-testid="modal-header"]').should('be.visible'); - }); - cy.get('[data-testid="modal-header"]') - .should('be.visible') - .should('contain', name); - cy.get('[data-testid="confirmation-text-input"]') - .should('be.visible') - .type(DELETE_TERM); - - cy.get('[data-testid="confirm-button"]') - .should('be.visible') - .should('not.disabled') - .click(); - - toastNotification('"Glossary Term" deleted successfully!'); - cy.get('[data-testid="delete-confirmation-modal"]').should('not.exist'); - cy.get('[data-testid="glossary-left-panel"]') - .should('be.visible') - .should('not.contain', name); -}; - -const goToAssetsTab = ( - name: string, - fqn: string, - fetchPermission?: boolean -) => { - visitGlossaryTermPage(name, fqn, fetchPermission); - - cy.get('[data-testid="assets"]') - .scrollIntoView() - .should('be.visible') - .click(); - cy.get('.ant-tabs-tab-active').contains('Assets').should('be.visible'); -}; - -const updateSynonyms = (uSynonyms) => { - cy.get('[data-testid="synonyms-container"]') - .scrollIntoView() - .should('be.visible'); - cy.get('[data-testid="synonyms-container"]') - .find('[data-testid="edit-button"]', { timeout: 10000 }) - .scrollIntoView() - .should('be.visible') - .click(); - cy.get('[data-testid="synonyms-container"] .ant-select-selector') - .should('be.visible') - .find('.ant-select-selection-item-remove') - .should('exist') - .click({ force: true, multiple: true }); - cy.get('.ant-select-selection-overflow') - .should('exist') - .type(uSynonyms.join('{enter}')) - .type('{enter}'); - cy.get('[data-testid="save-synonym-btn"]').scrollIntoView().click(); - verifyResponseStatusCode('@saveGlossaryTermData', 200); - cy.get('[data-testid="synonyms-container"]') - .as('synonyms-container') - .should('be.visible'); - uSynonyms.forEach((synonym) => { - cy.get('@synonyms-container').contains(synonym).should('be.visible'); - }); -}; - -const updateTerms = (newTerm: string) => { - interceptURL( - 'GET', - '/api/v1/search/query?q=*&index=glossary_term_search_index*', - 'getGlossaryTerm' - ); - cy.get('[data-testid="related-term-container"]') - .scrollIntoView() - .should('be.visible'); - cy.get('[data-testid="related-term-add-button"]') - .scrollIntoView() - .should('be.visible') - .click({ force: true }); - cy.get('.ant-select-selection-overflow') - .should('be.visible') - .click() - .type(newTerm); - verifyResponseStatusCode('@getGlossaryTerm', 200, { requestTimeout: 10000 }); - cy.get('.ant-select-dropdown').filter(':visible').contains(newTerm).click(); - cy.get('[data-testid="saveAssociatedTag"]').click(); - verifyResponseStatusCode('@saveGlossaryTermData', 200, { - requestTimeout: 10000, - }); - - cy.get('[data-testid="related-term-container"]') - .contains(newTerm) - .should('be.visible'); -}; - -const updateReferences = (newRef: { name: string; url: string }) => { - cy.get('[data-testid="section-References"]') - .find('[data-testid="edit-button"]') - .scrollIntoView() - .click(); - cy.get('[data-testid="add-references-button"]').should('be.visible').click(); - cy.get('#references_1_name').should('be.visible').type(newRef.name); - cy.get('#references_1_endpoint').should('be.visible').type(newRef.url); - cy.get('[data-testid="save-btn"]').scrollIntoView().click(); - verifyResponseStatusCode('@saveGlossaryTermData', 200); - cy.get('[data-testid="references-container"]') - .contains(newRef.name) - .should('be.visible') - .invoke('attr', 'href') - .should('eq', newRef.url); -}; - -const updateDescription = (newDescription, isGlossary) => { - if (isGlossary) { - interceptURL('PATCH', '/api/v1/glossaries/*', 'saveGlossary'); - } else { - interceptURL('PATCH', '/api/v1/glossaryTerms/*', 'saveData'); - } - - cy.get('[data-testid="edit-description"]').scrollIntoView().click(); - cy.get('.ant-modal-wrap').should('be.visible'); - cy.get(descriptionBox).should('be.visible').as('description'); - cy.get('@description').clear(); - cy.get('@description').type(newDescription); - cy.get('[data-testid="save"]').scrollIntoView().click(); - if (isGlossary) { - verifyResponseStatusCode('@saveGlossary', 200); - } else { - verifyResponseStatusCode('@saveData', 200); - } - cy.get('.ant-modal-wrap').should('not.exist'); - - cy.get('[data-testid="viewer-container"]') - .contains(newDescription) - .should('be.visible'); -}; - -const upVoting = (api: string) => { - cy.get('[data-testid="up-vote-btn"]').click(); - - cy.wait(api).then(({ request, response }) => { - expect(request.body.updatedVoteType).to.equal('votedUp'); - - expect(response.statusCode).to.equal(200); - }); - - cy.get('[data-testid="up-vote-count"]').contains(1); -}; - -const downVoting = (api: string) => { - cy.get('[data-testid="down-vote-btn"]').click(); - - cy.wait(api).then(({ request, response }) => { - expect(request.body.updatedVoteType).to.equal('votedDown'); - expect(response.statusCode).to.equal(200); - }); - - cy.get('[data-testid="down-vote-count"]').contains(1); - - // after voting down, the selected up-voting will cancel and count goes down - cy.get('[data-testid="up-vote-count"]').contains(0); -}; - -// goes to initial stage after down voting glossary or glossary term -const initialVoting = (api: string) => { - cy.get('[data-testid="down-vote-btn"]').click(); - - cy.wait(api).then(({ request, response }) => { - expect(request.body.updatedVoteType).to.equal('unVoted'); - expect(response.statusCode).to.equal(200); - }); - - cy.get('[data-testid="up-vote-count"]').contains(0); - cy.get('[data-testid="down-vote-count"]').contains(0); -}; - -const voteGlossary = (isGlossary?: boolean) => { - if (isGlossary) { - interceptURL('PUT', '/api/v1/glossaries/*/vote', 'voteGlossary'); - } else { - interceptURL('PUT', '/api/v1/glossaryTerms/*/vote', 'voteGlossaryTerm'); - } - upVoting(isGlossary ? '@voteGlossary' : '@voteGlossaryTerm'); - downVoting(isGlossary ? '@voteGlossary' : '@voteGlossaryTerm'); - initialVoting(isGlossary ? '@voteGlossary' : '@voteGlossaryTerm'); -}; - -const goToGlossaryPage = () => { - interceptURL('GET', '/api/v1/glossaryTerms*', 'getGlossaryTerms'); - interceptURL('GET', '/api/v1/glossaries?fields=*', 'fetchGlossaries'); - cy.sidebarClick(SidebarItem.GLOSSARY); -}; - -const approveGlossaryTermWorkflow = ({ glossary, glossaryTerm }) => { - goToGlossaryPage(); - - selectActiveGlossary(glossary.name); - - const { name, fullyQualifiedName } = glossaryTerm; - - visitGlossaryTermPage(name, fullyQualifiedName); - - cy.get('[data-testid="activity_feed"]').click(); - - interceptURL('GET', '/api/v1/feed*', 'activityFeed'); - - cy.get('[data-testid="global-setting-left-panel"]').contains('Tasks').click(); - - verifyResponseStatusCode('@activityFeed', 200); - - interceptURL('PUT', '/api/v1/feed/tasks/*/resolve', 'resolveTask'); - - // approve the task - cy.get( - '[data-testid="glossary-accept-reject-task-dropdown"] .ant-btn-compact-first-item > span' - ).click(); - - verifyResponseStatusCode('@resolveTask', 200); - - // Verify toast notification - toastNotification('Task resolved successfully'); - - verifyResponseStatusCode('@activityFeed', 200); - - goToGlossaryPage(); - selectActiveGlossary(glossary.name); - - cy.get(`[data-testid="${fullyQualifiedName}-status"]`) - .should('be.visible') - .contains('Approved'); -}; - -const addGlossaryTermsInEntityField = ({ - entityTerm, - entityField, - glossaryTerms, -}) => { - const glossaryContainer = `[data-row-key$="${entityTerm}.${entityField}"] [data-testid="glossary-container"]`; - - cy.get(`${glossaryContainer} [data-testid="entity-tags"] .ant-tag`) - .scrollIntoView() - .click(); - - const tagSelector = `${glossaryContainer} [data-testid="tag-selector"]`; - cy.get(tagSelector).click(); - - glossaryTerms.forEach((term) => { - cy.get(`${tagSelector} .ant-select-selection-search input`).type(term.name); - - cy.get( - `.ant-select-dropdown [data-testid='tag-${term.fullyQualifiedName}']` - ).click(); - cy.get(`[data-testid="selected-tag-${term.fullyQualifiedName}"]`).should( - 'exist' - ); - }); - - cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); -}; - -const checkTagsSortingAndHighlighting = ({ termFQN }) => { - cy.get('[data-testid="tags-viewer"] [data-testid="tags"]') - .first() - .as('firstTag'); - - cy.get('@firstTag').within(() => { - cy.get(`[data-testid="tag-${termFQN}"]`).should('exist'); - }); - cy.get('@firstTag').should('have.class', 'tag-highlight'); -}; - -const checkSummaryListItemSorting = ({ termFQN, columnName }) => { - cy.get('#right-panelV1 [data-testid="summary-list"]') - .as('summaryList') - .scrollIntoView(); - - cy.get('@summaryList').within(() => { - cy.get('[data-testid="summary-list-item"]') - .first() - .within(() => { - cy.get('[data-testid="entity-title"]') - .first() // checking for first entity title as collapse type will have more then 1 entity-title present - .should('have.text', columnName); - - checkTagsSortingAndHighlighting({ termFQN }); - }); - }); -}; - -const deleteUser = () => { - cy.getAllLocalStorage().then((storageData) => { - const token = getToken(storageData); - - cy.request({ - method: 'DELETE', - url: `/api/v1/users/${createdUserId}?hardDelete=true&recursive=false`, - headers: { Authorization: `Bearer ${token}` }, - }).then((response) => { - expect(response.status).to.eq(200); - }); - - cy.request({ - method: 'DELETE', - url: `/api/v1/users/${createdUserId_2}?hardDelete=true&recursive=false`, - headers: { Authorization: `Bearer ${token}` }, - }).then((response) => { - expect(response.status).to.eq(200); - }); - }); -}; - -describe('Glossary page should work properly', { tags: 'Governance' }, () => { - before(() => { - // Prerequisites - Create a user with data consumer role - cy.login(); - cy.getAllLocalStorage().then((data) => { - const token = getToken(data); - // Create a new user - cy.request({ - method: 'POST', - url: `/api/v1/users/signup`, - headers: { Authorization: `Bearer ${token}` }, - body: CREDENTIALS, - }).then((response) => { - createdUserId = response.body.id; - - // Assign user to the team - cy.sidebarClick(SidebarItem.SETTINGS); - // Clicking on teams - cy.settingClick(GlobalSettingOptions.TEAMS); - const appName = 'Applications'; - - interceptURL('GET', `/api/v1/teams/**`, 'getTeams'); - interceptURL( - 'GET', - `/api/v1/users?fields=*&limit=25&team=${appName}`, - 'teamUsers' - ); - - cy.get('[data-testid="search-bar-container"]').type(appName); - cy.get(`[data-row-key="${appName}"]`).contains(appName).click(); - verifyResponseStatusCode('@getTeams', 200); - verifyResponseStatusCode('@teamUsers', 200); - - interceptURL('GET', '/api/v1/users?*isBot=false*', 'getUsers'); - cy.get('[data-testid="add-new-user"]').click(); - verifyResponseStatusCode('@getUsers', 200); - interceptURL( - 'GET', - `api/v1/search/query?q=*&index=user_search_index*`, - 'searchOwner' - ); - cy.get( - '[data-testid="selectable-list"] [data-testid="search-bar-container"]' - ).type(userName); - verifyResponseStatusCode('@searchOwner', 200); - interceptURL('PATCH', `/api/v1/**`, 'patchOwner'); - cy.get(`.ant-popover [title="${userName}"]`).click(); - cy.get('[data-testid="selectable-list-update-btn"]').click(); - verifyResponseStatusCode('@patchOwner', 200); - }); - - // Create a new user_2 - cy.request({ - method: 'POST', - url: `/api/v1/users/signup`, - headers: { Authorization: `Bearer ${token}` }, - body: CREDENTIALS_2, - }).then((response) => { - createdUserId_2 = response.body.id; - }); - }); - }); - - after(() => { - cy.login(); - deleteUser(); - }); - - beforeEach(() => { - interceptURL('PATCH', '/api/v1/glossaryTerms/*', 'saveGlossaryTermData'); - cy.login(); - goToGlossaryPage(); - }); - - it('Create new glossary flow should work properly', () => { - createGlossary(GLOSSARY_1, true); - createGlossary(GLOSSARY_2, false); - createGlossary(GLOSSARY_3, false); - verifyGlossaryDetails(GLOSSARY_1); - verifyGlossaryDetails(GLOSSARY_2); - verifyGlossaryDetails(GLOSSARY_3); - }); - - it('Glossary Owner Flow', () => { - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_1.name) - .click(); - - checkDisplayName(GLOSSARY_1.name); - addOwner(userName, GLOSSARY_OWNER_LINK_TEST_ID); - cy.reload(); - addOwner('Alex Pollard', GLOSSARY_OWNER_LINK_TEST_ID); - cy.reload(); - removeOwner('Alex Pollard', 'Users', GLOSSARY_OWNER_LINK_TEST_ID); - }); - - it('Create glossary term should work properly', () => { - createGlossaryTerms(GLOSSARY_1); - createGlossaryTerms(GLOSSARY_2); - createGlossaryTerms(GLOSSARY_3); - }); - - it('Updating data of glossary should work properly', () => { - selectActiveGlossary(GLOSSARY_1.name); - - // Updating owner - addOwner(userName2, GLOSSARY_OWNER_LINK_TEST_ID); - - // Updating Reviewer - const reviewers = GLOSSARY_1.reviewers.map((reviewer) => reviewer.name); - addOwnerInGlossary( - [...reviewers, userName], - 'edit-reviewer-button', - 'glossary-reviewer-name', - false - ); - - // updating tags - removeTags(GLOSSARY_1.tag, EntityType.Glossary); - assignTags('PII.None', EntityType.Glossary); - - // updating description - updateDescription('Updated description', true); - - voteGlossary(true); - }); - - it('Team Approval Workflow for Glossary Term', () => { - cy.logout(); - cy.login(CREDENTIALS.email, CREDENTIALS.password); - approveGlossaryTermWorkflow({ - glossary: GLOSSARY_2, - glossaryTerm: GLOSSARY_2.terms[0], - }); - approveGlossaryTermWorkflow({ - glossary: GLOSSARY_2, - glossaryTerm: GLOSSARY_2.terms[1], - }); - cy.logout(); - Cypress.session.clearAllSavedSessions(); - cy.login(); - }); - - it('Update glossary term', () => { - const uSynonyms = ['pick up', 'take', 'obtain']; - const newRef = { name: 'take', url: 'https://take.com' }; - const term2 = GLOSSARY_3.terms[1].name; - const { name, fullyQualifiedName } = GLOSSARY_1.terms[0]; - const { name: newTermName, fullyQualifiedName: newTermFqn } = - GLOSSARY_1.terms[1]; - - // visit glossary page - interceptURL( - 'GET', - `/api/v1/glossaryTerms?directChildrenOf=*`, - 'glossaryTerm' - ); - interceptURL('GET', `/api/v1/permissions/glossary/*`, 'permissions'); - - cy.get('.ant-menu-item').contains(GLOSSARY_1.name).click(); - verifyMultipleResponseStatusCode(['@glossaryTerm', '@permissions'], 200); - - // visit glossary term page - visitGlossaryTermPage(name, fullyQualifiedName); - - // Updating synonyms - updateSynonyms(uSynonyms); - - // Updating References - updateReferences(newRef); - - // Updating Related terms - updateTerms(term2); - - // updating tags - // Some weired issue on terms, Term alread has an tag which causing failure - // Will fix this later - // updateTags(true); - - // updating description - updateDescription('Updated description', false); - - // Updating Reviewer - addOwnerInGlossary( - [userName], - 'edit-reviewer-button', - 'glossary-reviewer-name', - false - ); - - // updating voting for glossary term - voteGlossary(); - - goToGlossaryPage(); - cy.get('.ant-menu-item').contains(GLOSSARY_1.name).click(); - visitGlossaryTermPage(newTermName, newTermFqn); - - // Updating Reviewer - addOwnerInGlossary( - [userName], - 'edit-reviewer-button', - 'glossary-reviewer-name', - false - ); - }); - - it('User Approval Workflow for Glossary Term', () => { - cy.logout(); - cy.login(CREDENTIALS.email, CREDENTIALS.password); - approveGlossaryTermWorkflow({ - glossary: GLOSSARY_1, - glossaryTerm: GLOSSARY_1.terms[0], - }); - - approveGlossaryTermWorkflow({ - glossary: GLOSSARY_1, - glossaryTerm: GLOSSARY_1.terms[1], - }); - - approveGlossaryTermWorkflow({ - glossary: GLOSSARY_1, - glossaryTerm: GLOSSARY_1.terms[2], - }); - cy.logout(); - Cypress.session.clearAllSavedSessions(); - cy.login(); - }); - - it('Request Tags workflow for Glossary', function () { - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_1.name) - .click(); - - interceptURL( - 'GET', - `/api/v1/search/query?q=*%20AND%20disabled:false&index=tag_search_index*`, - 'suggestTag' - ); - interceptURL('POST', '/api/v1/feed', 'taskCreated'); - interceptURL('PUT', '/api/v1/feed/tasks/*/resolve', 'taskResolve'); - - cy.get('[data-testid="request-entity-tags"]').should('exist').click(); - - // check assignees for task which will be reviewer of the glossary term - cy.get( - '[data-testid="select-assignee"] > .ant-select-selector > .ant-select-selection-overflow' - ).within(() => { - for (const reviewer of [...GLOSSARY_1.reviewers, { name: userName }]) { - cy.contains(reviewer.name); - } - }); - - cy.get( - '[data-testid="select-assignee"] > .ant-select-selector > .ant-select-selection-overflow' - ).should('not.contain', userName2); - - cy.get('[data-testid="tag-selector"]') - .click() - .type('{backspace}') - .type('{backspace}') - .type('Personal'); - - verifyResponseStatusCode('@suggestTag', 200); - cy.get( - '.ant-select-dropdown [data-testid="tag-PersonalData.Personal"]' - ).click(); - cy.clickOutside(); - - cy.get('[data-testid="submit-tag-request"]').click(); - verifyResponseStatusCode('@taskCreated', 201); - - // Owner should not be able to accept the tag suggestion when reviewer is assigned - cy.logout(); - cy.login(CREDENTIALS_2.email, CREDENTIALS_2.password); - - goToGlossaryPage(); - - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_1.name) - .click(); - - cy.get('[data-testid="activity_feed"]').click(); - - cy.get('[data-testid="global-setting-left-panel"]') - .contains('Tasks') - .click(); - - // accept the tag suggestion button should not be present - cy.get('[data-testid="task-cta-buttons"]').should( - 'not.contain', - 'Accept Suggestion' - ); - - // Reviewer only should accepts the tag suggestion - cy.logout(); - cy.login(CREDENTIALS.email, CREDENTIALS.password); - - goToGlossaryPage(); - - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_1.name) - .click(); - - cy.get('[data-testid="activity_feed"]').click(); - - cy.get('[data-testid="global-setting-left-panel"]') - .contains('Tasks') - .click(); - - // Accept the tag suggestion which is created - cy.get('.ant-btn-compact-first-item').contains('Accept Suggestion').click(); - - verifyResponseStatusCode('@taskResolve', 200); - - cy.reload(); - - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_1.name) - .click(); - - checkDisplayName(GLOSSARY_1.name); - - cy.logout(); - Cypress.session.clearAllSavedSessions(); - cy.login(); - }); - - it('Request Tags workflow for Glossary and reviewer as Team', function () { - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_2.name) - .click(); - - interceptURL( - 'GET', - `/api/v1/search/query?q=*%20AND%20disabled:false&index=tag_search_index*`, - 'suggestTag' - ); - interceptURL('POST', '/api/v1/feed', 'taskCreated'); - interceptURL('PUT', '/api/v1/feed/tasks/*/resolve', 'taskResolve'); - - cy.get('[data-testid="request-entity-tags"]').should('exist').click(); - - // check assignees for task which will be Owner of the glossary term which is Team - cy.get( - '[data-testid="select-assignee"] > .ant-select-selector > .ant-select-selection-overflow' - ).within(() => { - for (const reviewer of GLOSSARY_2.reviewers) { - cy.contains(reviewer.name); - } - }); - - cy.get( - '[data-testid="select-assignee"] > .ant-select-selector > .ant-select-selection-overflow' - ).should('not.contain', GLOSSARY_2.owner); - - cy.get('[data-testid="tag-selector"]') - .click() - .type('{backspace}') - .type('{backspace}') - .type('Personal'); - - verifyResponseStatusCode('@suggestTag', 200); - cy.get( - '.ant-select-dropdown [data-testid="tag-PersonalData.Personal"]' - ).click(); - cy.clickOutside(); - - cy.get('[data-testid="submit-tag-request"]').click(); - verifyResponseStatusCode('@taskCreated', 201); - - // Reviewer should accepts the tag suggestion which belongs to the Team - cy.logout(); - cy.login(CREDENTIALS.email, CREDENTIALS.password); - - goToGlossaryPage(); - - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_2.name) - .click(); - - cy.get('[data-testid="activity_feed"]').click(); - - cy.get('[data-testid="global-setting-left-panel"]') - .contains('Tasks') - .click(); - - // Accept the tag suggestion which is created - cy.get('.ant-btn-compact-first-item').contains('Accept Suggestion').click(); - - verifyResponseStatusCode('@taskResolve', 200); - - cy.reload(); - - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_2.name) - .click(); - - checkDisplayName(GLOSSARY_2.name); - - cy.logout(); - Cypress.session.clearAllSavedSessions(); - cy.login(); - }); - - it('Request Description workflow for Glossary', function () { - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_3.name) - .click(); - - interceptURL( - 'GET', - `/api/v1/search/query?q=*%20AND%20disabled:false&index=tag_search_index*`, - 'suggestTag' - ); - interceptURL('POST', '/api/v1/feed', 'taskCreated'); - interceptURL('PUT', '/api/v1/feed/tasks/*/resolve', 'taskResolve'); - - cy.get('[data-testid="request-description"]').should('exist').click(); - - // check assignees for task which will be owner of the glossary since it has no reviewer - cy.get( - '[data-testid="select-assignee"] > .ant-select-selector > .ant-select-selection-overflow' - ).should('contain', GLOSSARY_3.owner); - - cy.get(descriptionBox).should('be.visible').as('description'); - cy.get('@description').clear(); - cy.get('@description').type(GLOSSARY_3.newDescription); - - cy.get('[data-testid="submit-btn"]').click(); - verifyResponseStatusCode('@taskCreated', 201); - - // Accept the tag suggestion which is created - cy.get('.ant-btn-compact-first-item').contains('Accept Suggestion').click(); - - verifyResponseStatusCode('@taskResolve', 200); - - cy.reload(); - - cy.get('[data-testid="glossary-left-panel"]') - .contains(GLOSSARY_3.name) - .click(); - - checkDisplayName(GLOSSARY_3.name); - }); - - it('Assets Tab should work properly', () => { - const glossary1 = GLOSSARY_1.name; - const term1 = GLOSSARY_1.terms[0]; - const term2 = GLOSSARY_1.terms[1]; - - const glossary2 = GLOSSARY_2.name; - const term3 = GLOSSARY_2.terms[0]; - const term4 = GLOSSARY_2.terms[1]; - - const entity = SEARCH_ENTITY_TABLE.table_3; - - selectActiveGlossary(glossary2); - - goToAssetsTab(term3.name, term3.fullyQualifiedName, true); - cy.contains('Adding a new Asset is easy, just give it a spin!').should( - 'be.visible' - ); - visitEntityDetailsPage({ - term: entity.term, - serviceName: entity.serviceName, - entity: entity.entity, - }); - - const parentPath = - '[data-testid="entity-right-panel"] [data-testid="glossary-container"]'; - - // Add glossary tag to entity for mutually exclusive - cy.get(parentPath).then((glossaryContainer) => { - // Check if the "Add Tag" button is visible - if (!glossaryContainer.find('[data-testid="add-tag"]').is(':visible')) { - // If "Add Tag" is not visible, click on "Edit Tag" - cy.get( - '[data-testid="entity-right-panel"] [data-testid="glossary-container"] [data-testid="edit-button"]' - ) - .scrollIntoView() - .click(); - cy.get('[data-testid="remove-tags"]') - .should('be.visible') - .click({ multiple: true }); - - interceptURL('PATCH', '/api/v1/tables/*', 'removeTags'); - cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); - verifyResponseStatusCode('@removeTags', 200); - } - }); - - cy.get(`${parentPath} [data-testid="add-tag"]`).click(); - - // Select 1st term - cy.get('[data-testid="tag-selector"] #tagsForm_tags') - .click() - .type(term1.name); - cy.get(`[data-testid="tag-${glossary1}.${term1.name}"]`).click(); - cy.get('[data-testid="tag-selector"]').should('contain', term1.name); - // Select 2nd term - cy.get('[data-testid="tag-selector"] #tagsForm_tags') - .click() - .type(term2.name); - cy.get(`[data-testid="tag-${glossary1}.${term2.name}"]`).click(); - cy.get('[data-testid="tag-selector"]').should('contain', term2.name); - - interceptURL('GET', '/api/v1/tags', 'tags'); - interceptURL('PATCH', '/api/v1/tables/*', 'saveTag'); - - cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView(); - cy.clickOutside(); - cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); - verifyResponseStatusCode('@saveTag', 400); - toastNotification( - `Tag labels ${glossary1}.${term2.name} and ${glossary1}.${term1.name} are mutually exclusive and can't be assigned together` - ); - - // Add non mutually exclusive tags - cy.get( - '[data-testid="entity-right-panel"] [data-testid="glossary-container"] > [data-testid="entity-tags"] [data-testid="add-tag"]' - ).click(); - - // Select 1st term - cy.get('[data-testid="tag-selector"] #tagsForm_tags') - .click() - .type(term3.name); - - cy.get(`[data-testid="tag-${glossary2}.${term3.name}"]`).click(); - cy.get('[data-testid="tag-selector"]').should('contain', term3.name); - // Select 2nd term - cy.get('[data-testid="tag-selector"] #tagsForm_tags') - .click() - .type(term4.name); - cy.get(`[data-testid="tag-${glossary2}.${term4.name}"]`).click(); - cy.clickOutside(); - cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); - verifyResponseStatusCode('@saveTag', 200); - cy.get( - '[data-testid="entity-right-panel"] [data-testid="glossary-container"]' - ) - .scrollIntoView() - .should('contain', term3.name) - .should('contain', term4.name); - - cy.get( - '[data-testid="entity-right-panel"] [data-testid="glossary-container"] [data-testid="icon"]' - ).should('have.length', 2); - - // Add tag to schema table - const firstColumn = - '[data-testid="glossary-tags-0"] > [data-testid="tags-wrapper"] > [data-testid="glossary-container"] > [data-testid="entity-tags"] [data-testid="add-tag"]'; - cy.get(firstColumn).scrollIntoView(); - cy.get(firstColumn).click(); - - cy.get('[data-testid="tag-selector"]').click().type(term3.name); - cy.get( - `.ant-select-dropdown [data-testid="tag-${glossary2}.${term3.name}"]` - ).click(); - - cy.get('[data-testid="tag-selector"] > .ant-select-selector').contains( - term3.name - ); - cy.clickOutside(); - cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); - cy.get( - '[data-testid="glossary-tags-0"] > [data-testid="tags-wrapper"] > [data-testid="glossary-container"]' - ) - .scrollIntoView() - .should('contain', term3.name); - cy.get( - '[data-testid="glossary-tags-0"] > [data-testid="tags-wrapper"] > [data-testid="glossary-container"] [data-testid="icon"]' - ).should('be.visible'); - - goToGlossaryPage(); - - cy.get('.ant-menu-item').contains(glossary2).click(); - - goToAssetsTab(term3.name, term3.fullyQualifiedName, false); - - cy.get('[data-testid="entity-header-display-name"]') - .contains(entity.term) - .should('be.visible'); - }); - - it('Add asset to glossary term using asset modal', () => { - const term = GLOSSARY_3.terms[0]; - addAssetToGlossaryTerm(term, GLOSSARY_3); - }); - - it('Remove asset from glossary term using asset modal', () => { - const term = GLOSSARY_3.terms[0]; - removeAssetsFromGlossaryTerm(term, GLOSSARY_3); - }); - - it('Remove Glossary term from entity should work properly', () => { - const glossaryName = GLOSSARY_2.name; - const { name, fullyQualifiedName } = GLOSSARY_2.terms[0]; - const entity = SEARCH_ENTITY_TABLE.table_3; - - selectActiveGlossary(glossaryName); - - interceptURL('GET', '/api/v1/search/query*', 'assetTab'); - // go assets tab - goToAssetsTab(name, fullyQualifiedName); - verifyResponseStatusCode('@assetTab', 200); - - interceptURL('GET', '/api/v1/feed*', 'entityDetails'); - - cy.get('[data-testid="entity-header-display-name"]') - .contains(entity.term) - .click(); - verifyResponseStatusCode('@entityDetails', 200); - - // Remove all added tags - cy.get( - '[data-testid="entity-right-panel"] [data-testid="glossary-container"] [data-testid="edit-button"]' - ) - .scrollIntoView() - .click(); - cy.get('[data-testid="remove-tags"]') - .should('be.visible') - .click({ multiple: true }); - - interceptURL('PATCH', '/api/v1/tables/*', 'removeTags'); - cy.clickOutside(); - cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); - verifyResponseStatusCode('@removeTags', 200); - - // Remove the added column tag from entity - interceptURL('PATCH', '/api/v1/tables/*', 'removeSchemaTags'); - - cy.get( - '[data-testid="glossary-tags-0"] > [data-testid="tags-wrapper"] > [data-testid="glossary-container"]' - ) - .scrollIntoView() - .trigger('mouseover') - .find('[data-testid="edit-button"]') - .scrollIntoView() - .click(); - - cy.get( - `[data-testid="selected-tag-${glossaryName}.${name}"] [data-testid="remove-tags"` - ).click(); - - cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click(); - verifyResponseStatusCode('@removeSchemaTags', 200); - - cy.get( - '[data-testid="glossary-tags-0"] > [data-testid="tags-wrapper"] > [data-testid="glossary-container"]' - ) - .scrollIntoView() - .should('not.contain', name) - .and('not.contain', 'Personal'); - - goToGlossaryPage(); - - selectActiveGlossary(glossaryName); - - goToAssetsTab(name, fullyQualifiedName); - cy.contains('Adding a new Asset is easy, just give it a spin!').should( - 'be.visible' - ); - }); - - it('Tags and entity summary columns should be sorted based on current Term Page', () => { - const terms = GLOSSARY_3.terms; - const entityTable = SEARCH_ENTITY_TABLE.table_1; - - visitEntityDetailsPage({ - term: entityTable.term, - serviceName: entityTable.serviceName, - entity: entityTable.entity, - }); - - addGlossaryTermsInEntityField({ - entityTerm: entityTable.term, - entityField: COLUMN_NAME_FOR_APPLY_GLOSSARY_TERM, - glossaryTerms: terms, - }); - - goToGlossaryPage(); - selectActiveGlossary(GLOSSARY_3.name); - goToAssetsTab(terms[0].name, terms[0].fullyQualifiedName, true); - - checkSummaryListItemSorting({ - columnName: COLUMN_NAME_FOR_APPLY_GLOSSARY_TERM, - termFQN: terms[0].fullyQualifiedName, - }); - }); - - it('Change glossary term hierarchy using menu options', () => { - interceptURL('PATCH', '/api/v1/glossaryTerms/*', 'saveGlossaryTermData'); - interceptURL( - 'GET', - '/api/v1/glossaryTerms/name/*', - 'fetchGlossaryTermData' - ); - - const parentTerm = GLOSSARY_3.terms[0]; - const childTerm = GLOSSARY_3.terms[1]; - selectActiveGlossary(GLOSSARY_3.name); - cy.get('[data-testid="expand-collapse-all-button"]').click(); - visitGlossaryTermPage(childTerm.name, childTerm.fullyQualifiedName, true); - - cy.get('[data-testid="manage-button"]').click(); - cy.get('[data-testid="change-parent-button"]').should('be.visible').click(); - cy.get( - '[data-testid="change-parent-select"] > .ant-select-selector' - ).click(); - cy.get(`[title="${parentTerm.name}"]`).click(); - - // Submit the select parent - cy.get('.ant-modal-footer > .ant-btn-primary').click(); - - verifyResponseStatusCode('@saveGlossaryTermData', 200); - verifyResponseStatusCode('@fetchGlossaryTermData', 200); - - // Todo: Need to fix this @Ashish8689 - // cy.get('[data-testid="assets"] [data-testid="filter-count"]') - // .should('be.visible') - // .contains('3'); - - // checking the breadcrumb, if the change parent term is updated and displayed - cy.get('[data-testid="breadcrumb-link"]') - .contains(`${parentTerm.name}`) - .as('breadcrumb'); - - cy.get('@breadcrumb').click(); - - verifyResponseStatusCode('@fetchGlossaryTermData', 200); - - // checking the child term is updated and displayed under the parent term - cy.get('[data-testid="terms"] [data-testid="filter-count"]') - .should('be.visible') - .contains('1') - .click(); - - cy.get(`[data-testid="${childTerm.name}"]`).should('be.visible'); - - goToGlossaryPage(); - - const newTermHierarchy = `${Cypress.$.escapeSelector(GLOSSARY_3.name)}.${ - parentTerm.name - }.${childTerm.name}`; - selectActiveGlossary(GLOSSARY_3.name); - cy.get('[data-testid="expand-collapse-all-button"]').click(); - // verify the term is moved under the parent term - cy.get(`[data-row-key='${newTermHierarchy}']`).should('be.visible'); - - // re-dropping the term to the root level - dragAndDropElement( - `${GLOSSARY_3.name}.${parentTerm.name}.${childTerm.name}`, - '.ant-table-thead > tr', - true - ); - - confirmationDragAndDropGlossary(childTerm.name, GLOSSARY_3.name, true); - }); - - it('Drag and Drop should work properly for glossary term', () => { - const { fullyQualifiedName: term1Fqn, name: term1Name } = - GLOSSARY_1.terms[0]; - const { fullyQualifiedName: term2Fqn, name: term2Name } = - GLOSSARY_1.terms[1]; - - selectActiveGlossary(GLOSSARY_1.name); - dragAndDropElement(term2Fqn, term1Fqn); - - confirmationDragAndDropGlossary(term2Name, term1Name); - - // clicking on the expand icon to view the child term - cy.get( - `[data-row-key=${Cypress.$.escapeSelector( - term1Fqn - )}] [data-testid="expand-icon"] > svg` - ).click(); - - cy.get( - `.ant-table-row-level-1[data-row-key="${Cypress.$.escapeSelector( - term1Fqn - )}.${term2Name}"]` - ).should('be.visible'); - }); - - it('Drag and Drop should work properly for glossary term at table level', () => { - selectActiveGlossary(GLOSSARY_1.name); - cy.get('[data-testid="expand-collapse-all-button"]').click(); - dragAndDropElement( - `${GLOSSARY_1.terms[0].fullyQualifiedName}.${GLOSSARY_1.terms[1].name}`, - '.ant-table-thead > tr', - true - ); - - confirmationDragAndDropGlossary( - GLOSSARY_1.terms[1].name, - GLOSSARY_1.name, - true - ); - - // verify the term is moved under the parent term - cy.get('[data-testid="expand-collapse-all-button"]').click(); - cy.get( - `.ant-table-row-level-0[data-row-key="${Cypress.$.escapeSelector( - GLOSSARY_1.terms[1].fullyQualifiedName - )}"]` - ).should('be.visible'); - }); - - it('Delete glossary term should work properly', () => { - selectActiveGlossary(GLOSSARY_2.name); - GLOSSARY_2.terms.forEach(deleteGlossaryTerm); - }); - - it('Delete glossary should work properly', () => { - verifyResponseStatusCode('@fetchGlossaries', 200); - [GLOSSARY_1.name, GLOSSARY_2.name, GLOSSARY_3.name].forEach((glossary) => { - deleteGlossary(glossary); - }); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts index 7d90a245d57..b44ecef87fc 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/Glossary.spec.ts @@ -11,8 +11,10 @@ * limitations under the License. */ import test, { expect } from '@playwright/test'; +import { get } from 'lodash'; import { SidebarItem } from '../../constant/sidebar'; import { DashboardClass } from '../../support/entity/DashboardClass'; +import { EntityTypeEndpoint } from '../../support/entity/Entity.interface'; import { TableClass } from '../../support/entity/TableClass'; import { TopicClass } from '../../support/entity/TopicClass'; import { Glossary } from '../../support/glossary/Glossary'; @@ -26,19 +28,38 @@ import { toastNotification, uuid, } from '../../utils/common'; +import { + addMultiOwner, + assignGlossaryTerm, + assignTag, + updateDescription, +} from '../../utils/entity'; import { addAssetToGlossaryTerm, + addReferences, + addRelatedTerms, + addSynonyms, approveGlossaryTermTask, + approveTagsTask, + assignTagToGlossaryTerm, + changeTermHierarchyFromModal, + confirmationDragAndDropGlossary, + createDescriptionTaskForGlossary, createGlossary, createGlossaryTerms, + createTagTaskForGlossary, + deleteGlossaryOrGlossaryTerm, + dragAndDropTerm, goToAssetsTab, renameGlossaryTerm, selectActiveGlossary, + selectActiveGlossaryTerm, validateGlossaryTerm, verifyGlossaryDetails, verifyGlossaryTermAssets, } from '../../utils/glossary'; import { sidebarClick } from '../../utils/sidebar'; +import { TaskDetails } from '../../utils/task'; import { performUserLogin } from '../../utils/user'; const user1 = new UserClass(); @@ -150,9 +171,97 @@ test.describe('Glossary tests', () => { await afterAction(); }); - test('Add and Remove Assets', async ({ browser }) => { - const { page, afterAction, apiContext } = await performAdminLogin(browser); + test('Update Glossary and Glossary Term', async ({ browser }) => { + test.slow(true); + const { page, afterAction, apiContext } = await performAdminLogin(browser); + const glossary1 = new Glossary(); + const glossaryTerm1 = new GlossaryTerm(glossary1); + const glossaryTerm2 = new GlossaryTerm(glossary1); + glossary1.data.terms = [glossaryTerm1, glossaryTerm2]; + const user3 = new UserClass(); + const user4 = new UserClass(); + await glossary1.create(apiContext); + await glossaryTerm1.create(apiContext); + await glossaryTerm2.create(apiContext); + await user3.create(apiContext); + await user4.create(apiContext); + + try { + await test.step('Update Glossary', async () => { + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + + // Update description + await updateDescription(page, 'Demo description to be updated'); + + // Update Owners + await addMultiOwner({ + page, + ownerNames: [user3.getUserName()], + activatorBtnDataTestId: 'edit-owner', + resultTestId: 'glossary-right-panel-owner-link', + endpoint: EntityTypeEndpoint.Glossary, + isSelectableInsideForm: false, + type: 'Users', + }); + + // Update Reviewer + await addMultiOwner({ + page, + ownerNames: [user3.getUserName()], + activatorBtnDataTestId: 'Add', + resultTestId: 'glossary-reviewer-name', + endpoint: EntityTypeEndpoint.Glossary, + type: 'Users', + }); + + await assignTag(page, 'PersonalData.Personal'); + }); + + await test.step('Update Glossary Term', async () => { + await redirectToHomePage(page); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + await selectActiveGlossaryTerm(page, glossaryTerm1.data.displayName); + // Update description + await updateDescription(page, 'Demo description to be updated'); + + // Update Synonyms + await addSynonyms(page, [getRandomLastName(), getRandomLastName()]); + + // Update References + const references = [ + { name: getRandomLastName(), url: 'http://example.com' }, + { name: getRandomLastName(), url: 'http://trial.com' }, + ]; + await addReferences(page, references); + + // Update Related Terms + await addRelatedTerms(page, [glossaryTerm2]); + + // Update Tag + await assignTagToGlossaryTerm( + page, + 'PersonalData.Personal', + 'Add', + 'panel-container' + ); + }); + } finally { + await glossaryTerm1.delete(apiContext); + await glossaryTerm2.delete(apiContext); + await glossary1.delete(apiContext); + await user3.delete(apiContext); + await user4.delete(apiContext); + await afterAction(); + } + }); + + test('Add and Remove Assets', async ({ browser }) => { + test.slow(true); + + const { page, afterAction, apiContext } = await performAdminLogin(browser); const glossary1 = new Glossary(); const glossaryTerm1 = new GlossaryTerm(glossary1); const glossaryTerm2 = new GlossaryTerm(glossary1); @@ -453,6 +562,307 @@ test.describe('Glossary tests', () => { } }); + test('Drag and Drop Glossary Term', async ({ browser }) => { + const { page, afterAction, apiContext } = await performAdminLogin(browser); + const glossary1 = new Glossary(); + const glossaryTerm1 = new GlossaryTerm(glossary1); + const glossaryTerm2 = new GlossaryTerm(glossary1); + glossary1.data.terms = [glossaryTerm1, glossaryTerm2]; + + try { + await glossary1.create(apiContext); + await glossaryTerm1.create(apiContext); + await glossaryTerm2.create(apiContext); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + + await test.step('Drag and Drop Glossary Term', async () => { + await dragAndDropTerm( + page, + glossaryTerm1.data.displayName, + glossaryTerm2.data.displayName + ); + + await confirmationDragAndDropGlossary( + page, + glossaryTerm1.data.name, + glossaryTerm2.data.name + ); + + await expect( + page.getByRole('cell', { + name: glossaryTerm1.responseData.displayName, + }) + ).not.toBeVisible(); + + const termRes = page.waitForResponse('/api/v1/glossaryTerms?*'); + + // verify the term is moved under the parent term + await page.getByTestId('expand-collapse-all-button').click(); + await termRes; + + await expect( + page.getByRole('cell', { + name: glossaryTerm1.responseData.displayName, + }) + ).toBeVisible(); + }); + + await test.step( + 'Drag and Drop Glossary Term back at parent level', + async () => { + await redirectToHomePage(page); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + await page.getByTestId('expand-collapse-all-button').click(); + + await dragAndDropTerm( + page, + glossaryTerm1.data.displayName, + 'Terms' // Header Cell + ); + + await confirmationDragAndDropGlossary( + page, + glossaryTerm1.data.name, + glossary1.responseData.displayName, + true + ); + + // verify the term is moved back at parent level + await expect( + page.getByRole('cell', { + name: glossaryTerm1.responseData.displayName, + }) + ).toBeVisible(); + } + ); + } finally { + await glossaryTerm1.delete(apiContext); + await glossaryTerm2.delete(apiContext); + await glossary1.delete(apiContext); + await afterAction(); + } + }); + + test('Change glossary term hierarchy using menu options', async ({ + browser, + }) => { + const { page, afterAction, apiContext } = await performAdminLogin(browser); + const glossary1 = new Glossary(); + const glossaryTerm1 = new GlossaryTerm(glossary1); + const glossaryTerm2 = new GlossaryTerm(glossary1); + glossary1.data.terms = [glossaryTerm1, glossaryTerm2]; + + try { + await glossary1.create(apiContext); + await glossaryTerm1.create(apiContext); + await glossaryTerm2.create(apiContext); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + + await changeTermHierarchyFromModal( + page, + glossaryTerm1.data.displayName, + glossaryTerm2.data.displayName + ); + + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + + await expect( + page.getByRole('cell', { + name: glossaryTerm1.responseData.displayName, + }) + ).not.toBeVisible(); + + const termRes = page.waitForResponse('/api/v1/glossaryTerms?*'); + + // verify the term is moved under the parent term + await page.getByTestId('expand-collapse-all-button').click(); + await termRes; + + await expect( + page.getByRole('cell', { + name: glossaryTerm1.responseData.displayName, + }) + ).toBeVisible(); + } finally { + await glossaryTerm1.delete(apiContext); + await glossaryTerm2.delete(apiContext); + await glossary1.delete(apiContext); + await afterAction(); + } + }); + + test('Assign Glossary Term to entity and check assets', async ({ + browser, + }) => { + const { page, afterAction, apiContext } = await performAdminLogin(browser); + const table = new TableClass(); + const glossary1 = new Glossary(); + const glossaryTerm1 = new GlossaryTerm(glossary1); + glossary1.data.terms = [glossaryTerm1]; + + try { + await table.create(apiContext); + await glossary1.create(apiContext); + await glossaryTerm1.create(apiContext); + await table.visitEntityPage(page); + await assignGlossaryTerm(page, glossaryTerm1.responseData); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + await goToAssetsTab(page, glossaryTerm1.data.displayName, 1); + const entityFqn = get(table, 'entityResponseData.fullyQualifiedName'); + + await expect( + page.getByTestId(`table-data-card_${entityFqn}`) + ).toBeVisible(); + } finally { + await table.delete(apiContext); + await glossaryTerm1.delete(apiContext); + await glossary1.delete(apiContext); + await afterAction(); + } + }); + + test('Request description task for Glossary', async ({ browser }) => { + const { page, afterAction, apiContext } = await performAdminLogin(browser); + const glossary1 = new Glossary(); + const user1 = new UserClass(); + + try { + await user1.create(apiContext); + await glossary1.create(apiContext); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + + const value: TaskDetails = { + term: glossary1.data.name, + assignee: user1.responseData.name, + }; + + await page.getByTestId('request-description').click(); + + await createDescriptionTaskForGlossary(page, value, glossary1); + + const taskResolve = page.waitForResponse('/api/v1/feed/tasks/*/resolve'); + await page.click( + '.ant-btn-compact-first-item:has-text("Accept Suggestion")' + ); + await taskResolve; + + await redirectToHomePage(page); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + + const viewerContainerText = await page.textContent( + '[data-testid="viewer-container"]' + ); + + await expect(viewerContainerText).toContain('Updated description'); + } finally { + await user1.delete(apiContext); + await glossary1.delete(apiContext); + await afterAction(); + } + }); + + test('Request description task for Glossary Term', async ({ browser }) => { + const { page, afterAction, apiContext } = await performAdminLogin(browser); + const glossary1 = new Glossary(); + const user1 = new UserClass(); + const glossaryTerm1 = new GlossaryTerm(glossary1); + glossary1.data.terms = [glossaryTerm1]; + + try { + await user1.create(apiContext); + await glossary1.create(apiContext); + await glossaryTerm1.create(apiContext); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + await selectActiveGlossaryTerm(page, glossaryTerm1.data.displayName); + + const value: TaskDetails = { + term: glossaryTerm1.data.name, + assignee: user1.responseData.name, + }; + + await page.getByTestId('request-description').click(); + + await createDescriptionTaskForGlossary(page, value, glossaryTerm1, false); + + const taskResolve = page.waitForResponse('/api/v1/feed/tasks/*/resolve'); + await page.click( + '.ant-btn-compact-first-item:has-text("Accept Suggestion")' + ); + await taskResolve; + + await redirectToHomePage(page); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + await selectActiveGlossaryTerm(page, glossaryTerm1.data.displayName); + + const viewerContainerText = await page.textContent( + '[data-testid="viewer-container"]' + ); + + await expect(viewerContainerText).toContain('Updated description'); + } finally { + await user1.delete(apiContext); + await glossaryTerm1.delete(apiContext); + await glossary1.delete(apiContext); + await afterAction(); + } + }); + + test('Request tags for Glossary', async ({ browser }) => { + const { page, afterAction, apiContext } = await performAdminLogin(browser); + const { page: page1, afterAction: afterActionUser1 } = + await performUserLogin(browser, user2); + + const glossary1 = new Glossary(); + try { + await glossary1.create(apiContext); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + + const value: TaskDetails = { + term: glossary1.data.name, + assignee: user2.responseData.name, + tag: 'PersonalData.Personal', + }; + + await page.getByTestId('request-entity-tags').click(); + await createTagTaskForGlossary(page, value, glossary1); + await approveTagsTask(page1, value, glossary1); + } finally { + await glossary1.delete(apiContext); + await afterAction(); + } + }); + + test('Delete Glossary and Glossary Term using Delete Modal', async ({ + browser, + }) => { + const { page, afterAction, apiContext } = await performAdminLogin(browser); + const glossary1 = new Glossary(); + const glossaryTerm1 = new GlossaryTerm(glossary1); + glossary1.data.terms = [glossaryTerm1]; + await glossary1.create(apiContext); + await glossaryTerm1.create(apiContext); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, glossary1.data.displayName); + + // Delete Glossary Term + await selectActiveGlossaryTerm(page, glossaryTerm1.data.displayName); + await deleteGlossaryOrGlossaryTerm(page, glossaryTerm1.data.name, true); + + // Delete Glossary + await deleteGlossaryOrGlossaryTerm(page, glossary1.data.name); + await afterAction(); + }); + test.afterAll(async ({ browser }) => { const { afterAction, apiContext } = await performAdminLogin(browser); await user1.delete(apiContext); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts index 42a173bf8b8..74b4faba50f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/glossary.ts @@ -25,15 +25,17 @@ import { } from '../support/glossary/Glossary.interface'; import { GlossaryTerm } from '../support/glossary/GlossaryTerm'; import { + clickOutside, getApiContext, INVALID_NAMES, NAME_MAX_LENGTH_VALIDATION_ERROR, NAME_VALIDATION_ERROR, redirectToHomePage, + toastNotification, } from './common'; import { addMultiOwner } from './entity'; import { sidebarClick } from './sidebar'; -import { TASK_OPEN_FETCH_LINK } from './task'; +import { TaskDetails, TASK_OPEN_FETCH_LINK } from './task'; export const descriptionBox = '.toastui-editor-md-container > .toastui-editor > .ProseMirror'; @@ -643,3 +645,353 @@ export const renameGlossaryTerm = async ( ); await glossaryTerm.rename(data.name, data.fullyQualifiedName); }; + +export const dragAndDropTerm = async ( + page: Page, + dragElement: string, + dropTarget: string +) => { + await page.getByRole('cell', { name: dragElement }).hover(); + await page.mouse.down(); + await page.getByRole('cell', { name: dropTarget }).hover(); + await page.mouse.up(); +}; + +export const confirmationDragAndDropGlossary = async ( + page: Page, + dragElement: string, + dropElement: string, + isHeader = false +) => { + await expect( + page.locator('[data-testid="confirmation-modal"] .ant-modal-body') + ).toContainText( + `Click on Confirm if you’d like to move ${ + isHeader + ? `${dragElement} under ${dropElement} .` + : `${dragElement} term under ${dropElement} term.` + }` + ); + + const patchGlossaryTermResponse = page.waitForResponse( + '/api/v1/glossaryTerms/*' + ); + await page.getByRole('button', { name: 'Confirm' }).click(); + await patchGlossaryTermResponse; +}; + +export const changeTermHierarchyFromModal = async ( + page: Page, + dragElement: string, + dropElement: string +) => { + await selectActiveGlossaryTerm(page, dragElement); + await page.getByTestId('manage-button').click(); + await page.getByTestId('change-parent-button').click(); + await page + .locator('[data-testid="change-parent-select"] > .ant-select-selector') + .click(); + await page.getByTitle(dropElement).click(); + const saveRes = page.waitForResponse('/api/v1/glossaryTerms/*'); + await page.getByRole('button', { name: 'Submit' }).click(); + await saveRes; +}; + +export const deleteGlossaryOrGlossaryTerm = async ( + page: Page, + entityName: string, + isGlossaryTerm = false +) => { + await page.click('[data-testid="manage-button"]'); + await page.click('[data-testid="delete-button"]'); + + await expect(page.locator('[role="dialog"]')).toBeVisible(); + await expect(page.locator('[data-testid="modal-header"]')).toContainText( + entityName + ); + + await page.fill('[data-testid="confirmation-text-input"]', 'DELETE'); + + const endpoint = isGlossaryTerm + ? '/api/v1/glossaryTerms/*' + : '/api/v1/glossaries/*'; + const deleteRes = page.waitForResponse(endpoint); + await page.click('[data-testid="confirm-button"]'); + await deleteRes; + + if (isGlossaryTerm) { + await toastNotification(page, /"Glossary Term" deleted successfully!/); + } else { + await toastNotification(page, /"Glossary" deleted successfully!/); + } +}; + +export const addSynonyms = async (page: Page, synonyms: string[]) => { + await page.getByTestId('synonym-add-button').click(); + await page.locator('.ant-select-selection-overflow').click(); + + for (const synonym of synonyms) { + await page.locator('#synonyms-select').fill(synonym); + await page.locator('#synonyms-select').press('Enter'); + } + + const saveRes = page.waitForResponse('/api/v1/glossaryTerms/*'); + await page.getByTestId('save-synonym-btn').click(); + await saveRes; + + for (const synonym of synonyms) { + await expect(page.getByTestId(synonym)).toBeVisible(); + } +}; + +export const addReferences = async ( + page: Page, + references: { name: string; url: string }[] +) => { + await page.getByTestId('term-references-add-button').click(); + + await expect( + page.getByTestId('glossary-term-references-modal').getByText('References') + ).toBeVisible(); + + for (const [index, value] of references.entries()) { + await page.locator(`#references_${index}_name`).fill(value.name); + await page.locator(`#references_${index}_endpoint`).fill(value.url); + if (index < references.length - 1) { + await page.getByTestId('add-references-button').click(); + } + } + const saveRes = page.waitForResponse('/api/v1/glossaryTerms/*'); + await page.getByTestId('save-btn').click(); + await saveRes; + + for (const reference of references) { + await expect( + page.getByTestId(`reference-link-${reference.name}`) + ).toBeVisible(); + } +}; + +export const addRelatedTerms = async ( + page: Page, + relatedTerms: GlossaryTerm[] +) => { + await page.getByTestId('related-term-add-button').click(); + for (const term of relatedTerms) { + const entityName = get(term, 'responseData.name'); + const entityFqn = get(term, 'responseData.fullyQualifiedName'); + await page.locator('#tagsForm_tags').fill(entityName); + await page.getByTestId(`tag-${entityFqn}`).click(); + } + + const saveRes = page.waitForResponse('/api/v1/glossaryTerms/*'); + await page.getByTestId('saveAssociatedTag').click(); + await saveRes; + + for (const term of relatedTerms) { + const entityName = get(term, 'responseData.displayName'); + + await expect(page.getByTestId(entityName)).toBeVisible(); + } +}; + +export const assignTagToGlossaryTerm = async ( + page: Page, + tag: string, + action: 'Add' | 'Edit' = 'Add', + parentTestId = 'entity-right-panel' +) => { + await page + .getByTestId(parentTestId) + .getByTestId('tags-container') + .getByTestId(action === 'Add' ? 'add-tag' : 'edit-button') + .click(); + + const searchTags = page.waitForResponse( + `/api/v1/search/query?q=*${encodeURIComponent(tag)}*` + ); + await page.locator('#tagsForm_tags').fill(tag); + await searchTags; + await page.getByTestId(`tag-${tag}`).click(); + + await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled(); + + await page.getByTestId('saveAssociatedTag').click(); + + await expect(page.getByRole('heading')).toContainText( + 'Would you like to proceed with updating the tags?' + ); + + const validateRes = page.waitForResponse('/api/v1/glossaryTerms/*'); + await page.getByRole('button', { name: 'Yes, confirm' }).click(); + await validateRes; + + await expect( + page + .getByTestId(parentTestId) + .getByTestId('tags-container') + .getByTestId(`tag-${tag}`) + ).toBeVisible(); +}; + +export const createDescriptionTaskForGlossary = async ( + page: Page, + value: TaskDetails, + entity: Glossary | GlossaryTerm, + isGlossary = true, + addDescription = true +) => { + const entityType = isGlossary ? 'glossary' : 'glossaryTerm'; + const entityName = get(entity, 'responseData.displayName'); + + expect(await page.locator('#title').inputValue()).toBe( + `${ + addDescription ? 'Update' : 'Request' + } description for ${entityType} ${entityName}` + ); + + if (isUndefined(value.assignee)) { + expect( + await page + .locator('[data-testid="select-assignee"] > .ant-select-selector') + .innerText() + ).toBe(value.assignee); + + expect( + await page + .locator('[data-testid="select-assignee"] > .ant-select-selector input') + .isDisabled() + ); + } else { + const assigneeField = page.locator( + '[data-testid="select-assignee"] > .ant-select-selector #assignees' + ); + await assigneeField.click(); + + const userSearchResponse = page.waitForResponse( + `/api/v1/search/suggest?q=${value.assignee}&index=user_search_index%2Cteam_search_index` + ); + await assigneeField.fill(value.assignee); + await userSearchResponse; + + // select value from dropdown + const dropdownValue = page.getByTestId(value.assignee); + await dropdownValue.hover(); + await dropdownValue.click(); + await clickOutside(page); + } + + if (addDescription) { + await page + .locator(descriptionBox) + .fill(value.description ?? 'Updated description'); + } + await page.click('button[type="submit"]'); + + await toastNotification(page, /Task created successfully./); +}; + +export const createTagTaskForGlossary = async ( + page: Page, + value: TaskDetails, + entity: Glossary | GlossaryTerm, + isGlossary = true, + addTag = true +) => { + const entityType = isGlossary ? 'glossary' : 'glossaryTerm'; + const entityName = get(entity, 'responseData.displayName'); + + expect(await page.locator('#title').inputValue()).toBe( + `Request tags for ${entityType} ${entityName}` + ); + + if (isUndefined(value.assignee)) { + expect( + await page + .locator('[data-testid="select-assignee"] > .ant-select-selector') + .innerText() + ).toBe(value.assignee); + + expect( + await page + .locator('[data-testid="select-assignee"] > .ant-select-selector input') + .isDisabled() + ); + } else { + // select assignee + const assigneeField = page.locator( + '[data-testid="select-assignee"] > .ant-select-selector #assignees' + ); + await assigneeField.click(); + const userSearchResponse = page.waitForResponse( + `/api/v1/search/suggest?q=${value.assignee}&index=user_search_index%2Cteam_search_index` + ); + await assigneeField.fill(value.assignee); + await userSearchResponse; + + // select value from dropdown + const dropdownValue = page.getByTestId(value.assignee); + await dropdownValue.hover(); + await dropdownValue.click(); + await clickOutside(page); + } + + if (addTag) { + // select tags + const suggestTags = page.locator( + '[data-testid="tag-selector"] > .ant-select-selector .ant-select-selection-search-input' + ); + await suggestTags.click(); + + const querySearchResponse = page.waitForResponse( + `/api/v1/search/query?q=*${value.tag}*&index=tag_search_index&*` + ); + await suggestTags.fill(value.tag ?? ''); + + await querySearchResponse; + + // select value from dropdown + const dropdownValue = page.getByTestId(`tag-${value.tag ?? ''}`); + await dropdownValue.hover(); + await dropdownValue.click(); + await clickOutside(page); + } + + await page.click('button[type="submit"]'); + + await toastNotification(page, /Task created successfully./); +}; + +export const approveTagsTask = async ( + page: Page, + value: TaskDetails, + entity: Glossary | GlossaryTerm +) => { + await redirectToHomePage(page); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, entity.data.displayName); + + await page.click('[data-testid="activity_feed"]'); + + const taskFeeds = page.waitForResponse(TASK_OPEN_FETCH_LINK); + await page + .getByTestId('global-setting-left-panel') + .getByText('Tasks') + .click(); + + await taskFeeds; + + const taskResolve = page.waitForResponse('/api/v1/feed/tasks/*/resolve'); + await page.click('.ant-btn-compact-first-item:has-text("Accept Suggestion")'); + await taskResolve; + + await redirectToHomePage(page); + await sidebarClick(page, SidebarItem.GLOSSARY); + await selectActiveGlossary(page, entity.data.displayName); + + const tagVisibility = await page.isVisible( + `[data-testid="tag-${value.tag}"]` + ); + + await expect(tagVisibility).toBe(true); +};