fix(ui): tour functionality (#22711)

* fix: hide scrollbar from sidebar

* fix feed id

* fix tour

* fix tour test

* monir fix

* remove redundunt myData tests

* fix tourpage useEffect

* fix curated asset test
This commit is contained in:
Pranita Fulsundar 2025-08-04 12:17:25 +05:30 committed by GitHub
parent c7f50c9376
commit 802c922ec8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 84 additions and 144 deletions

View File

@ -35,6 +35,8 @@ const test = base.extend<{ page: Page }>({
});
base.beforeAll('Setup pre-requests', async ({ browser }) => {
test.slow(true);
const { afterAction, apiContext } = await performAdminLogin(browser);
await adminUser.create(apiContext);
await adminUser.setAdminRole(apiContext);
@ -43,6 +45,8 @@ base.beforeAll('Setup pre-requests', async ({ browser }) => {
});
base.afterAll('Cleanup', async ({ browser }) => {
test.slow(true);
const { afterAction, apiContext } = await performAdminLogin(browser);
await adminUser.delete(apiContext);
await persona.delete(apiContext);
@ -50,7 +54,7 @@ base.afterAll('Cleanup', async ({ browser }) => {
});
test.describe('Curated Assets', () => {
test.beforeEach(async ({ page }) => {
test.beforeAll(async ({ page }) => {
test.slow(true);
await redirectToHomePage(page);
@ -93,7 +97,7 @@ test.describe('Curated Assets', () => {
await selectOption(
page,
ruleLocator.locator('.rule--operator .ant-select'),
'!='
'Not in'
);
await selectOption(

View File

@ -18,6 +18,9 @@ import { redirectToHomePage } from '../../utils/common';
const user = new UserClass();
const validateTourSteps = async (page: Page) => {
await page.waitForTimeout(1000);
await page.waitForSelector(`[data-tour-elem="badge"]`);
await expect(page.locator(`[data-tour-elem="badge"]`)).toHaveText('1');
// step 1
@ -30,8 +33,8 @@ const validateTourSteps = async (page: Page) => {
await expect(page.locator(`[data-tour-elem="badge"]`)).toHaveText('3');
await page.getByTestId('customise-searchbox').fill('dim_a');
await page.getByTestId('customise-searchbox').press('Enter');
await page.getByTestId('searchBox').fill('dim_a');
await page.getByTestId('searchBox').press('Enter');
await expect(page.locator(`[data-tour-elem="badge"]`)).toHaveText('4');
@ -108,7 +111,7 @@ const validateTourSteps = async (page: Page) => {
await page.getByTestId('saveButton').click();
};
test.describe.skip('Tour should work properly', () => {
test.describe('Tour should work properly', () => {
test.beforeAll(async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await user.create(apiContext);
@ -130,6 +133,9 @@ test.describe.skip('Tour should work properly', () => {
await page.locator('[data-testid="help-icon"]').click();
await page.getByRole('link', { name: 'Tour', exact: true }).click();
await page.waitForURL('**/tour');
await page.waitForSelector('#feedWidgetData');
await validateTourSteps(page);
});
@ -141,14 +147,17 @@ test.describe.skip('Tour should work properly', () => {
await page.getByText('Take a product tour to get started!').click();
await page.waitForURL('**/tour');
await page.waitForSelector('#feedWidgetData');
await validateTourSteps(page);
});
test('Tour should work from URL directly', async ({ page }) => {
await expect(page.getByTestId('global-search-selector')).toBeVisible();
await page.goto('/tour');
await page.waitForURL('**/tour');
await page.waitForSelector('#feedWidgetData');
await validateTourSteps(page);
});
});

View File

@ -1,122 +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 { expect, Page, test as base } from '@playwright/test';
import { TableClass } from '../../support/entity/TableClass';
import { UserClass } from '../../support/user/UserClass';
import { performAdminLogin } from '../../utils/admin';
import { redirectToHomePage, removeLandingBanner } from '../../utils/common';
import { verifyEntities } from '../../utils/myData';
const user = new UserClass();
const TableEntities = Array(20)
.fill(undefined)
.map(() => new TableClass());
const test = base.extend<{ page: Page }>({
page: async ({ browser }, use) => {
const Page = await browser.newPage();
await user.login(Page);
await use(Page);
await Page.close();
},
});
test.describe.serial('My Data page', () => {
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await user.create(apiContext);
const tablePromises = TableEntities.map(async (table) => {
await table.create(apiContext);
await table.patch({
apiContext,
patchData: [
{
op: 'add',
path: '/owners/0',
value: {
id: user.responseData.id,
type: 'user',
deleted: false,
displayName: user.responseData.displayName,
fullyQualifiedName: user.responseData.fullyQualifiedName,
href: user.responseData['href'] ?? '',
name: user.responseData.name,
},
},
],
});
await table.followTable(apiContext, user.responseData.id);
});
await Promise.all(tablePromises);
await afterAction();
});
test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await user.delete(apiContext);
await Promise.all(TableEntities.map((table) => table.delete(apiContext)));
await afterAction();
});
test.beforeEach('Visit entity details page', async ({ page }) => {
await redirectToHomePage(page);
await removeLandingBanner(page);
});
test.skip('Verify my data widget', async ({ page }) => {
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
// Verify total count
await expect(
page.locator('[data-testid="my-data-total-count"]')
).toContainText('(20)');
await page
.locator('[data-testid="my-data-widget"] [data-testid="view-all-link"]')
.click();
await page.waitForLoadState('networkidle');
await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
// Verify entities
await verifyEntities(
page,
'/api/v1/search/query?q=*&index=all&from=0&size=25*',
TableEntities
);
});
test.skip('Verify following widget', async ({ page }) => {
// Verify total count
await expect(
page.locator('[data-testid="following-data-total-count"]')
).toContainText('(20)');
await page.locator('[data-testid="following-data"]').click();
// Verify entities
await verifyEntities(
page,
'/api/v1/search/query?q=*followers:*&index=all&from=0&size=25*',
TableEntities
);
});
});

View File

@ -178,7 +178,7 @@ const MyFeedWidgetInternal = ({
dataTestId="KnowledgePanel.ActivityFeed"
header={widgetHeader}
loading={loading}>
<div className="feed-widget-container">
<div className="feed-widget-container" id="feedWidgetData">
<div className="feed-content flex-1">
{widgetBody}
<WidgetFooter

View File

@ -312,6 +312,11 @@
display: flex;
flex-direction: column;
overflow-y: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.top-menu {

View File

@ -11,7 +11,7 @@
* limitations under the License.
*/
import { useEffect, useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import Tour from '../../components/AppTour/Tour';
import { TOUR_SEARCH_TERM } from '../../constants/constants';
@ -31,6 +31,7 @@ const TourPage = () => {
updateTourSearch,
} = useTourProvider();
const { t } = useTranslation();
const [isTourReady, setIsTourReady] = useState(false);
const clearSearchTerm = () => {
updateTourSearch('');
@ -38,6 +39,21 @@ const TourPage = () => {
useEffect(() => {
updateIsTourOpen(true);
let attempts = 0;
const maxAttempts = 10;
const waitForElement = () => {
const el = document.querySelector('#feedWidgetData');
if (el) {
setIsTourReady(true);
} else if (attempts < maxAttempts) {
attempts++;
setTimeout(waitForElement, 100);
}
};
waitForElement();
}, []);
const currentPageComponent = useMemo(() => {
@ -58,15 +74,17 @@ const TourPage = () => {
return (
<>
<Tour
steps={getTourSteps({
searchTerm: TOUR_SEARCH_TERM,
clearSearchTerm,
updateActiveTab,
updateTourPage,
})}
/>
{currentPageComponent}
{isTourReady && (
<Tour
steps={getTourSteps({
searchTerm: TOUR_SEARCH_TERM,
clearSearchTerm,
updateActiveTab,
updateTourPage,
})}
/>
)}
</>
);
};

View File

@ -17,11 +17,18 @@ import TourPage from './TourPage.component';
const mockUseTourProvider = {
updateIsTourOpen: jest.fn(),
currentTourPage: '',
currentTourPage: CurrentTourPageType.MY_DATA_PAGE,
updateActiveTab: jest.fn(),
updateTourPage: jest.fn(),
updateTourSearch: jest.fn(),
};
const mockQuerySelector = jest.fn();
Object.defineProperty(document, 'querySelector', {
value: mockQuerySelector,
writable: true,
});
jest.mock('../../context/TourProvider/TourProvider', () => ({
useTourProvider: jest.fn().mockImplementation(() => mockUseTourProvider),
}));
@ -53,6 +60,22 @@ jest.mock('../../utils/TourUtils', () => ({
}));
describe('TourPage component', () => {
beforeEach(() => {
jest.useFakeTimers();
mockQuerySelector.mockImplementation((selector) => {
if (selector === '#feedWidgetData') {
return document.createElement('div');
}
return null;
});
});
afterEach(() => {
jest.clearAllMocks();
jest.useRealTimers();
});
it('should render correctly', async () => {
render(<TourPage />);
@ -69,7 +92,8 @@ describe('TourPage component', () => {
});
it('MyDataPage Component should be visible, if currentTourPage is myDataPage', async () => {
(useTourProvider as jest.Mock).mockImplementationOnce(() => ({
(useTourProvider as jest.Mock).mockReset();
(useTourProvider as jest.Mock).mockImplementation(() => ({
...mockUseTourProvider,
currentTourPage: CurrentTourPageType.MY_DATA_PAGE,
}));
@ -79,7 +103,8 @@ describe('TourPage component', () => {
});
it('ExplorePage Component should be visible, if currentTourPage is explorePage', async () => {
(useTourProvider as jest.Mock).mockImplementationOnce(() => ({
(useTourProvider as jest.Mock).mockReset();
(useTourProvider as jest.Mock).mockImplementation(() => ({
...mockUseTourProvider,
currentTourPage: CurrentTourPageType.EXPLORE_PAGE,
}));
@ -91,7 +116,8 @@ describe('TourPage component', () => {
});
it('TableDetailsPage Component should be visible, if currentTourPage is datasetPage', async () => {
(useTourProvider as jest.Mock).mockImplementationOnce(() => ({
(useTourProvider as jest.Mock).mockReset();
(useTourProvider as jest.Mock).mockImplementation(() => ({
...mockUseTourProvider,
currentTourPage: CurrentTourPageType.DATASET_PAGE,
}));

View File

@ -41,7 +41,7 @@ export const getTourSteps = ({
/>
</p>
),
selector: '#feedData',
selector: '#feedWidgetData',
stepInteraction: false,
},
{