mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-27 18:36:08 +00:00
998 lines
29 KiB
TypeScript
998 lines
29 KiB
TypeScript
/*
|
||
* Copyright 2024 Collate.
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
import { expect, Page } from '@playwright/test';
|
||
import { get, isUndefined } from 'lodash';
|
||
import { SidebarItem } from '../constant/sidebar';
|
||
import { GLOSSARY_TERM_PATCH_PAYLOAD } from '../constant/version';
|
||
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';
|
||
import {
|
||
GlossaryData,
|
||
GlossaryTermData,
|
||
} 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 { TaskDetails, TASK_OPEN_FETCH_LINK } from './task';
|
||
|
||
export const descriptionBox =
|
||
'.toastui-editor-md-container > .toastui-editor > .ProseMirror';
|
||
|
||
export const checkDisplayName = async (page: Page, displayName: string) => {
|
||
await expect(page.getByTestId('entity-header-display-name')).toHaveText(
|
||
displayName
|
||
);
|
||
};
|
||
|
||
export const selectActiveGlossary = async (
|
||
page: Page,
|
||
glossaryName: string
|
||
) => {
|
||
const menuItem = page.getByRole('menuitem', { name: glossaryName });
|
||
const isSelected = await menuItem.evaluate((element) => {
|
||
return element.classList.contains('ant-menu-item-selected');
|
||
});
|
||
|
||
if (!isSelected) {
|
||
const glossaryResponse = page.waitForResponse('/api/v1/glossaryTerms*');
|
||
await menuItem.click();
|
||
await glossaryResponse;
|
||
}
|
||
};
|
||
|
||
export const selectActiveGlossaryTerm = async (
|
||
page: Page,
|
||
glossaryTermName: string
|
||
) => {
|
||
await page.getByTestId(glossaryTermName).click();
|
||
|
||
await expect(
|
||
page.locator('[data-testid="entity-header-display-name"]')
|
||
).toContainText(glossaryTermName);
|
||
};
|
||
|
||
export const goToAssetsTab = async (
|
||
page: Page,
|
||
displayName: string,
|
||
count = 0
|
||
) => {
|
||
await selectActiveGlossaryTerm(page, displayName);
|
||
await page.getByTestId('assets').click();
|
||
await page.waitForSelector('.ant-tabs-tab-active:has-text("Assets")');
|
||
|
||
await expect(
|
||
page.getByTestId('assets').getByTestId('filter-count')
|
||
).toContainText(`${count}`);
|
||
};
|
||
|
||
export const removeReviewer = async (
|
||
page: Page,
|
||
endpoint: EntityTypeEndpoint
|
||
) => {
|
||
const patchResponse = page.waitForResponse(`/api/v1/${endpoint}/*`);
|
||
|
||
await page.click('[data-testid="edit-reviewer-button"]');
|
||
|
||
await page.click('[data-testid="clear-all-button"]');
|
||
|
||
await page.click('[data-testid="selectable-list-update-btn"]');
|
||
|
||
await patchResponse;
|
||
|
||
await expect(
|
||
page.locator('[data-testid="glossary-reviewer"] [data-testid="Add"]')
|
||
).toBeVisible();
|
||
};
|
||
|
||
// Create a glossary and two glossary terms, then link them with a related term relationship
|
||
export const setupGlossaryAndTerms = async (page: Page) => {
|
||
const glossary = new Glossary();
|
||
const term1 = new GlossaryTerm(glossary);
|
||
const term2 = new GlossaryTerm(glossary);
|
||
|
||
// Get API context for performing operations
|
||
const { apiContext, afterAction } = await getApiContext(page);
|
||
|
||
// Create glossary and terms
|
||
await glossary.create(apiContext);
|
||
await term1.create(apiContext);
|
||
await term2.create(apiContext);
|
||
|
||
// Prepare the payload for linking term2 as a related term to term1
|
||
const relatedTermLink = {
|
||
op: 'add',
|
||
path: '/relatedTerms/0',
|
||
value: {
|
||
id: term1.responseData.id,
|
||
type: 'glossaryTerm',
|
||
displayName: term1.responseData.displayName,
|
||
name: term1.responseData.name,
|
||
},
|
||
};
|
||
|
||
// Update term2 to include term1 as a related term
|
||
await term2.patch(apiContext, [
|
||
...GLOSSARY_TERM_PATCH_PAYLOAD,
|
||
relatedTermLink,
|
||
]);
|
||
|
||
const cleanup = async () => {
|
||
await glossary.delete(apiContext);
|
||
await afterAction();
|
||
};
|
||
|
||
return { glossary, term1, term2, cleanup };
|
||
};
|
||
|
||
export const validateForm = async (page) => {
|
||
// Error messages
|
||
await expect(page.locator('#name_help')).toHaveText('Name is required');
|
||
await expect(page.locator('#description_help')).toHaveText(
|
||
'Description is required'
|
||
);
|
||
|
||
// Max length validation
|
||
await page.locator('[data-testid="name"]').type(INVALID_NAMES.MAX_LENGTH);
|
||
|
||
await expect(page.locator('#name_help')).toHaveText(
|
||
NAME_MAX_LENGTH_VALIDATION_ERROR
|
||
);
|
||
|
||
// With special char validation
|
||
await page.locator('[data-testid="name"]').clear();
|
||
await page
|
||
.locator('[data-testid="name"]')
|
||
.type(INVALID_NAMES.WITH_SPECIAL_CHARS);
|
||
|
||
await expect(page.locator('#name_help')).toHaveText(NAME_VALIDATION_ERROR);
|
||
};
|
||
|
||
export const addTeamAsReviewer = async (
|
||
page: Page,
|
||
teamName: string,
|
||
activatorBtnDataTestId: string,
|
||
dataTestId?: string,
|
||
isSelectableInsideForm = false
|
||
) => {
|
||
const teamsResponse = page.waitForResponse(
|
||
'/api/v1/search/query?q=*&from=0&size=*&index=team_search_index&sort_field=displayName.keyword&sort_order=asc'
|
||
);
|
||
|
||
const teamsSearchResponse = page.waitForResponse(
|
||
`api/v1/search/query?q=*${encodeURI(teamName)}*`
|
||
);
|
||
|
||
await page.click(`[data-testid="${activatorBtnDataTestId}"]`);
|
||
|
||
await expect(page.locator("[data-testid='select-owner-tabs']")).toBeVisible();
|
||
|
||
await teamsResponse;
|
||
|
||
await page.fill('[data-testid="owner-select-teams-search-bar"]', teamName);
|
||
await teamsSearchResponse;
|
||
|
||
const ownerItem = page.locator(`.ant-popover [title="${teamName}"]`);
|
||
|
||
if (isSelectableInsideForm) {
|
||
await ownerItem.click();
|
||
} else {
|
||
const patchRequest = page.waitForRequest(
|
||
(request) => request.method() === 'PATCH'
|
||
);
|
||
await ownerItem.click();
|
||
await patchRequest;
|
||
}
|
||
|
||
await expect(
|
||
page.locator(`[data-testid=${dataTestId ?? 'owner-link'}]`)
|
||
).toContainText(teamName);
|
||
};
|
||
|
||
export const createGlossary = async (
|
||
page: Page,
|
||
glossaryData: GlossaryData,
|
||
bValidateForm: boolean
|
||
) => {
|
||
// Click on the "Add Glossary" button
|
||
await page.click('[data-testid="add-glossary"]');
|
||
|
||
// Validate redirection to the add glossary page
|
||
await page.waitForSelector('[data-testid="form-heading"]');
|
||
|
||
await expect(page.locator('[data-testid="form-heading"]')).toHaveText(
|
||
'Add Glossary'
|
||
);
|
||
|
||
// Perform glossary creation steps
|
||
await page.click('[data-testid="save-glossary"]');
|
||
|
||
if (bValidateForm) {
|
||
await validateForm(page);
|
||
}
|
||
|
||
await page.fill('[data-testid="name"]', glossaryData.name);
|
||
|
||
await page.fill(descriptionBox, glossaryData.description);
|
||
|
||
await expect(
|
||
page.locator('[data-testid="form-item-alert"]')
|
||
).not.toBeVisible();
|
||
|
||
if (glossaryData.mutuallyExclusive) {
|
||
await page.click('[data-testid="mutually-exclusive-button"]');
|
||
|
||
await expect(page.locator('[data-testid="form-item-alert"]')).toBeVisible();
|
||
}
|
||
|
||
if (glossaryData.tags && glossaryData.tags.length > 0) {
|
||
const tagsResponse = page.waitForResponse('/api/v1/search/query');
|
||
|
||
// Add tag
|
||
await page.click('[data-testid="tag-selector"]');
|
||
await page.fill(
|
||
'[data-testid="tag-selector"] input[type="search"]',
|
||
glossaryData.tags[0]
|
||
);
|
||
await tagsResponse;
|
||
await page.click(`[data-testid="tag-${glossaryData.tags[0]}"]`);
|
||
await page.click('[data-testid="right-panel"]');
|
||
}
|
||
|
||
if (glossaryData.reviewers.length > 0) {
|
||
// Add reviewer
|
||
if (glossaryData.reviewers[0].type === 'user') {
|
||
await addMultiOwner({
|
||
page,
|
||
ownerNames: glossaryData.reviewers.map((reviewer) => reviewer.name),
|
||
activatorBtnDataTestId: 'add-reviewers',
|
||
resultTestId: 'reviewers-container',
|
||
endpoint: EntityTypeEndpoint.Glossary,
|
||
isSelectableInsideForm: true,
|
||
type: 'Users',
|
||
});
|
||
} else {
|
||
await addTeamAsReviewer(
|
||
page,
|
||
glossaryData.reviewers[0].name,
|
||
'add-reviewers',
|
||
'reviewers-container',
|
||
true
|
||
);
|
||
}
|
||
}
|
||
|
||
const glossaryResponse = page.waitForResponse('/api/v1/glossaries');
|
||
await page.click('[data-testid="save-glossary"]');
|
||
await glossaryResponse;
|
||
|
||
await expect(page).toHaveURL(/\/glossary\//);
|
||
|
||
await checkDisplayName(page, glossaryData.name);
|
||
};
|
||
|
||
export const verifyGlossaryDetails = async (
|
||
page: Page,
|
||
glossaryDetails: GlossaryData
|
||
) => {
|
||
await page
|
||
.getByRole('menuitem', { name: glossaryDetails.name })
|
||
.locator('span')
|
||
.click();
|
||
|
||
await checkDisplayName(page, glossaryDetails.name);
|
||
|
||
const viewerContainerText = await page.textContent(
|
||
'[data-testid="viewer-container"]'
|
||
);
|
||
|
||
await expect(viewerContainerText).toContain(glossaryDetails.description);
|
||
|
||
// Owner
|
||
if (glossaryDetails.owners.length > 0) {
|
||
for (const owner of glossaryDetails.owners) {
|
||
await expect(
|
||
page
|
||
.getByTestId('glossary-right-panel-owner-link')
|
||
.getByTestId('owner-label')
|
||
).toContainText(owner.name);
|
||
}
|
||
}
|
||
|
||
// Reviewer
|
||
if (glossaryDetails.reviewers.length > 0) {
|
||
for (const reviewer of glossaryDetails.reviewers) {
|
||
await expect(
|
||
page.getByTestId('glossary-reviewer').getByTestId('owner-link')
|
||
).toContainText(reviewer.name);
|
||
}
|
||
}
|
||
|
||
// Tags
|
||
if (glossaryDetails.tags && glossaryDetails.tags.length > 0) {
|
||
const tagVisibility = await page.isVisible(
|
||
`[data-testid="tag-${glossaryDetails.tags[0]}"]`
|
||
);
|
||
|
||
await expect(tagVisibility).toBe(true);
|
||
}
|
||
};
|
||
|
||
export const deleteGlossary = async (page: Page, glossary: GlossaryData) => {
|
||
await page
|
||
.getByRole('menuitem', { name: glossary.displayName })
|
||
.locator('span')
|
||
.click();
|
||
|
||
await page.click('[data-testid="manage-button"]');
|
||
await page.click('[data-testid="delete-button"]');
|
||
|
||
await page.waitForSelector('[data-testid="delete-confirmation-modal"]');
|
||
|
||
await expect(page.locator('[role="dialog"]')).toBeVisible();
|
||
await expect(page.locator('[data-testid="modal-header"]')).toBeVisible();
|
||
|
||
await expect(page.locator('[data-testid="modal-header"]')).toContainText(
|
||
glossary.displayName
|
||
);
|
||
|
||
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
|
||
|
||
const deleteGlossary = page.waitForResponse(
|
||
(response) =>
|
||
response.url().includes('/api/v1/glossaries/') &&
|
||
response.request().method() === 'DELETE' &&
|
||
response.status() === 200
|
||
);
|
||
|
||
await page.click('[data-testid="confirm-button"]');
|
||
|
||
// Wait for the API response and verify the status code
|
||
await deleteGlossary;
|
||
|
||
// Display toast notification
|
||
await expect(page.locator('.toast-notification')).toHaveText(
|
||
'"Glossary" deleted successfully!'
|
||
);
|
||
};
|
||
|
||
export const fillGlossaryTermDetails = async (
|
||
page: Page,
|
||
term: GlossaryTermData,
|
||
validateCreateForm = true
|
||
) => {
|
||
await page.click('[data-testid="add-new-tag-button-header"]');
|
||
|
||
await page.waitForSelector('[role="dialog"].edit-glossary-modal');
|
||
|
||
await expect(
|
||
page.locator('[role="dialog"].edit-glossary-modal')
|
||
).toBeVisible();
|
||
await expect(page.locator('.ant-modal-title')).toContainText(
|
||
'Add Glossary Term'
|
||
);
|
||
|
||
// Validation should work
|
||
await page.click('[data-testid="save-glossary-term"]');
|
||
|
||
if (validateCreateForm) {
|
||
await validateForm(page);
|
||
}
|
||
|
||
await expect(page.locator('[data-testid="name"]')).toBeVisible();
|
||
|
||
await page.locator('[data-testid="name"]').fill(term.name);
|
||
|
||
await expect(page.locator(descriptionBox)).toBeVisible();
|
||
|
||
await page.locator(descriptionBox).fill(term.description);
|
||
|
||
const synonyms = (term.synonyms ?? '').split(',');
|
||
|
||
await expect(page.locator('[data-testid="synonyms"]')).toBeVisible();
|
||
|
||
for (const synonym of synonyms) {
|
||
if (synonym === '') {
|
||
continue;
|
||
}
|
||
await page
|
||
.locator('[data-testid="synonyms"] input[type="search"]')
|
||
.fill(`${synonym}`);
|
||
await page
|
||
.locator('[data-testid="synonyms"] input[type="search"]')
|
||
.press('Enter');
|
||
}
|
||
|
||
await expect(
|
||
page.locator('[data-testid="form-item-alert"]')
|
||
).not.toBeVisible();
|
||
|
||
if (term.mutuallyExclusive) {
|
||
await page.click('[data-testid="mutually-exclusive-button"]');
|
||
|
||
await expect(page.locator('[data-testid="form-item-alert"]')).toBeVisible();
|
||
}
|
||
|
||
await expect(page.locator('[data-testid="add-reference"]')).toBeVisible();
|
||
|
||
await page.click('[data-testid="add-reference"]');
|
||
|
||
await expect(page.locator('#name-0')).toBeVisible();
|
||
|
||
await page.locator('#name-0').fill('test');
|
||
|
||
await expect(page.locator('#url-0')).toBeVisible();
|
||
|
||
await page.locator('#url-0').fill('https://test.com');
|
||
|
||
if (term.icon) {
|
||
await page.locator('[data-testid="icon-url"]').fill(term.icon);
|
||
}
|
||
|
||
if (term.color) {
|
||
await page.locator('[data-testid="color-color-input"]').fill(term.color);
|
||
}
|
||
|
||
if (!isUndefined(term.owners)) {
|
||
await addMultiOwner({
|
||
page,
|
||
ownerNames: term.owners.map((owner) => owner.name),
|
||
activatorBtnDataTestId: 'add-owner',
|
||
resultTestId: 'owner-container',
|
||
endpoint: EntityTypeEndpoint.GlossaryTerm,
|
||
isSelectableInsideForm: true,
|
||
type: 'Users',
|
||
});
|
||
}
|
||
};
|
||
|
||
const validateGlossaryTermTask = async (page: Page, term: GlossaryTermData) => {
|
||
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 taskFeedCards = page.locator('[data-testid="task-feed-card"]');
|
||
|
||
// Filter to find the specific card that contains the text
|
||
const cardWithText = taskFeedCards.filter({
|
||
has: page.locator('[data-testid="entity-link"]', {
|
||
hasText: term.name,
|
||
}),
|
||
});
|
||
|
||
await expect(cardWithText).toHaveCount(1);
|
||
};
|
||
|
||
export const approveGlossaryTermTask = async (
|
||
page: Page,
|
||
term: GlossaryTermData
|
||
) => {
|
||
await validateGlossaryTermTask(page, term);
|
||
const taskResolve = page.waitForResponse('/api/v1/feed/tasks/*/resolve');
|
||
await page.getByRole('button', { name: 'Approve' }).click();
|
||
await taskResolve;
|
||
|
||
// Display toast notification
|
||
await expect(page.locator('.Toastify__toast-body')).toHaveText(
|
||
/Task resolved successfully/
|
||
);
|
||
};
|
||
|
||
export const validateGlossaryTerm = async (
|
||
page: Page,
|
||
term: GlossaryTermData,
|
||
status: 'Draft' | 'Approved'
|
||
) => {
|
||
// eslint-disable-next-line no-useless-escape
|
||
const escapedFqn = term.fullyQualifiedName.replace(/\"/g, '\\"');
|
||
const termSelector = `[data-row-key="${escapedFqn}"]`;
|
||
const statusSelector = `[data-testid="${escapedFqn}-status"]`;
|
||
|
||
await expect(page.locator(termSelector)).toContainText(term.name);
|
||
await expect(page.locator(statusSelector)).toContainText(status);
|
||
|
||
if (status === 'Draft') {
|
||
await validateGlossaryTermTask(page, term);
|
||
await page.click('[data-testid="terms"]');
|
||
}
|
||
};
|
||
|
||
export const createGlossaryTerm = async (
|
||
page: Page,
|
||
term: GlossaryTermData,
|
||
status: 'Draft' | 'Approved',
|
||
validateCreateForm = true
|
||
) => {
|
||
await fillGlossaryTermDetails(page, term, validateCreateForm);
|
||
const glossaryTermResponse = page.waitForResponse('/api/v1/glossaryTerms');
|
||
await page.click('[data-testid="save-glossary-term"]');
|
||
await glossaryTermResponse;
|
||
await validateGlossaryTerm(page, term, status);
|
||
};
|
||
|
||
export const createGlossaryTerms = async (
|
||
page: Page,
|
||
glossary: GlossaryData
|
||
) => {
|
||
await selectActiveGlossary(page, glossary.name);
|
||
|
||
const termStatus = glossary.reviewers.length > 0 ? 'Draft' : 'Approved';
|
||
|
||
for (const term of glossary.terms) {
|
||
await createGlossaryTerm(page, term.data, termStatus, false);
|
||
}
|
||
};
|
||
|
||
export const checkAssetsCount = async (page: Page, assetsCount: number) => {
|
||
await expect(
|
||
page.locator('[data-testid="assets"] [data-testid="filter-count"]')
|
||
).toHaveText(assetsCount.toString());
|
||
};
|
||
|
||
export const addAssetToGlossaryTerm = async (
|
||
page: Page,
|
||
assets: (TableClass | TopicClass | DashboardClass)[],
|
||
hasExistingAssets = false
|
||
) => {
|
||
if (!hasExistingAssets) {
|
||
await page.waitForSelector(
|
||
'text=Adding a new Asset is easy, just give it a spin!'
|
||
);
|
||
}
|
||
|
||
await page.click('[data-testid="glossary-term-add-button-menu"]');
|
||
await page.getByRole('menuitem', { name: 'Assets' }).click();
|
||
|
||
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
|
||
await expect(
|
||
page.locator('[data-testid="asset-selection-modal"] .ant-modal-title')
|
||
).toContainText('Add Assets');
|
||
|
||
for (const asset of assets) {
|
||
const entityFqn = get(asset, 'entityResponseData.fullyQualifiedName');
|
||
const entityName = get(asset, 'entityResponseData.name');
|
||
const searchRes = page.waitForResponse('/api/v1/search/query*');
|
||
|
||
await page
|
||
.locator(
|
||
'[data-testid="asset-selection-modal"] [data-testid="searchbar"]'
|
||
)
|
||
.fill(entityName);
|
||
|
||
await searchRes;
|
||
await page.click(
|
||
`[data-testid="table-data-card_${entityFqn}"] input[type="checkbox"]`
|
||
);
|
||
}
|
||
|
||
await page.click('[data-testid="save-btn"]');
|
||
await checkAssetsCount(page, assets.length);
|
||
};
|
||
|
||
export const updateNameForGlossaryTerm = async (
|
||
page: Page,
|
||
name: string,
|
||
endPoint: string
|
||
) => {
|
||
await page.click('[data-testid="manage-button"]');
|
||
await page.click('[data-testid="rename-button"]');
|
||
|
||
await expect(page.locator('#name')).toBeVisible();
|
||
|
||
await page.fill('#name', name);
|
||
const updateNameResponsePromise = page.waitForResponse(
|
||
`/api/v1/${endPoint}/*`
|
||
);
|
||
await page.click('[data-testid="save-button"]');
|
||
const updateNameResponse = await updateNameResponsePromise;
|
||
const data = await updateNameResponse.json();
|
||
|
||
await expect(page.locator('[data-testid="entity-header-name"]')).toHaveText(
|
||
name
|
||
);
|
||
|
||
return data;
|
||
};
|
||
|
||
export const verifyGlossaryTermAssets = async (
|
||
page: Page,
|
||
glossary: GlossaryData,
|
||
glossaryTermData: GlossaryTermData,
|
||
assetsLength: number
|
||
) => {
|
||
await page.click('[data-testid="overview"]');
|
||
await redirectToHomePage(page);
|
||
await sidebarClick(page, SidebarItem.GLOSSARY);
|
||
await selectActiveGlossary(page, glossary.displayName);
|
||
await goToAssetsTab(page, glossaryTermData.displayName, assetsLength);
|
||
};
|
||
|
||
export const renameGlossaryTerm = async (
|
||
page: Page,
|
||
glossaryTerm: GlossaryTerm,
|
||
glossaryNewName: string
|
||
) => {
|
||
const data = await updateNameForGlossaryTerm(
|
||
page,
|
||
glossaryNewName,
|
||
EntityTypeEndpoint.GlossaryTerm
|
||
);
|
||
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);
|
||
};
|