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:
Pranita Fulsundar 2025-08-02 15:23:43 +05:30 committed by GitHub
parent 8e58620f58
commit ebcfd95164
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1035 additions and 1251 deletions

View File

@ -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();
});
});

View File

@ -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
);
}
});
}
});

View File

@ -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);
}
);
});
});

View File

@ -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
);
});
});

View File

@ -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();
};

View File

@ -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')

View File

@ -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;
};

View File

@ -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,

View File

@ -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}

View File

@ -233,6 +233,7 @@ function CustomizeMyData({
overlappedContainer
addedWidgetsList={addedWidgetsList}
backgroundColor={backgroundColor}
dataTestId="customise-landing-page-header"
handleAddWidget={handleMainPanelAddWidget}
onBackgroundColorUpdate={handleBackgroundColorUpdate}
/>

View File

@ -56,7 +56,7 @@ function EmptyWidgetPlaceholderV1({
return (
<WidgetWrapper
className="empty-widget-placeholder-v1"
data-testid={widgetKey}>
dataTestId={widgetKey}>
{widgetContent}
</WidgetWrapper>
);

View File

@ -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);

View File

@ -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

View File

@ -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">

View File

@ -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>
))}

View File

@ -336,6 +336,7 @@ const MyDataWidgetInternal = ({
return (
<WidgetWrapper
dataLength={data.length > 0 ? data.length : 10}
dataTestId="KnowledgePanel.MyData"
header={widgetHeader}
loading={isLoading}>
{widgetContent}

View File

@ -321,6 +321,7 @@ function FollowingWidget({
return (
<WidgetWrapper
dataLength={followedData.length !== 0 ? followedData.length : 10}
dataTestId="KnowledgePanel.Following"
header={widgetHeader}
loading={isLoadingOwnedData}>
{WidgetContent}

View File

@ -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,

View File

@ -20,6 +20,7 @@ const mockProps = {
loading: false,
dataLength: 5,
className: 'custom-wrapper-class',
dataTestId: 'widget-wrapper',
};
const renderWidgetWrapper = (props = {}) => {

View File

@ -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}

View File

@ -465,6 +465,7 @@ const CuratedAssetsWidget = ({
<>
<WidgetWrapper
dataLength={data.length !== 0 ? data.length : 10}
dataTestId="KnowledgePanel.CuratedAssets"
header={widgetHeader}
loading={isLoading}>
{widgetContent}

View File

@ -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}

View File

@ -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')

View File

@ -206,6 +206,7 @@ const DataAssetsWidget = ({
return (
<WidgetWrapper
dataLength={services.length !== 0 ? services.length : 10}
dataTestId="KnowledgePanel.DataAssets"
header={widgetHeader}
loading={loading}>
{widgetContent}

View File

@ -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
});

View File

@ -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 ? (

View File

@ -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;

View File

@ -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'
);
});
});

View File

@ -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">

View File

@ -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 () => {

View File

@ -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', () => {

View File

@ -177,6 +177,7 @@ const MyTaskWidget = ({
return (
<WidgetWrapper
dataLength={entityThread.length > 0 ? entityThread.length : 10}
dataTestId="KnowledgePanel.MyTask"
header={widgetHeader}
loading={loading}>
{widgetContent}

View File

@ -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();
});
});

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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">

View File

@ -255,6 +255,7 @@ const MyDataPage = () => {
<CustomiseLandingPageHeader
overlappedContainer
backgroundColor={backgroundColor}
dataTestId="landing-page-header"
hideCustomiseButton={!selectedPersona}
onHomePage
onBackgroundColorUpdate={handleBackgroundColorUpdate}

View File

@ -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`,