mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-02 10:36:29 +00:00
test(ui): e2e tests for landing page (#22678)
* add customise landing page tests * add recently veiwed assets test * fix unit tests * remove lint errors * add tests for filter, add, remove widgets * add curated assets test * minor fix * fix flaky tests * fix tests * fix tasks test * minor fix * address pr comments
This commit is contained in:
parent
8e58620f58
commit
ebcfd95164
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2025 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, test as base } from '@playwright/test';
|
||||
import { PersonaClass } from '../../support/persona/PersonaClass';
|
||||
import { UserClass } from '../../support/user/UserClass';
|
||||
import { performAdminLogin } from '../../utils/admin';
|
||||
import { selectOption } from '../../utils/advancedSearch';
|
||||
import { redirectToHomePage } from '../../utils/common';
|
||||
import {
|
||||
addCuratedAssetPlaceholder,
|
||||
saveCustomizeLayoutPage,
|
||||
setUserDefaultPersona,
|
||||
} from '../../utils/customizeLandingPage';
|
||||
|
||||
const adminUser = new UserClass();
|
||||
const persona = new PersonaClass();
|
||||
|
||||
const test = base.extend<{ page: Page }>({
|
||||
page: async ({ browser }, use) => {
|
||||
const page = await browser.newPage();
|
||||
await adminUser.login(page);
|
||||
await use(page);
|
||||
await page.close();
|
||||
},
|
||||
});
|
||||
|
||||
base.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||
await adminUser.create(apiContext);
|
||||
await adminUser.setAdminRole(apiContext);
|
||||
await persona.create(apiContext, [adminUser.responseData.id]);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
base.afterAll('Cleanup', async ({ browser }) => {
|
||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||
await adminUser.delete(apiContext);
|
||||
await persona.delete(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.describe('Curated Assets', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('sidebar-toggle').click();
|
||||
await setUserDefaultPersona(page, persona.responseData.displayName);
|
||||
});
|
||||
|
||||
test('Create Curated Asset', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await addCuratedAssetPlaceholder({
|
||||
page,
|
||||
personaName: persona.responseData.name,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByTestId('KnowledgePanel.CuratedAssets')
|
||||
.getByText('Create')
|
||||
.click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
|
||||
|
||||
await page.waitForSelector('[data-testid="title-input"]');
|
||||
|
||||
await page.locator('[data-testid="title-input"]').fill('Popular Charts');
|
||||
|
||||
await page.locator('[data-testid="asset-type-select"]').click();
|
||||
|
||||
await page.locator('[data-testid="chart-option"]').click();
|
||||
|
||||
const ruleLocator = page.locator('.rule').nth(0);
|
||||
await selectOption(
|
||||
page,
|
||||
ruleLocator.locator('.rule--field .ant-select'),
|
||||
'Owners'
|
||||
);
|
||||
|
||||
await selectOption(
|
||||
page,
|
||||
ruleLocator.locator('.rule--operator .ant-select'),
|
||||
'!='
|
||||
);
|
||||
|
||||
await selectOption(
|
||||
page,
|
||||
ruleLocator.locator('.rule--value .ant-select'),
|
||||
'admin'
|
||||
);
|
||||
|
||||
await page.getByRole('button', { name: 'Add Condition' }).click();
|
||||
|
||||
const ruleLocator2 = page.locator('.rule').nth(1);
|
||||
await selectOption(
|
||||
page,
|
||||
ruleLocator2.locator('.rule--field .ant-select'),
|
||||
'Description'
|
||||
);
|
||||
|
||||
await selectOption(
|
||||
page,
|
||||
ruleLocator2.locator('.rule--operator .ant-select'),
|
||||
'=='
|
||||
);
|
||||
|
||||
await selectOption(
|
||||
page,
|
||||
ruleLocator2.locator('.rule--value .ant-select'),
|
||||
'Complete'
|
||||
);
|
||||
|
||||
await expect(page.locator('[data-testid="saveButton"]')).toBeEnabled();
|
||||
|
||||
const queryResponse = page.waitForResponse(
|
||||
'/api/v1/search/query?q=&index=chart&*'
|
||||
);
|
||||
|
||||
await page.locator('[data-testid="saveButton"]').click();
|
||||
await queryResponse;
|
||||
|
||||
await expect(
|
||||
page.locator('[data-testid="KnowledgePanel.CuratedAssets"]')
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByTestId('KnowledgePanel.CuratedAssets')
|
||||
.getByText('Popular Charts')
|
||||
).toBeVisible();
|
||||
|
||||
await saveCustomizeLayoutPage(page);
|
||||
|
||||
const chartResponsePromise = page.waitForResponse((response) => {
|
||||
const url = response.url();
|
||||
|
||||
return (
|
||||
url.includes('/api/v1/search/query') &&
|
||||
url.includes('index=chart') &&
|
||||
response.status() === 200
|
||||
);
|
||||
});
|
||||
|
||||
await redirectToHomePage(page);
|
||||
|
||||
const chartResponse = await chartResponsePromise;
|
||||
|
||||
expect(chartResponse.status()).toBe(200);
|
||||
|
||||
const chartResponseJson = await chartResponse.json();
|
||||
const chartHits = chartResponseJson.hits.hits;
|
||||
|
||||
if (chartHits.length > 0) {
|
||||
const firstSource = chartHits[0]._source;
|
||||
const displayName = firstSource.displayName;
|
||||
const name = firstSource.name;
|
||||
|
||||
await expect(
|
||||
page.locator(`[data-testid="Curated Assets-${displayName ?? name}"]`)
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByTestId('KnowledgePanel.CuratedAssets')
|
||||
.getByText('Popular Charts')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('Curated Asset placeholder is not available in home page', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.slow(true);
|
||||
|
||||
await addCuratedAssetPlaceholder({
|
||||
page,
|
||||
personaName: persona.responseData.name,
|
||||
});
|
||||
|
||||
await page.locator('[data-testid="save-button"]').click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await redirectToHomePage(page);
|
||||
|
||||
await expect(
|
||||
page.locator('[data-testid="KnowledgePanel.CuratedAssets"]')
|
||||
).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
@ -42,7 +42,7 @@ const entities = [
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
test.describe.skip('Recently viewed data assets', () => {
|
||||
test.describe('Recently viewed data assets', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await redirectToHomePage(page);
|
||||
});
|
||||
@ -63,30 +63,38 @@ test.describe.skip('Recently viewed data assets', () => {
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test('Recently viewed widget should be visible on the home page', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.slow(true);
|
||||
for (const entity of entities) {
|
||||
test(`Check ${entity.getType()} in recently viewed`, async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
for await (const entity of entities) {
|
||||
await test.step(
|
||||
`Check ${entity.getType()} in recently viewed widget `,
|
||||
async () => {
|
||||
await entity.visitEntityPage(page);
|
||||
await entity.visitEntityPage(page);
|
||||
|
||||
await page.waitForSelector(`[data-testid="breadcrumb"]`);
|
||||
await page.waitForSelector('[data-testid="breadcrumb"]');
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await redirectToHomePage(page);
|
||||
|
||||
await page.waitForSelector(`[data-testid="recently-viewed-widget"]`);
|
||||
const entityName = getEntityDisplayName(entity.entity);
|
||||
|
||||
const selector = `[data-testid="recently-viewed-widget"] [title="${getEntityDisplayName(
|
||||
entity.entity
|
||||
)}"]`;
|
||||
await expect(
|
||||
page.getByTestId('recently-viewed-asset').getByText(entityName)
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.locator(selector)).toBeVisible();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
await page
|
||||
.getByTestId('recently-viewed-asset')
|
||||
.getByText(entityName)
|
||||
.click();
|
||||
|
||||
await page.waitForSelector('[data-testid="breadcrumb"]');
|
||||
|
||||
if ((await page.getByTestId('entity-header-display-name').count()) > 0) {
|
||||
await expect(
|
||||
page.getByTestId('entity-header-display-name')
|
||||
).toContainText(entityName);
|
||||
} else {
|
||||
await expect(page.getByTestId('entity-header-name')).toContainText(
|
||||
entityName
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -55,293 +55,210 @@ base.afterAll('Cleanup', async ({ browser }) => {
|
||||
});
|
||||
|
||||
test.describe('Customize Landing Page Flow', () => {
|
||||
test.fixme('Check all default widget present', async ({ adminPage }) => {
|
||||
test('Check all default widget present', async ({ adminPage }) => {
|
||||
await redirectToHomePage(adminPage);
|
||||
await adminPage.getByTestId('welcome-screen-close-btn').click();
|
||||
await checkAllDefaultWidgets(adminPage);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'Add,Remove and Reset widget should work properly',
|
||||
async ({ adminPage }) => {
|
||||
await redirectToHomePage(adminPage);
|
||||
await setUserDefaultPersona(adminPage, persona.responseData.displayName);
|
||||
test('Add, Remove and Reset widget should work properly', async ({
|
||||
adminPage,
|
||||
}) => {
|
||||
test.slow(true);
|
||||
|
||||
await test.step('Remove widget', async () => {
|
||||
await navigateToCustomizeLandingPage(adminPage, {
|
||||
personaName: persona.responseData.name,
|
||||
customPageDataResponse: 404,
|
||||
});
|
||||
await redirectToHomePage(adminPage);
|
||||
await setUserDefaultPersona(adminPage, persona.responseData.displayName);
|
||||
|
||||
await removeAndCheckWidget(adminPage, {
|
||||
widgetTestId: 'activity-feed-widget',
|
||||
widgetKey: 'KnowledgePanel.ActivityFeed',
|
||||
});
|
||||
await removeAndCheckWidget(adminPage, {
|
||||
widgetTestId: 'following-widget',
|
||||
widgetKey: 'KnowledgePanel.Following',
|
||||
});
|
||||
await removeAndCheckWidget(adminPage, {
|
||||
widgetTestId: 'kpi-widget',
|
||||
widgetKey: 'KnowledgePanel.KPI',
|
||||
});
|
||||
await test.step('Remove widget', async () => {
|
||||
test.slow(true);
|
||||
|
||||
await saveCustomizeLayoutPage(adminPage, true);
|
||||
|
||||
await redirectToHomePage(adminPage);
|
||||
|
||||
// Check if removed widgets are not present on landing adminPage
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="activity-feed-widget"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="following-widget"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="kpi-widget"]')
|
||||
).not.toBeVisible();
|
||||
await navigateToCustomizeLandingPage(adminPage, {
|
||||
personaName: persona.responseData.name,
|
||||
});
|
||||
|
||||
await test.step('Add widget', async () => {
|
||||
await removeAndCheckWidget(adminPage, {
|
||||
widgetKey: 'KnowledgePanel.ActivityFeed',
|
||||
});
|
||||
await removeAndCheckWidget(adminPage, {
|
||||
widgetKey: 'KnowledgePanel.Following',
|
||||
});
|
||||
await removeAndCheckWidget(adminPage, {
|
||||
widgetKey: 'KnowledgePanel.KPI',
|
||||
});
|
||||
|
||||
await saveCustomizeLayoutPage(adminPage, true);
|
||||
|
||||
await redirectToHomePage(adminPage);
|
||||
|
||||
// Check if removed widgets are not present on landing adminPage
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.ActivityFeed"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.Following"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.KPI"]')
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Add widget', async () => {
|
||||
test.slow(true);
|
||||
|
||||
await navigateToCustomizeLandingPage(adminPage, {
|
||||
personaName: persona.responseData.name,
|
||||
});
|
||||
|
||||
// Check if removed widgets are not present on customize page
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.ActivityFeed"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.Following"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.KPI"]')
|
||||
).not.toBeVisible();
|
||||
|
||||
// Check if other widgets are present
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.MyData"]')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.TotalAssets"]')
|
||||
).toBeVisible();
|
||||
|
||||
await openAddCustomizeWidgetModal(adminPage);
|
||||
|
||||
await adminPage.locator('[data-testid="loader"]').waitFor({
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
// Check if 'check' icon is present for existing widgets
|
||||
await expect(
|
||||
adminPage
|
||||
.locator('[data-testid="sidebar-option-KnowledgePanel.MyData"]')
|
||||
.locator('.selected-widget-icon')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
adminPage
|
||||
.locator('[data-testid="sidebar-option-KnowledgePanel.TotalAssets"]')
|
||||
.locator('.selected-widget-icon')
|
||||
).toBeVisible();
|
||||
|
||||
// Check if 'check' icon is not present for removed widgets
|
||||
await expect(
|
||||
adminPage
|
||||
.locator('[data-testid="sidebar-option-KnowledgePanel.ActivityFeed"]')
|
||||
.locator('.selected-widget-icon')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage
|
||||
.locator('[data-testid="sidebar-option-KnowledgePanel.Following"]')
|
||||
.locator('.selected-widget-icon')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage
|
||||
.locator('[data-testid="sidebar-option-KnowledgePanel.KPI"]')
|
||||
.locator('.selected-widget-icon')
|
||||
).not.toBeVisible();
|
||||
|
||||
// Add Following widget
|
||||
await adminPage
|
||||
.locator('[data-testid="KnowledgePanel.Following"]')
|
||||
.click();
|
||||
|
||||
await adminPage.locator('[data-testid="apply-btn"]').click();
|
||||
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KnowledgePanel.Following"]')
|
||||
).toBeVisible();
|
||||
|
||||
// Check if check icons are present in tab labels for newly added widgets
|
||||
await openAddCustomizeWidgetModal(adminPage);
|
||||
|
||||
// Check if 'check' icon is present for the Following widget
|
||||
await expect(
|
||||
adminPage
|
||||
.locator('[data-testid="sidebar-option-KnowledgePanel.Following"]')
|
||||
.locator('.selected-widget-icon')
|
||||
).toBeVisible();
|
||||
|
||||
// Close the add widget modal
|
||||
await adminPage.locator('[data-testid="cancel-btn"]').click();
|
||||
|
||||
// Save the updated layout
|
||||
await saveCustomizeLayoutPage(adminPage);
|
||||
|
||||
// Navigate to the landing page
|
||||
await redirectToHomePage(adminPage);
|
||||
await adminPage.waitForLoadState('networkidle');
|
||||
|
||||
// Check if removed widgets are not present on the landing page
|
||||
await expect(
|
||||
adminPage.getByTestId('KnowledgePanel.ActivityFeed')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.getByTestId('KnowledgePanel.KPI')
|
||||
).not.toBeVisible();
|
||||
|
||||
// Check if newly added widgets are present on the landing page
|
||||
await expect(
|
||||
adminPage.getByTestId('KnowledgePanel.Following')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Resetting the layout flow should work properly',
|
||||
async () => {
|
||||
test.slow(true);
|
||||
|
||||
// Check if removed widgets are not present on landing page
|
||||
await expect(
|
||||
adminPage.getByTestId('KnowledgePanel.ActivityFeed')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.getByTestId('KnowledgePanel.KPI')
|
||||
).not.toBeVisible();
|
||||
|
||||
await navigateToCustomizeLandingPage(adminPage, {
|
||||
personaName: persona.responseData.name,
|
||||
customPageDataResponse: 200,
|
||||
});
|
||||
|
||||
// Check if removed widgets are not present on customize page
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="activity-feed-widget"]')
|
||||
adminPage.locator('[data-testid="KnowledgePanel.ActivityFeed"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="following-widget"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="kpi-widget"]')
|
||||
adminPage.locator('[data-testid="KnowledgePanel.KPI"]')
|
||||
).not.toBeVisible();
|
||||
|
||||
// Check if other widgets are present
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="recently-viewed-widget"]')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="my-data-widget"]')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="total-assets-widget"]')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator(
|
||||
'[data-testid="ExtraWidget.EmptyWidgetPlaceholder"]'
|
||||
)
|
||||
).toBeVisible();
|
||||
await adminPage.locator('[data-testid="reset-button"]').click();
|
||||
|
||||
await openAddCustomizeWidgetModal(adminPage);
|
||||
// Confirm reset in modal
|
||||
const resetResponse = adminPage.waitForResponse('/api/v1/docStore/*');
|
||||
|
||||
// Check if 'check' icon is present for existing widgets
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="MyData-check-icon"]')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="RecentlyViewed-check-icon"]')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="TotalAssets-check-icon"]')
|
||||
).toBeVisible();
|
||||
|
||||
// Check if 'check' icon is not present for removed widgets
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="ActivityFeed-check-icon"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="Following-check-icon"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="KPI-check-icon"]')
|
||||
).not.toBeVisible();
|
||||
|
||||
// Add Following widget
|
||||
await adminPage
|
||||
.locator('[data-testid="Following-widget-tab-label"]')
|
||||
.click();
|
||||
await adminPage
|
||||
.locator(
|
||||
'[aria-labelledby$="KnowledgePanel.Following"] [data-testid="add-widget-button"]'
|
||||
)
|
||||
.locator('[data-testid="reset-layout-modal"] .ant-modal-footer')
|
||||
.locator('text=Yes')
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="following-widget"]')
|
||||
).toBeVisible();
|
||||
await resetResponse;
|
||||
|
||||
// Check if check icons are present in tab labels for newly added widgets
|
||||
await openAddCustomizeWidgetModal(adminPage);
|
||||
// Verify the toast notification
|
||||
await toastNotification(adminPage, 'Page layout updated successfully.');
|
||||
|
||||
// Check if 'check' icon is present for the Following widget
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="Following-check-icon"]')
|
||||
).toBeVisible();
|
||||
// Check if all widgets are present after resetting the layout
|
||||
await checkAllDefaultWidgets(adminPage);
|
||||
|
||||
// Close the add widget modal
|
||||
await adminPage
|
||||
.locator('[data-testid="add-widget-modal"] [aria-label="Close"]')
|
||||
.click();
|
||||
|
||||
// Save the updated layout
|
||||
await saveCustomizeLayoutPage(adminPage);
|
||||
|
||||
// Navigate to the landing page
|
||||
// Check if all widgets are present on landing page
|
||||
await redirectToHomePage(adminPage);
|
||||
|
||||
// Check if removed widgets are not present on the landing page
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="activity-feed-widget"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="kpi-widget"]')
|
||||
).not.toBeVisible();
|
||||
// Ensures the page is fully loaded
|
||||
await adminPage.waitForLoadState('networkidle');
|
||||
|
||||
// Check if newly added widgets are present on the landing page
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="following-widget"]')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Resetting the layout flow should work properly',
|
||||
async () => {
|
||||
// Check if removed widgets are not present on landing page
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="activity-feed-widget"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="kpi-widget"]')
|
||||
).not.toBeVisible();
|
||||
|
||||
await navigateToCustomizeLandingPage(adminPage, {
|
||||
personaName: persona.responseData.name,
|
||||
customPageDataResponse: 200,
|
||||
});
|
||||
|
||||
// Check if removed widgets are not present on customize page
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="activity-feed-widget"]')
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
adminPage.locator('[data-testid="kpi-widget"]')
|
||||
).not.toBeVisible();
|
||||
|
||||
await adminPage.locator('[data-testid="reset-button"]').click();
|
||||
|
||||
// Confirm reset in modal
|
||||
const resetResponse = adminPage.waitForResponse('/api/v1/docStore/*');
|
||||
|
||||
await adminPage
|
||||
.locator('[data-testid="reset-layout-modal"] .ant-modal-footer')
|
||||
.locator('text=Yes')
|
||||
.click();
|
||||
|
||||
await resetResponse;
|
||||
|
||||
// Verify the toast notification
|
||||
await toastNotification(
|
||||
adminPage,
|
||||
'Page layout updated successfully.'
|
||||
);
|
||||
|
||||
// Check if all widgets are present after resetting the layout
|
||||
await checkAllDefaultWidgets(adminPage, true);
|
||||
|
||||
// Check if all widgets are present on landing page
|
||||
await redirectToHomePage(adminPage);
|
||||
|
||||
// Ensures the page is fully loaded
|
||||
await adminPage.waitForLoadState('networkidle');
|
||||
|
||||
await checkAllDefaultWidgets(adminPage);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test.fixme(
|
||||
'Remove and add the widget in the same placeholder',
|
||||
async ({ adminPage }) => {
|
||||
await redirectToHomePage(adminPage);
|
||||
|
||||
const feedResponse = adminPage.waitForResponse(
|
||||
'/api/v1/feed?type=Conversation&*'
|
||||
);
|
||||
await navigateToCustomizeLandingPage(adminPage, {
|
||||
personaName: persona2.responseData.name,
|
||||
customPageDataResponse: 404,
|
||||
});
|
||||
|
||||
await feedResponse;
|
||||
|
||||
await adminPage.waitForSelector('[data-testid="activity-feed-widget"]');
|
||||
|
||||
const followingElementStyle = await adminPage
|
||||
.locator('[id="KnowledgePanel.Following"]')
|
||||
.evaluate((node) => {
|
||||
const computedStyle = window.getComputedStyle(node);
|
||||
|
||||
return {
|
||||
transform: computedStyle.transform,
|
||||
};
|
||||
});
|
||||
|
||||
// Remove and check the placement of Following widget.
|
||||
await adminPage.click(
|
||||
'[data-testid="following-widget"] [data-testid="remove-widget-button"]'
|
||||
);
|
||||
|
||||
await adminPage.waitForSelector('[data-testid="following-widget"]', {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
await adminPage.waitForSelector(
|
||||
'[data-testid*="KnowledgePanel.Following"][data-testid$="EmptyWidgetPlaceholder"]'
|
||||
);
|
||||
|
||||
// Add KPI widget in the same placeholder
|
||||
const getWidgetList = adminPage.waitForResponse(
|
||||
'api/v1/docStore?fqnPrefix=KnowledgePanel&*'
|
||||
);
|
||||
await adminPage.click(
|
||||
'[data-testid="KnowledgePanel.Following.EmptyWidgetPlaceholder"] [data-testid="add-widget-button"]'
|
||||
);
|
||||
|
||||
await getWidgetList;
|
||||
|
||||
await adminPage.waitForSelector('[role="dialog"].ant-modal');
|
||||
|
||||
expect(adminPage.locator('[role="dialog"].ant-modal')).toBeVisible();
|
||||
|
||||
await adminPage.click('[data-testid="KPI-widget-tab-label"]');
|
||||
|
||||
await adminPage
|
||||
.locator('.ant-tabs-tabpane-active [data-testid="add-widget-button"]')
|
||||
.click();
|
||||
|
||||
await adminPage.waitForSelector('[role="dialog"].ant-modal', {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
const kpiElement = adminPage.locator('[id^="KnowledgePanel.KPI-"]');
|
||||
const kpiElementStyle = await kpiElement.evaluate((node) => {
|
||||
const computedStyle = window.getComputedStyle(node);
|
||||
|
||||
return {
|
||||
transform: computedStyle.transform,
|
||||
};
|
||||
});
|
||||
|
||||
// Check if the KPI widget is added in the same placeholder,by their transform property or placement.
|
||||
expect(kpiElementStyle.transform).toEqual(
|
||||
followingElementStyle.transform
|
||||
);
|
||||
|
||||
await saveCustomizeLayoutPage(adminPage, true);
|
||||
}
|
||||
);
|
||||
await checkAllDefaultWidgets(adminPage);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright 2025 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, test as base } from '@playwright/test';
|
||||
import { PersonaClass } from '../../support/persona/PersonaClass';
|
||||
import { UserClass } from '../../support/user/UserClass';
|
||||
import { performAdminLogin } from '../../utils/admin';
|
||||
import { redirectToHomePage } from '../../utils/common';
|
||||
import {
|
||||
addAndVerifyWidget,
|
||||
removeAndVerifyWidget,
|
||||
setUserDefaultPersona,
|
||||
} from '../../utils/customizeLandingPage';
|
||||
import {
|
||||
verifyActivityFeedFilters,
|
||||
verifyDataFilters,
|
||||
verifyDomainsFilters,
|
||||
verifyTaskFilters,
|
||||
verifyTotalDataAssetsFilters,
|
||||
} from '../../utils/widgetFilters';
|
||||
|
||||
const adminUser = new UserClass();
|
||||
const persona = new PersonaClass();
|
||||
|
||||
const test = base.extend<{ page: Page }>({
|
||||
page: async ({ browser }, use) => {
|
||||
const page = await browser.newPage();
|
||||
await adminUser.login(page);
|
||||
await use(page);
|
||||
await page.close();
|
||||
},
|
||||
});
|
||||
|
||||
base.beforeAll('Setup pre-requests', async ({ browser, page }) => {
|
||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||
await adminUser.create(apiContext);
|
||||
await adminUser.setAdminRole(apiContext);
|
||||
await persona.create(apiContext, [adminUser.responseData.id]);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
base.afterAll('Cleanup', async ({ browser }) => {
|
||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||
await adminUser.delete(apiContext);
|
||||
await persona.delete(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.describe('Widgets', () => {
|
||||
test.beforeAll(async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('sidebar-toggle').click();
|
||||
await setUserDefaultPersona(page, persona.responseData.displayName);
|
||||
});
|
||||
|
||||
test('Activity Feed', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('welcome-screen-close-btn').click();
|
||||
|
||||
await expect(page.getByTestId('KnowledgePanel.ActivityFeed')).toBeVisible();
|
||||
|
||||
await verifyActivityFeedFilters(page, 'KnowledgePanel.ActivityFeed');
|
||||
|
||||
await removeAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.ActivityFeed',
|
||||
persona.responseData.name
|
||||
);
|
||||
|
||||
await addAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.ActivityFeed',
|
||||
persona.responseData.name
|
||||
);
|
||||
});
|
||||
|
||||
test('Data Assets', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('welcome-screen-close-btn').click();
|
||||
|
||||
await expect(page.getByTestId('KnowledgePanel.DataAssets')).toBeVisible();
|
||||
|
||||
await removeAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.DataAssets',
|
||||
persona.responseData.name
|
||||
);
|
||||
|
||||
await addAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.DataAssets',
|
||||
persona.responseData.name
|
||||
);
|
||||
});
|
||||
|
||||
test('My Data', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('welcome-screen-close-btn').click();
|
||||
|
||||
await expect(page.getByTestId('KnowledgePanel.MyData')).toBeVisible();
|
||||
|
||||
await verifyDataFilters(page, 'KnowledgePanel.MyData');
|
||||
|
||||
await removeAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.MyData',
|
||||
persona.responseData.name
|
||||
);
|
||||
|
||||
await addAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.MyData',
|
||||
persona.responseData.name
|
||||
);
|
||||
});
|
||||
|
||||
test('KPI', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('welcome-screen-close-btn').click();
|
||||
|
||||
await expect(page.getByTestId('KnowledgePanel.KPI')).toBeVisible();
|
||||
|
||||
await removeAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.KPI',
|
||||
persona.responseData.name
|
||||
);
|
||||
|
||||
await addAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.KPI',
|
||||
persona.responseData.name
|
||||
);
|
||||
});
|
||||
|
||||
test('Total Data Assets', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('welcome-screen-close-btn').click();
|
||||
|
||||
await expect(page.getByTestId('KnowledgePanel.TotalAssets')).toBeVisible();
|
||||
|
||||
await verifyTotalDataAssetsFilters(page, 'KnowledgePanel.TotalAssets');
|
||||
|
||||
await removeAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.TotalAssets',
|
||||
persona.responseData.name
|
||||
);
|
||||
|
||||
await addAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.TotalAssets',
|
||||
persona.responseData.name
|
||||
);
|
||||
});
|
||||
|
||||
test('Following Assets', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('welcome-screen-close-btn').click();
|
||||
|
||||
await expect(page.getByTestId('KnowledgePanel.Following')).toBeVisible();
|
||||
|
||||
await verifyDataFilters(page, 'KnowledgePanel.Following');
|
||||
|
||||
await removeAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.Following',
|
||||
persona.responseData.name
|
||||
);
|
||||
|
||||
await addAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.Following',
|
||||
persona.responseData.name
|
||||
);
|
||||
});
|
||||
|
||||
test('Domains', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('welcome-screen-close-btn').click();
|
||||
|
||||
await expect(page.getByTestId('KnowledgePanel.Domains')).not.toBeVisible();
|
||||
|
||||
await addAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.Domains',
|
||||
persona.responseData.name
|
||||
);
|
||||
|
||||
await verifyDomainsFilters(page, 'KnowledgePanel.Domains');
|
||||
|
||||
await removeAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.Domains',
|
||||
persona.responseData.name
|
||||
);
|
||||
});
|
||||
|
||||
test('My Tasks', async ({ page }) => {
|
||||
test.slow(true);
|
||||
|
||||
await redirectToHomePage(page);
|
||||
await page.getByTestId('welcome-screen-close-btn').click();
|
||||
|
||||
await expect(page.getByTestId('KnowledgePanel.MyTask')).not.toBeVisible();
|
||||
|
||||
await addAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.MyTask',
|
||||
persona.responseData.name
|
||||
);
|
||||
|
||||
await verifyTaskFilters(page, 'KnowledgePanel.MyTask');
|
||||
|
||||
await removeAndVerifyWidget(
|
||||
page,
|
||||
'KnowledgePanel.MyTask',
|
||||
persona.responseData.name
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -12,15 +12,16 @@
|
||||
*/
|
||||
import { expect, Page } from '@playwright/test';
|
||||
import { GlobalSettingOptions } from '../constant/settings';
|
||||
import { toastNotification, visitOwnProfilePage } from './common';
|
||||
import {
|
||||
redirectToHomePage,
|
||||
toastNotification,
|
||||
visitOwnProfilePage,
|
||||
} from './common';
|
||||
import { settingClick } from './sidebar';
|
||||
|
||||
export const navigateToCustomizeLandingPage = async (
|
||||
page: Page,
|
||||
{
|
||||
personaName,
|
||||
customPageDataResponse,
|
||||
}: { personaName: string; customPageDataResponse: number }
|
||||
{ personaName }: { personaName: string }
|
||||
) => {
|
||||
const getPersonas = page.waitForResponse('/api/v1/personas*');
|
||||
|
||||
@ -38,60 +39,32 @@ export const navigateToCustomizeLandingPage = async (
|
||||
await page.getByRole('tab', { name: 'Customize UI' }).click();
|
||||
|
||||
await page.getByTestId('LandingPage').click();
|
||||
await getCustomPageDataResponse;
|
||||
|
||||
expect((await getCustomPageDataResponse).status()).toBe(
|
||||
customPageDataResponse
|
||||
);
|
||||
await page.waitForLoadState('networkidle');
|
||||
};
|
||||
|
||||
export const removeAndCheckWidget = async (
|
||||
page: Page,
|
||||
{ widgetTestId, widgetKey }: { widgetTestId: string; widgetKey: string }
|
||||
{ widgetKey }: { widgetKey: string }
|
||||
) => {
|
||||
// Click on remove widget button
|
||||
await page.click(
|
||||
`[data-testid="${widgetTestId}"] [data-testid="remove-widget-button"]`
|
||||
);
|
||||
await page
|
||||
.locator(`[data-testid="${widgetKey}"] [data-testid="more-options-button"]`)
|
||||
.click();
|
||||
|
||||
// Check if widget does not exist
|
||||
await page.waitForSelector(`[data-testid="${widgetTestId}"]`, {
|
||||
state: 'detached',
|
||||
});
|
||||
await page.getByText('Remove').click();
|
||||
|
||||
// Check if empty widget placeholder is displayed in place of removed widget
|
||||
await page.waitForSelector(
|
||||
`[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"]`
|
||||
);
|
||||
|
||||
// Remove empty widget placeholder
|
||||
await page.click(
|
||||
`[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"] [data-testid="remove-widget-button"]`
|
||||
);
|
||||
|
||||
// Check if empty widget placeholder does not exist
|
||||
await page.waitForSelector(
|
||||
`[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"]`,
|
||||
{ state: 'detached' }
|
||||
);
|
||||
await expect(page.getByTestId(`${widgetKey}`)).not.toBeVisible();
|
||||
};
|
||||
|
||||
export const checkAllDefaultWidgets = async (
|
||||
page: Page,
|
||||
checkEmptyWidgetPlaceholder = false
|
||||
) => {
|
||||
await expect(page.getByTestId('activity-feed-widget')).toBeVisible();
|
||||
await expect(page.getByTestId('following-widget')).toBeVisible();
|
||||
await expect(page.getByTestId('recently-viewed-widget')).toBeVisible();
|
||||
await expect(page.getByTestId('data-assets-widget')).toBeVisible();
|
||||
await expect(page.getByTestId('my-data-widget')).toBeVisible();
|
||||
await expect(page.getByTestId('kpi-widget')).toBeVisible();
|
||||
await expect(page.getByTestId('total-assets-widget')).toBeVisible();
|
||||
|
||||
if (checkEmptyWidgetPlaceholder) {
|
||||
await expect(
|
||||
page.getByTestId('ExtraWidget.EmptyWidgetPlaceholder')
|
||||
).toBeVisible();
|
||||
}
|
||||
export const checkAllDefaultWidgets = async (page: Page) => {
|
||||
await expect(page.getByTestId('KnowledgePanel.ActivityFeed')).toBeVisible();
|
||||
await expect(page.getByTestId('KnowledgePanel.Following')).toBeVisible();
|
||||
await expect(page.getByTestId('KnowledgePanel.DataAssets')).toBeVisible();
|
||||
await expect(page.getByTestId('KnowledgePanel.MyData')).toBeVisible();
|
||||
await expect(page.getByTestId('KnowledgePanel.KPI')).toBeVisible();
|
||||
await expect(page.getByTestId('KnowledgePanel.TotalAssets')).toBeVisible();
|
||||
};
|
||||
|
||||
export const setUserDefaultPersona = async (
|
||||
@ -101,9 +74,12 @@ export const setUserDefaultPersona = async (
|
||||
await visitOwnProfilePage(page);
|
||||
|
||||
await page.locator('[data-testid="edit-user-persona"]').nth(1).click();
|
||||
await page.locator('[data-testid="persona-popover"]').isVisible();
|
||||
await page.locator('input[role="combobox"]').nth(1).click();
|
||||
await page.waitForSelector('[data-testid="persona-select-list"]');
|
||||
|
||||
await expect(
|
||||
page.locator('[data-testid="persona-select-list"]')
|
||||
).toBeVisible();
|
||||
|
||||
await page.locator('[data-testid="persona-select-list"]').click();
|
||||
|
||||
const setDefaultPersona = page.waitForResponse('/api/v1/users/*');
|
||||
|
||||
@ -123,7 +99,7 @@ export const openAddCustomizeWidgetModal = async (page: Page) => {
|
||||
);
|
||||
await page
|
||||
.locator(
|
||||
'[data-testid="ExtraWidget.EmptyWidgetPlaceholder"] [data-testid="add-widget-button"]'
|
||||
'[data-testid="customize-landing-page-header"] [data-testid="add-widget-button"]'
|
||||
)
|
||||
.click();
|
||||
|
||||
@ -145,3 +121,81 @@ export const saveCustomizeLayoutPage = async (
|
||||
`Page layout ${isCreated ? 'created' : 'updated'} successfully.`
|
||||
);
|
||||
};
|
||||
|
||||
export const removeAndVerifyWidget = async (
|
||||
page: Page,
|
||||
widgetKey: string,
|
||||
personaName: string
|
||||
) => {
|
||||
await navigateToCustomizeLandingPage(page, {
|
||||
personaName,
|
||||
});
|
||||
|
||||
await removeAndCheckWidget(page, {
|
||||
widgetKey,
|
||||
});
|
||||
|
||||
await page.locator('[data-testid="save-button"]').click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await redirectToHomePage(page);
|
||||
|
||||
await expect(page.getByTestId(widgetKey)).not.toBeVisible();
|
||||
};
|
||||
|
||||
export const addAndVerifyWidget = async (
|
||||
page: Page,
|
||||
widgetKey: string,
|
||||
personaName: string
|
||||
) => {
|
||||
await navigateToCustomizeLandingPage(page, {
|
||||
personaName,
|
||||
});
|
||||
|
||||
await openAddCustomizeWidgetModal(page);
|
||||
await page.locator('[data-testid="loader"]').waitFor({
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
await page.locator(`[data-testid="${widgetKey}"]`).click();
|
||||
|
||||
await page.locator('[data-testid="apply-btn"]').click();
|
||||
|
||||
await expect(page.getByTestId(widgetKey)).toBeVisible();
|
||||
|
||||
await page.locator('[data-testid="save-button"]').click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await redirectToHomePage(page);
|
||||
|
||||
await expect(page.getByTestId(widgetKey)).toBeVisible();
|
||||
};
|
||||
|
||||
export const addCuratedAssetPlaceholder = async ({
|
||||
page,
|
||||
personaName,
|
||||
}: {
|
||||
page: Page;
|
||||
personaName: string;
|
||||
}) => {
|
||||
await navigateToCustomizeLandingPage(page, {
|
||||
personaName,
|
||||
});
|
||||
|
||||
await openAddCustomizeWidgetModal(page);
|
||||
await page.locator('[data-testid="loader"]').waitFor({
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
await page.locator('[data-testid="KnowledgePanel.CuratedAssets"]').click();
|
||||
|
||||
await page.locator('[data-testid="apply-btn"]').click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByTestId('KnowledgePanel.CuratedAssets')
|
||||
.getByTestId('widget-empty-state')
|
||||
).toBeVisible();
|
||||
};
|
||||
|
||||
@ -38,6 +38,32 @@ import {
|
||||
import { searchAndClickOnOption } from './explore';
|
||||
import { sidebarClick } from './sidebar';
|
||||
|
||||
const waitForAllLoadersToDisappear = async (page: Page) => {
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
const allLoaders = page.locator('[data-testid="loader"]');
|
||||
const count = await allLoaders.count();
|
||||
|
||||
let allLoadersGone = true;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const loader = allLoaders.nth(i);
|
||||
try {
|
||||
if (await loader.isVisible()) {
|
||||
await loader.waitFor({ state: 'detached', timeout: 1000 });
|
||||
allLoadersGone = false;
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
if (allLoadersGone) {
|
||||
break;
|
||||
}
|
||||
await page.waitForTimeout(100); // slight buffer before next retry
|
||||
}
|
||||
};
|
||||
|
||||
export const visitEntityPage = async (data: {
|
||||
page: Page;
|
||||
searchTerm: string;
|
||||
@ -46,17 +72,8 @@ export const visitEntityPage = async (data: {
|
||||
const { page, searchTerm, dataTestId } = data;
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await Promise.all([
|
||||
page.getByRole('main').getByTestId('loader').waitFor({ state: 'detached' }),
|
||||
page
|
||||
.getByTestId('content-resizable-panel-container')
|
||||
.getByTestId('loader')
|
||||
.waitFor({ state: 'detached' }),
|
||||
page
|
||||
.getByTestId('content-height-with-resizable-panel')
|
||||
.getByTestId('loader')
|
||||
.waitFor({ state: 'detached' }),
|
||||
]);
|
||||
// Unified loader handling
|
||||
await waitForAllLoadersToDisappear(page);
|
||||
|
||||
const isWelcomeScreenVisible = await page
|
||||
.getByTestId('welcome-screen')
|
||||
|
||||
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2025 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';
|
||||
|
||||
export const verifyActivityFeedFilters = async (
|
||||
page: Page,
|
||||
widgetKey: string
|
||||
) => {
|
||||
await expect(
|
||||
page.getByTestId(widgetKey).getByTestId('widget-sort-by-dropdown')
|
||||
).toBeVisible();
|
||||
|
||||
const myDataFilter = page.waitForResponse(
|
||||
'/api/v1/feed?type=Conversation&filterType=OWNER&*'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'My Data' }).click();
|
||||
await myDataFilter;
|
||||
|
||||
const followingFilter = page.waitForResponse(
|
||||
'/api/v1/feed?type=Conversation&filterType=FOLLOWS&*'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Following' }).click();
|
||||
await followingFilter;
|
||||
|
||||
const allActivityFilter = page.waitForResponse(
|
||||
'/api/v1/feed?type=Conversation&*'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'All Activity' }).click();
|
||||
await allActivityFilter;
|
||||
};
|
||||
|
||||
export const verifyDataFilters = async (page: Page, widgetKey: string) => {
|
||||
await expect(
|
||||
page.getByTestId(widgetKey).getByTestId('widget-sort-by-dropdown')
|
||||
).toBeVisible();
|
||||
|
||||
const aToZFilter = page.waitForResponse(
|
||||
'/api/v1/search/query?q=*&index=all*&sort_field=name.keyword&sort_order=asc'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'A to Z' }).click();
|
||||
await aToZFilter;
|
||||
|
||||
const zToAFilter = page.waitForResponse(
|
||||
'/api/v1/search/query?q=*&index=all*&sort_field=name.keyword&sort_order=desc'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Z to A' }).click();
|
||||
await zToAFilter;
|
||||
|
||||
const latestFilter = page.waitForResponse(
|
||||
'/api/v1/search/query?q=*&index=all*&sort_field=updatedAt&sort_order=desc'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Latest' }).click();
|
||||
await latestFilter;
|
||||
};
|
||||
|
||||
export const verifyTotalDataAssetsFilters = async (
|
||||
page: Page,
|
||||
widgetKey: string
|
||||
) => {
|
||||
await expect(
|
||||
page.getByTestId(widgetKey).getByTestId('widget-sort-by-dropdown')
|
||||
).toBeVisible();
|
||||
|
||||
const last14DaysFilter = page.waitForResponse(
|
||||
'/api/v1/analytics/dataInsights/system/charts/name/total_data_assets/data?start=*&end=*'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Last 14 days' }).click();
|
||||
await last14DaysFilter;
|
||||
|
||||
const last7DaysFilter = page.waitForResponse(
|
||||
'/api/v1/analytics/dataInsights/system/charts/name/total_data_assets/data?start=*&end=*'
|
||||
);
|
||||
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Last 7 days' }).click();
|
||||
await last7DaysFilter;
|
||||
};
|
||||
|
||||
export const verifyDomainsFilters = async (page: Page, widgetKey: string) => {
|
||||
await expect(
|
||||
page.getByTestId(widgetKey).getByTestId('widget-sort-by-dropdown')
|
||||
).toBeVisible();
|
||||
|
||||
const aToZFilter = page.waitForResponse(
|
||||
'api/v1/search/query?q=*&index=domain_search_index&*&sort_field=name.keyword&sort_order=asc'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'A to Z' }).click();
|
||||
await aToZFilter;
|
||||
|
||||
const zToAFilter = page.waitForResponse(
|
||||
'api/v1/search/query?q=*&index=domain_search_index&*&sort_field=name.keyword&sort_order=desc'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Z to A' }).click();
|
||||
await zToAFilter;
|
||||
|
||||
const latestFilter = page.waitForResponse(
|
||||
'api/v1/search/query?q=*&index=domain_search_index&*&sort_field=updatedAt&sort_order=desc'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Latest' }).click();
|
||||
await latestFilter;
|
||||
};
|
||||
|
||||
export const verifyTaskFilters = async (page: Page, widgetKey: string) => {
|
||||
await expect(
|
||||
page.getByTestId(widgetKey).getByTestId('widget-sort-by-dropdown')
|
||||
).toBeVisible();
|
||||
|
||||
const mentionsTaskFilter = page.waitForResponse(
|
||||
'/api/v1/feed?type=Task&filterType=MENTIONS&*'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Mentions' }).click();
|
||||
await mentionsTaskFilter;
|
||||
|
||||
const assignedTasksFilter = page.waitForResponse(
|
||||
'/api/v1/feed?type=Task&filterType=ASSIGNED_TO&*'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Assigned' }).click();
|
||||
await assignedTasksFilter;
|
||||
|
||||
const allTasksFilter = page.waitForResponse(
|
||||
'/api/v1/feed?type=Task&filterType=OWNER_OR_FOLLOWS&*'
|
||||
);
|
||||
await page
|
||||
.getByTestId(widgetKey)
|
||||
.getByTestId('widget-sort-by-dropdown')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'All' }).click();
|
||||
await allTasksFilter;
|
||||
};
|
||||
@ -15,6 +15,7 @@ import { Document } from '../../../../generated/entity/docStore/document';
|
||||
export interface CustomiseLandingPageHeaderProps {
|
||||
addedWidgetsList?: string[];
|
||||
backgroundColor?: string;
|
||||
dataTestId?: string;
|
||||
handleAddWidget?: (
|
||||
newWidgetData: Document,
|
||||
placeholderWidgetKey: string,
|
||||
|
||||
@ -52,6 +52,7 @@ import CustomiseSearchBar from './CustomiseSearchBar';
|
||||
const CustomiseLandingPageHeader = ({
|
||||
addedWidgetsList,
|
||||
backgroundColor,
|
||||
dataTestId,
|
||||
handleAddWidget,
|
||||
hideCustomiseButton = false,
|
||||
isPreviewHeader = false,
|
||||
@ -153,7 +154,10 @@ const CustomiseLandingPageHeader = ({
|
||||
}, [fetchAnnouncements]);
|
||||
|
||||
return (
|
||||
<div className="customise-landing-page-header" style={landingPageStyle}>
|
||||
<div
|
||||
className="customise-landing-page-header"
|
||||
data-testid={dataTestId}
|
||||
style={landingPageStyle}>
|
||||
<div className="header-container">
|
||||
<div className="dashboard-header">
|
||||
<div
|
||||
@ -246,6 +250,7 @@ const CustomiseLandingPageHeader = ({
|
||||
className={classNames('customise-recently-viewed-data', {
|
||||
disabled: !onHomePage,
|
||||
})}
|
||||
data-testid="recently-viewed-asset"
|
||||
key={index}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
|
||||
@ -233,6 +233,7 @@ function CustomizeMyData({
|
||||
overlappedContainer
|
||||
addedWidgetsList={addedWidgetsList}
|
||||
backgroundColor={backgroundColor}
|
||||
dataTestId="customise-landing-page-header"
|
||||
handleAddWidget={handleMainPanelAddWidget}
|
||||
onBackgroundColorUpdate={handleBackgroundColorUpdate}
|
||||
/>
|
||||
|
||||
@ -56,7 +56,7 @@ function EmptyWidgetPlaceholderV1({
|
||||
return (
|
||||
<WidgetWrapper
|
||||
className="empty-widget-placeholder-v1"
|
||||
data-testid={widgetKey}>
|
||||
dataTestId={widgetKey}>
|
||||
{widgetContent}
|
||||
</WidgetWrapper>
|
||||
);
|
||||
|
||||
@ -58,7 +58,9 @@ describe('WidgetCard', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('widget-card')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('KnowledgePanel.TestWidget')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Widget')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('This is a test widget description')
|
||||
@ -104,7 +106,9 @@ describe('WidgetCard', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('widget-card')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('KnowledgePanel.TestWidget')
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('widget-description')).toBeInTheDocument();
|
||||
|
||||
@ -124,7 +128,7 @@ describe('WidgetCard', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('widget-card'));
|
||||
fireEvent.click(screen.getByTestId('KnowledgePanel.TestWidget'));
|
||||
|
||||
expect(mockOnSelectWidget).toHaveBeenCalledWith('widget-test-1');
|
||||
expect(mockOnSelectWidget).toHaveBeenCalledTimes(1);
|
||||
@ -139,7 +143,7 @@ describe('WidgetCard', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('widget-card'));
|
||||
fireEvent.click(screen.getByTestId('KnowledgePanel.NoId'));
|
||||
|
||||
expect(mockOnSelectWidget).toHaveBeenCalledWith('');
|
||||
expect(mockOnSelectWidget).toHaveBeenCalledTimes(1);
|
||||
@ -155,7 +159,7 @@ describe('WidgetCard', () => {
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
fireEvent.click(screen.getByTestId('widget-card'));
|
||||
fireEvent.click(screen.getByTestId('KnowledgePanel.TestWidget'));
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@ -168,7 +172,7 @@ describe('WidgetCard', () => {
|
||||
/>
|
||||
);
|
||||
|
||||
const card = screen.getByTestId('widget-card');
|
||||
const card = screen.getByTestId('KnowledgePanel.TestWidget');
|
||||
|
||||
fireEvent.click(card);
|
||||
fireEvent.click(card);
|
||||
|
||||
@ -67,7 +67,7 @@ const WidgetCard = ({
|
||||
className={`widget-card h-full d-flex flex-col ${
|
||||
isSelected ? 'selected' : ''
|
||||
}`}
|
||||
data-testid="widget-card"
|
||||
data-testid={widget.fullyQualifiedName}
|
||||
onClick={handleClick}>
|
||||
<div className="widget-card-content d-flex justify-between items-center flex-1">
|
||||
<img
|
||||
|
||||
@ -174,8 +174,8 @@ const MyFeedWidgetInternal = ({
|
||||
|
||||
return (
|
||||
<WidgetWrapper
|
||||
data-testid="feed-widget"
|
||||
dataLength={entityThread.length > 0 ? entityThread.length : 10}
|
||||
dataTestId="KnowledgePanel.ActivityFeed"
|
||||
header={widgetHeader}
|
||||
loading={loading}>
|
||||
<div className="feed-widget-container">
|
||||
|
||||
@ -38,6 +38,7 @@ const HeaderTheme = ({ selectedColor, setSelectedColor }: HeaderThemeProps) => {
|
||||
hideCustomiseButton
|
||||
isPreviewHeader
|
||||
backgroundColor={selectedColor}
|
||||
dataTestId="modal-header-theme"
|
||||
/>
|
||||
</div>
|
||||
<div className="select-background-container">
|
||||
@ -49,6 +50,7 @@ const HeaderTheme = ({ selectedColor, setSelectedColor }: HeaderThemeProps) => {
|
||||
{headerBackgroundColors.map((value) => (
|
||||
<Button
|
||||
className="option-color-container cursor-pointer"
|
||||
data-testid="option-color"
|
||||
key={value.color}
|
||||
style={{
|
||||
backgroundColor: value.color,
|
||||
@ -59,7 +61,6 @@ const HeaderTheme = ({ selectedColor, setSelectedColor }: HeaderThemeProps) => {
|
||||
className={`option-color w-full h-full ${
|
||||
selectedColor === value.color ? 'white-border' : ''
|
||||
}`}
|
||||
data-testid="option-color"
|
||||
/>
|
||||
</Button>
|
||||
))}
|
||||
|
||||
@ -336,6 +336,7 @@ const MyDataWidgetInternal = ({
|
||||
return (
|
||||
<WidgetWrapper
|
||||
dataLength={data.length > 0 ? data.length : 10}
|
||||
dataTestId="KnowledgePanel.MyData"
|
||||
header={widgetHeader}
|
||||
loading={isLoading}>
|
||||
{widgetContent}
|
||||
|
||||
@ -321,6 +321,7 @@ function FollowingWidget({
|
||||
return (
|
||||
<WidgetWrapper
|
||||
dataLength={followedData.length !== 0 ? followedData.length : 10}
|
||||
dataTestId="KnowledgePanel.Following"
|
||||
header={widgetHeader}
|
||||
loading={isLoadingOwnedData}>
|
||||
{WidgetContent}
|
||||
|
||||
@ -29,21 +29,19 @@ export interface WidgetMoreOptionsProps {
|
||||
menuItems: MoreOption[];
|
||||
onMenuClick: (e: MenuInfo) => void;
|
||||
className?: string;
|
||||
dataTestId?: string;
|
||||
}
|
||||
|
||||
const WidgetMoreOptions = ({
|
||||
menuItems,
|
||||
onMenuClick,
|
||||
className = '',
|
||||
dataTestId = 'widget-more-options',
|
||||
}: WidgetMoreOptionsProps) => {
|
||||
return (
|
||||
<div className="widget-more-options-container">
|
||||
<Dropdown
|
||||
destroyPopupOnHide
|
||||
className={`widget-more-options ${className}`}
|
||||
data-testid={dataTestId}
|
||||
data-testid="widget-more-options"
|
||||
getPopupContainer={getVisiblePopupContainer}
|
||||
menu={{
|
||||
items: menuItems,
|
||||
|
||||
@ -20,6 +20,7 @@ const mockProps = {
|
||||
loading: false,
|
||||
dataLength: 5,
|
||||
className: 'custom-wrapper-class',
|
||||
dataTestId: 'widget-wrapper',
|
||||
};
|
||||
|
||||
const renderWidgetWrapper = (props = {}) => {
|
||||
|
||||
@ -20,6 +20,7 @@ export interface WidgetWrapperProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
dataLength?: number;
|
||||
dataTestId?: string;
|
||||
header?: ReactNode;
|
||||
loading?: boolean;
|
||||
}
|
||||
@ -28,13 +29,14 @@ const WidgetWrapper = ({
|
||||
children,
|
||||
className = '',
|
||||
dataLength = 5,
|
||||
dataTestId,
|
||||
header,
|
||||
loading = false,
|
||||
}: WidgetWrapperProps) => {
|
||||
return (
|
||||
<Card
|
||||
className={`widget-wrapper-container card-widget ${className}`}
|
||||
data-testid="widget-wrapper">
|
||||
data-testid={dataTestId}>
|
||||
{/* Header stays visible during loading */}
|
||||
{header}
|
||||
|
||||
|
||||
@ -465,6 +465,7 @@ const CuratedAssetsWidget = ({
|
||||
<>
|
||||
<WidgetWrapper
|
||||
dataLength={data.length !== 0 ? data.length : 10}
|
||||
dataTestId="KnowledgePanel.CuratedAssets"
|
||||
header={widgetHeader}
|
||||
loading={isLoading}>
|
||||
{widgetContent}
|
||||
|
||||
@ -113,6 +113,7 @@ export const SelectAssetTypeField = ({
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
data-testid="asset-type-select"
|
||||
label={t('label.select-asset-type')}
|
||||
messageVariables={{
|
||||
fieldName: t('label.data-asset-plural'),
|
||||
@ -120,7 +121,6 @@ export const SelectAssetTypeField = ({
|
||||
name="resources"
|
||||
style={{ marginBottom: 8 }}>
|
||||
<Select
|
||||
data-testid="asset-type-select"
|
||||
options={resourcesOptions}
|
||||
placeholder={t('label.select-asset-type')}
|
||||
value={selectedResource}
|
||||
|
||||
@ -110,7 +110,9 @@ describe('DataAssetsWidget', () => {
|
||||
|
||||
renderDataAssetsWidget();
|
||||
|
||||
expect(await screen.findByTestId('widget-wrapper')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByTestId('KnowledgePanel.DataAssets')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('widget-header')).toBeInTheDocument();
|
||||
expect(screen.getByText('label.data-asset-plural')).toBeInTheDocument();
|
||||
});
|
||||
@ -137,7 +139,9 @@ describe('DataAssetsWidget', () => {
|
||||
|
||||
renderDataAssetsWidget();
|
||||
|
||||
expect(await screen.findByTestId('widget-wrapper')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByTestId('KnowledgePanel.DataAssets')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('widget-header')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('label.data-asset-plural')
|
||||
|
||||
@ -206,6 +206,7 @@ const DataAssetsWidget = ({
|
||||
return (
|
||||
<WidgetWrapper
|
||||
dataLength={services.length !== 0 ? services.length : 10}
|
||||
dataTestId="KnowledgePanel.DataAssets"
|
||||
header={widgetHeader}
|
||||
loading={loading}>
|
||||
{widgetContent}
|
||||
|
||||
@ -141,7 +141,9 @@ describe('DomainsWidget', () => {
|
||||
it('renders widget wrapper', async () => {
|
||||
renderDomainsWidget();
|
||||
|
||||
expect(await screen.findByTestId('widget-wrapper')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByTestId('KnowledgePanel.Domains')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a list of domains successfully', async () => {
|
||||
@ -361,7 +363,7 @@ describe('DomainsWidget', () => {
|
||||
|
||||
renderDomainsWidget();
|
||||
|
||||
expect(screen.getByTestId('widget-wrapper')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('KnowledgePanel.Domains')).toBeInTheDocument();
|
||||
// Widget wrapper handles loading state internally
|
||||
});
|
||||
|
||||
|
||||
@ -251,7 +251,11 @@ const DomainsWidget = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<WidgetWrapper dataLength={10} header={widgetHeader} loading={loading}>
|
||||
<WidgetWrapper
|
||||
dataLength={10}
|
||||
dataTestId="KnowledgePanel.Domains"
|
||||
header={widgetHeader}
|
||||
loading={loading}>
|
||||
<div className="domains-widget-container">
|
||||
<div className="widget-content flex-1">
|
||||
{error ? (
|
||||
|
||||
@ -1,330 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 { CloseOutlined, DragOutlined } from '@ant-design/icons';
|
||||
import { Button, Space, Tabs, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { isUndefined } from 'lodash';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { PAGE_SIZE_MEDIUM, ROUTES } from '../../../../constants/constants';
|
||||
import { FEED_COUNT_INITIAL_DATA } from '../../../../constants/entity.constants';
|
||||
import { mockFeedData } from '../../../../constants/mockTourData.constants';
|
||||
import { TAB_SUPPORTED_FILTER } from '../../../../constants/Widgets.constant';
|
||||
import { useTourProvider } from '../../../../context/TourProvider/TourProvider';
|
||||
import { EntityTabs, EntityType } from '../../../../enums/entity.enum';
|
||||
import { FeedFilter } from '../../../../enums/mydata.enum';
|
||||
import {
|
||||
ThreadTaskStatus,
|
||||
ThreadType,
|
||||
} from '../../../../generated/entity/feed/thread';
|
||||
import { useApplicationStore } from '../../../../hooks/useApplicationStore';
|
||||
import { FeedCounts } from '../../../../interface/feed.interface';
|
||||
import { WidgetCommonProps } from '../../../../pages/CustomizablePage/CustomizablePage.interface';
|
||||
import { getFeedCount } from '../../../../rest/feedsAPI';
|
||||
import { getCountBadge, Transi18next } from '../../../../utils/CommonUtils';
|
||||
import entityUtilClassBase from '../../../../utils/EntityUtilClassBase';
|
||||
import { getEntityUserLink } from '../../../../utils/EntityUtils';
|
||||
import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import ActivityFeedListV1New from '../../../ActivityFeed/ActivityFeedList/ActivityFeedListV1New.component';
|
||||
import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
|
||||
import '../../../ActivityFeed/ActivityFeedTab/activity-feed-tab.less';
|
||||
import { ActivityFeedTabs } from '../../../ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
|
||||
import FeedsFilterPopover from '../../../common/FeedsFilterPopover/FeedsFilterPopover.component';
|
||||
|
||||
const FeedsWidget = ({
|
||||
isEditView = false,
|
||||
handleRemoveWidget,
|
||||
widgetKey,
|
||||
}: WidgetCommonProps) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { isTourOpen } = useTourProvider();
|
||||
const { currentUser } = useApplicationStore();
|
||||
const [activeTab, setActiveTab] = useState<ActivityFeedTabs>(
|
||||
ActivityFeedTabs.ALL
|
||||
);
|
||||
const { loading, entityThread, entityPaging, getFeedData } =
|
||||
useActivityFeedProvider();
|
||||
const [count, setCount] = useState<FeedCounts>(FEED_COUNT_INITIAL_DATA);
|
||||
|
||||
const [defaultFilter, setDefaultFilter] = useState<FeedFilter>(
|
||||
currentUser?.isAdmin ? FeedFilter.ALL : FeedFilter.OWNER_OR_FOLLOWS
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === ActivityFeedTabs.ALL) {
|
||||
getFeedData(
|
||||
defaultFilter,
|
||||
undefined,
|
||||
ThreadType.Conversation,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
PAGE_SIZE_MEDIUM
|
||||
);
|
||||
} else if (activeTab === ActivityFeedTabs.MENTIONS) {
|
||||
getFeedData(FeedFilter.MENTIONS);
|
||||
} else if (activeTab === ActivityFeedTabs.TASKS) {
|
||||
getFeedData(
|
||||
defaultFilter,
|
||||
undefined,
|
||||
ThreadType.Task,
|
||||
undefined,
|
||||
undefined,
|
||||
ThreadTaskStatus.Open
|
||||
);
|
||||
}
|
||||
}, [activeTab, defaultFilter]);
|
||||
|
||||
const mentionCountBadge = useMemo(
|
||||
() =>
|
||||
getCountBadge(
|
||||
count.mentionCount,
|
||||
'',
|
||||
activeTab === ActivityFeedTabs.MENTIONS
|
||||
),
|
||||
[count.mentionCount, activeTab]
|
||||
);
|
||||
|
||||
const taskCountBadge = useMemo(
|
||||
() =>
|
||||
getCountBadge(
|
||||
count.openTaskCount,
|
||||
'',
|
||||
activeTab === ActivityFeedTabs.TASKS
|
||||
),
|
||||
[count.openTaskCount, activeTab]
|
||||
);
|
||||
|
||||
const onTabChange = (key: string) => {
|
||||
if (key === ActivityFeedTabs.TASKS) {
|
||||
setDefaultFilter(FeedFilter.OWNER);
|
||||
} else if (key === ActivityFeedTabs.ALL) {
|
||||
setDefaultFilter(
|
||||
currentUser?.isAdmin ? FeedFilter.ALL : FeedFilter.OWNER_OR_FOLLOWS
|
||||
);
|
||||
}
|
||||
setActiveTab(key as ActivityFeedTabs);
|
||||
};
|
||||
|
||||
const redirectToUserPage = useCallback(() => {
|
||||
navigate(
|
||||
entityUtilClassBase.getEntityLink(
|
||||
EntityType.USER,
|
||||
currentUser?.name as string,
|
||||
EntityTabs.ACTIVITY_FEED,
|
||||
activeTab
|
||||
)
|
||||
);
|
||||
}, [activeTab, currentUser]);
|
||||
|
||||
const moreButton = useMemo(() => {
|
||||
if (!loading && entityPaging.after) {
|
||||
return (
|
||||
<div className="p-x-md p-b-md">
|
||||
<Button className="w-full" onClick={redirectToUserPage}>
|
||||
<Typography.Text className="text-primary">
|
||||
{t('label.more')}
|
||||
</Typography.Text>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [loading, entityPaging, redirectToUserPage]);
|
||||
const onFilterUpdate = (filter: FeedFilter) => {
|
||||
setDefaultFilter(filter);
|
||||
};
|
||||
|
||||
const fetchFeedsCount = async () => {
|
||||
try {
|
||||
const res = await getFeedCount(
|
||||
getEntityUserLink(currentUser?.name ?? '')
|
||||
);
|
||||
setCount((prev) => ({
|
||||
...prev,
|
||||
openTaskCount: res?.[0]?.openTaskCount ?? 0,
|
||||
|
||||
mentionCount: res?.[0]?.mentionCount ?? 0,
|
||||
}));
|
||||
} catch (err) {
|
||||
showErrorToast(err as AxiosError, t('server.entity-feed-fetch-error'));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setDefaultFilter(
|
||||
currentUser?.isAdmin ? FeedFilter.ALL : FeedFilter.OWNER_OR_FOLLOWS
|
||||
);
|
||||
fetchFeedsCount();
|
||||
}, [currentUser]);
|
||||
|
||||
const threads = useMemo(() => {
|
||||
if (activeTab === 'tasks') {
|
||||
return entityThread.filter(
|
||||
(thread) => thread.task?.status === ThreadTaskStatus.Open
|
||||
);
|
||||
}
|
||||
|
||||
return entityThread;
|
||||
}, [activeTab, entityThread]);
|
||||
|
||||
const handleCloseClick = useCallback(() => {
|
||||
!isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey);
|
||||
}, [widgetKey]);
|
||||
|
||||
const emptyPlaceholderText = useMemo(
|
||||
() => (
|
||||
<Transi18next
|
||||
i18nKey="message.no-activity-feed"
|
||||
renderElement={<Link rel="noreferrer" to={ROUTES.EXPLORE} />}
|
||||
values={{
|
||||
explored: t('message.have-not-explored-yet'),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
[]
|
||||
);
|
||||
const handleFeedFetchFromFeedList = useCallback(() => {
|
||||
getFeedData(
|
||||
defaultFilter,
|
||||
undefined,
|
||||
ThreadType.Task,
|
||||
undefined,
|
||||
undefined,
|
||||
ThreadTaskStatus.Open
|
||||
);
|
||||
}, [defaultFilter, getFeedData]);
|
||||
const handleAfterTaskClose = () => {
|
||||
handleFeedFetchFromFeedList();
|
||||
fetchFeedsCount();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="feeds-widget-container h-full"
|
||||
data-testid="activity-feed-widget">
|
||||
<Tabs
|
||||
destroyInactiveTabPane
|
||||
className="h-full d-flex"
|
||||
items={[
|
||||
{
|
||||
label: t('label.all'),
|
||||
key: ActivityFeedTabs.ALL,
|
||||
children: (
|
||||
<>
|
||||
<ActivityFeedListV1New
|
||||
isForFeedTab
|
||||
emptyPlaceholderText={emptyPlaceholderText}
|
||||
feedList={isTourOpen ? mockFeedData : threads}
|
||||
hidePopover={isEditView}
|
||||
isLoading={loading && !isTourOpen}
|
||||
showThread={false}
|
||||
/>
|
||||
{moreButton}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
{`@${t('label.mention-plural')}`}
|
||||
{mentionCountBadge}
|
||||
</>
|
||||
),
|
||||
key: ActivityFeedTabs.MENTIONS,
|
||||
children: (
|
||||
<>
|
||||
<ActivityFeedListV1New
|
||||
isForFeedTab
|
||||
emptyPlaceholderText={
|
||||
<Typography.Text className="placeholder-text">
|
||||
{t('message.no-mentions')}
|
||||
</Typography.Text>
|
||||
}
|
||||
feedList={threads}
|
||||
hidePopover={isEditView}
|
||||
isLoading={loading}
|
||||
showThread={false}
|
||||
/>
|
||||
{moreButton}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<>
|
||||
{`${t('label.task-plural')} `}
|
||||
{taskCountBadge}
|
||||
</>
|
||||
),
|
||||
key: ActivityFeedTabs.TASKS,
|
||||
children: (
|
||||
<>
|
||||
<ActivityFeedListV1New
|
||||
isForFeedTab
|
||||
emptyPlaceholderText={
|
||||
<div className="d-flex flex-col gap-4">
|
||||
<Typography.Text className="placeholder-title">
|
||||
{t('message.no-open-tasks-title')}
|
||||
</Typography.Text>
|
||||
<Typography.Text className="placeholder-text">
|
||||
{t('message.no-open-tasks-description')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
}
|
||||
feedList={threads}
|
||||
hidePopover={isEditView}
|
||||
isLoading={loading}
|
||||
showThread={false}
|
||||
onAfterClose={handleAfterTaskClose}
|
||||
/>
|
||||
{moreButton}
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
tabBarExtraContent={
|
||||
<Space>
|
||||
{TAB_SUPPORTED_FILTER.includes(activeTab) && (
|
||||
<FeedsFilterPopover
|
||||
defaultFilter={defaultFilter}
|
||||
feedTab={activeTab}
|
||||
onUpdate={onFilterUpdate}
|
||||
/>
|
||||
)}
|
||||
{isEditView && (
|
||||
<>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
onChange={onTabChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedsWidget;
|
||||
@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { PAGE_SIZE_MEDIUM } from '../../../../constants/constants';
|
||||
import { useApplicationStore } from '../../../../hooks/useApplicationStore';
|
||||
import { mockUserData } from '../../../Settings/Users/mocks/User.mocks';
|
||||
import FeedsWidget from './FeedsWidget.component';
|
||||
|
||||
const mockHandleRemoveWidget = jest.fn();
|
||||
|
||||
const mockThread = [
|
||||
{
|
||||
id: '33873393-bd68-46e9-bccc-7701c1c41ad6',
|
||||
type: 'Task',
|
||||
threadTs: 1703570590556,
|
||||
about: 'test',
|
||||
entityId: '6206a003-281c-4984-9728-4e949a4e4023',
|
||||
posts: [
|
||||
{
|
||||
id: '12345',
|
||||
message: 'Resolved the Task.',
|
||||
postTs: 1703570590652,
|
||||
from: 'admin',
|
||||
reactions: [],
|
||||
},
|
||||
],
|
||||
task: {
|
||||
id: 6,
|
||||
type: 'RequestTestCaseFailureResolution',
|
||||
assignees: [
|
||||
{
|
||||
id: '2345rt',
|
||||
type: 'user',
|
||||
name: 'aaron_johnson0',
|
||||
fullyQualifiedName: 'aaron_johnson0',
|
||||
displayName: 'Aaron Johnson',
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
status: 'Closed',
|
||||
closedBy: 'admin',
|
||||
closedAt: 1703570590648,
|
||||
newValue: 'Resolution comment',
|
||||
testCaseResolutionStatusId: 'f93d08e9-2d38-4d01-a294-f8b44fbb0f4a',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockUseActivityFeedProviderValue = {
|
||||
entityPaging: { total: 4 },
|
||||
entityThread: mockThread,
|
||||
getFeedData: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
loading: false,
|
||||
selectedThread: mockThread[0],
|
||||
setActiveThread: jest.fn(),
|
||||
};
|
||||
|
||||
const widgetProps = {
|
||||
selectedGridSize: 10,
|
||||
isEditView: true,
|
||||
widgetKey: 'testWidgetKey',
|
||||
handleRemoveWidget: mockHandleRemoveWidget,
|
||||
};
|
||||
|
||||
const tabs = ['All', 'Mentions', 'Tasks'];
|
||||
|
||||
jest.mock(
|
||||
'../../../ActivityFeed/ActivityFeedList/ActivityFeedListV1New.component',
|
||||
() => jest.fn().mockImplementation(({ children }) => <p>{children}</p>)
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'../../../common/FeedsFilterPopover/FeedsFilterPopover.component',
|
||||
() =>
|
||||
jest.fn().mockImplementation(({ onUpdate }) => (
|
||||
<div>
|
||||
<button onClick={() => onUpdate('ALL')}>all_button</button>
|
||||
<button onClick={() => onUpdate('ASSIGNED_TO')}>assigned_button</button>
|
||||
<button onClick={() => onUpdate('ASSIGNED_BY')}>
|
||||
created_by_button
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../../../../rest/feedsAPI', () => ({
|
||||
getFeedsWithFilter: jest.fn().mockReturnValue(Promise.resolve(mockThread)),
|
||||
getFeedCount: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ openTaskCount: 0, mentionCount: 0 }]),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../utils/CommonUtils', () => ({
|
||||
getCountBadge: jest.fn(),
|
||||
getEntityDetailLink: jest.fn(),
|
||||
Transi18next: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('quilljs-markdown', () => {
|
||||
class MockQuillMarkdown {
|
||||
constructor() {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Markdown constructor');
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new MockQuillMarkdown();
|
||||
|
||||
return instance;
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
'../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider',
|
||||
() => ({
|
||||
useActivityFeedProvider: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockUseActivityFeedProviderValue),
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('../../../../hooks/useApplicationStore', () => ({
|
||||
useApplicationStore: jest.fn(() => ({
|
||||
currentUser: mockUserData,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useNavigate: jest.fn().mockReturnValue(jest.fn()),
|
||||
}));
|
||||
|
||||
describe('FeedsWidget', () => {
|
||||
it('should call getFeedData for owner conversation on load for non admin user', () => {
|
||||
render(<FeedsWidget {...widgetProps} />);
|
||||
|
||||
expect(mockUseActivityFeedProviderValue.getFeedData).toHaveBeenCalledWith(
|
||||
'OWNER_OR_FOLLOWS',
|
||||
undefined,
|
||||
'Conversation',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
PAGE_SIZE_MEDIUM
|
||||
);
|
||||
});
|
||||
|
||||
it('should call getFeedData for ALL conversation on load for admin user', () => {
|
||||
(useApplicationStore as unknown as jest.Mock).mockImplementationOnce(
|
||||
() => ({
|
||||
currentUser: { ...mockUserData, isAdmin: true },
|
||||
})
|
||||
);
|
||||
|
||||
render(<FeedsWidget {...widgetProps} />);
|
||||
|
||||
expect(mockUseActivityFeedProviderValue.getFeedData).toHaveBeenCalledWith(
|
||||
'ALL',
|
||||
undefined,
|
||||
'Conversation',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
PAGE_SIZE_MEDIUM
|
||||
);
|
||||
});
|
||||
|
||||
it('should render FeedsWidget', async () => {
|
||||
await act(async () => {
|
||||
render(<FeedsWidget {...widgetProps} />);
|
||||
});
|
||||
const activityFeedWidget = screen.getByTestId('activity-feed-widget');
|
||||
|
||||
expect(activityFeedWidget).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render All tab by default', () => {
|
||||
const { getAllByRole } = render(<FeedsWidget {...widgetProps} />);
|
||||
const tabs = getAllByRole('tab');
|
||||
const allTab = tabs[0];
|
||||
|
||||
expect(allTab).toHaveAttribute('aria-selected', 'true');
|
||||
});
|
||||
|
||||
tabs.map((tab, index) => {
|
||||
it(`should select ${tab} tab`, () => {
|
||||
const { getAllByRole } = render(<FeedsWidget {...widgetProps} />);
|
||||
const tabs = getAllByRole('tab');
|
||||
const selectedTab = tabs[index];
|
||||
fireEvent.click(selectedTab);
|
||||
|
||||
expect(selectedTab.getAttribute('aria-selected')).toBe('true');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle close click when in edit view', () => {
|
||||
const { getByTestId } = render(<FeedsWidget {...widgetProps} />);
|
||||
const closeButton = getByTestId('remove-widget-button');
|
||||
fireEvent.click(closeButton);
|
||||
|
||||
expect(mockHandleRemoveWidget).toHaveBeenCalledWith(widgetProps.widgetKey);
|
||||
});
|
||||
|
||||
it('should call api with correct parameters based on the tab selected', () => {
|
||||
render(<FeedsWidget {...widgetProps} />);
|
||||
const tabs = screen.getAllByRole('tab');
|
||||
const conversationTab = tabs[0];
|
||||
fireEvent.click(conversationTab);
|
||||
|
||||
// initial API call for the Feed
|
||||
expect(conversationTab.getAttribute('aria-selected')).toBe('true');
|
||||
expect(mockUseActivityFeedProviderValue.getFeedData).toHaveBeenCalledWith(
|
||||
'OWNER_OR_FOLLOWS',
|
||||
undefined,
|
||||
'Conversation',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
25
|
||||
);
|
||||
|
||||
// Reset mock between checks
|
||||
mockUseActivityFeedProviderValue.getFeedData.mockReset();
|
||||
|
||||
// Testing for "Task Tab", to call API with OWNER filter parameters
|
||||
const taskTab = tabs[2];
|
||||
fireEvent.click(taskTab);
|
||||
|
||||
expect(taskTab.getAttribute('aria-selected')).toBe('true');
|
||||
expect(mockUseActivityFeedProviderValue.getFeedData).toHaveBeenCalledWith(
|
||||
'OWNER',
|
||||
undefined,
|
||||
'Task',
|
||||
undefined,
|
||||
undefined,
|
||||
'Open'
|
||||
);
|
||||
|
||||
// Reset mock for the next check
|
||||
mockUseActivityFeedProviderValue.getFeedData.mockReset();
|
||||
|
||||
// Applying the filter for the assigned button
|
||||
const assignedFilterButton = screen.getByText('assigned_button');
|
||||
fireEvent.click(assignedFilterButton);
|
||||
|
||||
expect(mockUseActivityFeedProviderValue.getFeedData).toHaveBeenCalledWith(
|
||||
'ASSIGNED_TO',
|
||||
undefined,
|
||||
'Task',
|
||||
undefined,
|
||||
undefined,
|
||||
'Open'
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -457,6 +457,7 @@ const KPIWidget = ({
|
||||
return (
|
||||
<WidgetWrapper
|
||||
dataLength={kpiList.length > 0 ? kpiList.length : 10}
|
||||
dataTestId="KnowledgePanel.KPI"
|
||||
header={widgetHeader}
|
||||
loading={isKPIListLoading || isLoading}>
|
||||
<div className="kpi-widget-container" data-testid="kpi-widget">
|
||||
|
||||
@ -123,7 +123,7 @@ describe('KPIWidget', () => {
|
||||
it('renders widget wrapper', async () => {
|
||||
render(<KPIWidget {...widgetProps} />);
|
||||
|
||||
expect(await screen.findByTestId('widget-wrapper')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('KnowledgePanel.KPI')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should fetch kpi list api initially', async () => {
|
||||
|
||||
@ -133,7 +133,7 @@ describe('MyTaskWidget', () => {
|
||||
it('renders widget wrapper', () => {
|
||||
renderMyTaskWidget();
|
||||
|
||||
expect(screen.getByTestId('widget-wrapper')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('KnowledgePanel.MyTask')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls getFeedData on mount with correct parameters', () => {
|
||||
|
||||
@ -177,6 +177,7 @@ const MyTaskWidget = ({
|
||||
return (
|
||||
<WidgetWrapper
|
||||
dataLength={entityThread.length > 0 ? entityThread.length : 10}
|
||||
dataTestId="KnowledgePanel.MyTask"
|
||||
header={widgetHeader}
|
||||
loading={loading}>
|
||||
{widgetContent}
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import RecentlyViewed from './RecentlyViewed';
|
||||
|
||||
const mockProp = {
|
||||
widgetKey: 'testKey',
|
||||
};
|
||||
|
||||
jest.mock(
|
||||
'../../../common/Skeleton/MyData/EntityListSkeleton/EntityListSkeleton.component',
|
||||
() => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => <div>{children}</div>);
|
||||
}
|
||||
);
|
||||
jest.mock('../../../../hooks/useCustomLocation/useCustomLocation', () => {
|
||||
return jest.fn().mockImplementation(() => ({ pathname: '' }));
|
||||
});
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
Link: jest.fn().mockImplementation(() => <div>Link</div>),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../hooks/currentUserStore/useCurrentUserStore', () => ({
|
||||
useCurrentUserPreferences: jest.fn().mockReturnValue({
|
||||
preferences: {
|
||||
recentlyViewed: [
|
||||
{
|
||||
displayName: 'test',
|
||||
entityType: 'table',
|
||||
fqn: 'test',
|
||||
id: '1',
|
||||
serviceType: 'BigQuery',
|
||||
name: 'Test Item',
|
||||
fullyQualifiedName: 'test.item',
|
||||
type: 'test',
|
||||
timestamp: 1706533046620,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('RecentlyViewed', () => {
|
||||
it('should render RecentlyViewed', async () => {
|
||||
await act(async () => {
|
||||
render(<RecentlyViewed widgetKey={mockProp.widgetKey} />);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('recently-viewed-widget')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call handleCloseClick when close button is clicked', async () => {
|
||||
const handleRemoveWidget = jest.fn();
|
||||
const { getByTestId } = render(
|
||||
<RecentlyViewed
|
||||
isEditView
|
||||
handleRemoveWidget={handleRemoveWidget}
|
||||
widgetKey="testKey"
|
||||
/>
|
||||
);
|
||||
fireEvent.click(getByTestId('remove-widget-button'));
|
||||
|
||||
expect(handleRemoveWidget).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders list item when data is not empty', async () => {
|
||||
await act(async () => {
|
||||
render(<RecentlyViewed widgetKey={mockProp.widgetKey} />);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('Recently Viewed-test')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -1,161 +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 { CloseOutlined, DragOutlined } from '@ant-design/icons';
|
||||
import { Button, Card, Col, Row, Space, Typography } from 'antd';
|
||||
import { isEmpty, isUndefined } from 'lodash';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ReactComponent as RecentlyViewedEmptyIcon } from '../../../../assets/svg/recently-viewed-no-data-placeholder.svg';
|
||||
import { RECENTLY_VIEWED } from '../../../../constants/docs.constants';
|
||||
import { ERROR_PLACEHOLDER_TYPE, SIZE } from '../../../../enums/common.enum';
|
||||
import { EntityReference } from '../../../../generated/type/entityReference';
|
||||
import { useCurrentUserPreferences } from '../../../../hooks/currentUserStore/useCurrentUserStore';
|
||||
import { WidgetCommonProps } from '../../../../pages/CustomizablePage/CustomizablePage.interface';
|
||||
import { prepareLabel } from '../../../../utils/CommonUtils';
|
||||
import entityUtilClassBase from '../../../../utils/EntityUtilClassBase';
|
||||
import { getEntityName } from '../../../../utils/EntityUtils';
|
||||
import searchClassBase from '../../../../utils/SearchClassBase';
|
||||
import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||
import './recently-viewed.less';
|
||||
|
||||
const RecentlyViewed = ({
|
||||
isEditView,
|
||||
handleRemoveWidget,
|
||||
widgetKey,
|
||||
}: WidgetCommonProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
preferences: { recentlyViewed },
|
||||
} = useCurrentUserPreferences();
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (recentlyViewed.length) {
|
||||
return recentlyViewed
|
||||
.map((item) => {
|
||||
return {
|
||||
serviceType: item.serviceType,
|
||||
name: item.displayName || prepareLabel(item.entityType, item.fqn),
|
||||
fullyQualifiedName: item.fqn,
|
||||
type: item.entityType,
|
||||
id: item.id,
|
||||
};
|
||||
})
|
||||
.filter((item) => item.name);
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [recentlyViewed]);
|
||||
|
||||
const handleCloseClick = useCallback(() => {
|
||||
!isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey);
|
||||
}, [widgetKey]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
className="recently-viewed-widget-container card-widget"
|
||||
data-testid="recently-viewed-widget">
|
||||
<>
|
||||
<Row justify="space-between">
|
||||
<Col>
|
||||
<Typography.Paragraph className="font-medium m-b-sm">
|
||||
{t('label.recent-views')}
|
||||
</Typography.Paragraph>
|
||||
</Col>
|
||||
{isEditView && (
|
||||
<Col>
|
||||
<Space>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
{isEmpty(data) ? (
|
||||
<div className="flex-center h-full">
|
||||
<ErrorPlaceHolder
|
||||
className="border-none"
|
||||
icon={
|
||||
<RecentlyViewedEmptyIcon
|
||||
height={SIZE.X_SMALL}
|
||||
width={SIZE.X_SMALL}
|
||||
/>
|
||||
}
|
||||
type={ERROR_PLACEHOLDER_TYPE.CUSTOM}>
|
||||
<Typography.Paragraph>
|
||||
{t('message.no-recently-viewed-date')}
|
||||
</Typography.Paragraph>
|
||||
<a
|
||||
className="link-title"
|
||||
href={RECENTLY_VIEWED}
|
||||
rel="noreferrer"
|
||||
target="_blank">
|
||||
{t('label.learn-more')}
|
||||
</a>
|
||||
</ErrorPlaceHolder>
|
||||
</div>
|
||||
) : (
|
||||
<div className="entity-list-body no-scrollbar">
|
||||
{data.map((item) => {
|
||||
return (
|
||||
<div
|
||||
className="right-panel-list-item flex items-center justify-between"
|
||||
data-testid={`Recently Viewed-${getEntityName(item)}`}
|
||||
key={item.id}>
|
||||
<div className=" flex items-center">
|
||||
<Link
|
||||
className=""
|
||||
to={entityUtilClassBase.getEntityLink(
|
||||
item.type || '',
|
||||
item.fullyQualifiedName as string
|
||||
)}>
|
||||
<Button
|
||||
className="entity-button flex-center p-0 m--ml-1"
|
||||
icon={
|
||||
<div className="entity-button-icon m-r-xs">
|
||||
{searchClassBase.getEntityIcon(item.type ?? '')}
|
||||
</div>
|
||||
}
|
||||
title={getEntityName(
|
||||
item as unknown as EntityReference
|
||||
)}
|
||||
type="text">
|
||||
<Typography.Text
|
||||
className="w-72 text-left text-xs"
|
||||
ellipsis={{ tooltip: true }}>
|
||||
{getEntityName(item)}
|
||||
</Typography.Text>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecentlyViewed;
|
||||
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 url('../../../../styles/variables.less');
|
||||
|
||||
.right-panel-heading {
|
||||
margin-bottom: 8px !important;
|
||||
color: @text-grey-muted;
|
||||
}
|
||||
|
||||
.right-panel-list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.right-panel-list-item {
|
||||
.entity-button-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
svg {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recently-viewed-widget-container {
|
||||
.ant-card-body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@ -346,6 +346,7 @@ const TotalDataAssetsWidget = ({
|
||||
return (
|
||||
<WidgetWrapper
|
||||
dataLength={graphData.length > 0 ? graphData.length : 10}
|
||||
dataTestId="KnowledgePanel.TotalAssets"
|
||||
header={widgetHeader}
|
||||
loading={isLoading}>
|
||||
<div className="total-data-assets-widget-container">
|
||||
|
||||
@ -255,6 +255,7 @@ const MyDataPage = () => {
|
||||
<CustomiseLandingPageHeader
|
||||
overlappedContainer
|
||||
backgroundColor={backgroundColor}
|
||||
dataTestId="landing-page-header"
|
||||
hideCustomiseButton={!selectedPersona}
|
||||
onHomePage
|
||||
onBackgroundColorUpdate={handleBackgroundColorUpdate}
|
||||
|
||||
@ -138,7 +138,11 @@ export const getAlertEventsFromId = async ({
|
||||
params,
|
||||
}: {
|
||||
id: string;
|
||||
params?: { status?: TypedEventStatus; limit?: number; paginationOffset?: number };
|
||||
params?: {
|
||||
status?: TypedEventStatus;
|
||||
limit?: number;
|
||||
paginationOffset?: number;
|
||||
};
|
||||
}) => {
|
||||
const response = await axiosClient.get<PagingResponse<TypedEvent[]>>(
|
||||
`${BASE_URL}/id/${id}/listEvents`,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user