mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-14 01:40:08 +00:00
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:
parent
c7f50c9376
commit
802c922ec8
@ -35,6 +35,8 @@ const test = base.extend<{ page: Page }>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
base.beforeAll('Setup pre-requests', async ({ browser }) => {
|
base.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||||
await adminUser.create(apiContext);
|
await adminUser.create(apiContext);
|
||||||
await adminUser.setAdminRole(apiContext);
|
await adminUser.setAdminRole(apiContext);
|
||||||
@ -43,6 +45,8 @@ base.beforeAll('Setup pre-requests', async ({ browser }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
base.afterAll('Cleanup', async ({ browser }) => {
|
base.afterAll('Cleanup', async ({ browser }) => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||||
await adminUser.delete(apiContext);
|
await adminUser.delete(apiContext);
|
||||||
await persona.delete(apiContext);
|
await persona.delete(apiContext);
|
||||||
@ -50,7 +54,7 @@ base.afterAll('Cleanup', async ({ browser }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Curated Assets', () => {
|
test.describe('Curated Assets', () => {
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeAll(async ({ page }) => {
|
||||||
test.slow(true);
|
test.slow(true);
|
||||||
|
|
||||||
await redirectToHomePage(page);
|
await redirectToHomePage(page);
|
||||||
@ -93,7 +97,7 @@ test.describe('Curated Assets', () => {
|
|||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
ruleLocator.locator('.rule--operator .ant-select'),
|
ruleLocator.locator('.rule--operator .ant-select'),
|
||||||
'!='
|
'Not in'
|
||||||
);
|
);
|
||||||
|
|
||||||
await selectOption(
|
await selectOption(
|
||||||
|
|||||||
@ -18,6 +18,9 @@ import { redirectToHomePage } from '../../utils/common';
|
|||||||
const user = new UserClass();
|
const user = new UserClass();
|
||||||
|
|
||||||
const validateTourSteps = async (page: Page) => {
|
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');
|
await expect(page.locator(`[data-tour-elem="badge"]`)).toHaveText('1');
|
||||||
|
|
||||||
// step 1
|
// step 1
|
||||||
@ -30,8 +33,8 @@ const validateTourSteps = async (page: Page) => {
|
|||||||
|
|
||||||
await expect(page.locator(`[data-tour-elem="badge"]`)).toHaveText('3');
|
await expect(page.locator(`[data-tour-elem="badge"]`)).toHaveText('3');
|
||||||
|
|
||||||
await page.getByTestId('customise-searchbox').fill('dim_a');
|
await page.getByTestId('searchBox').fill('dim_a');
|
||||||
await page.getByTestId('customise-searchbox').press('Enter');
|
await page.getByTestId('searchBox').press('Enter');
|
||||||
|
|
||||||
await expect(page.locator(`[data-tour-elem="badge"]`)).toHaveText('4');
|
await expect(page.locator(`[data-tour-elem="badge"]`)).toHaveText('4');
|
||||||
|
|
||||||
@ -108,7 +111,7 @@ const validateTourSteps = async (page: Page) => {
|
|||||||
await page.getByTestId('saveButton').click();
|
await page.getByTestId('saveButton').click();
|
||||||
};
|
};
|
||||||
|
|
||||||
test.describe.skip('Tour should work properly', () => {
|
test.describe('Tour should work properly', () => {
|
||||||
test.beforeAll(async ({ browser }) => {
|
test.beforeAll(async ({ browser }) => {
|
||||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||||
await user.create(apiContext);
|
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.locator('[data-testid="help-icon"]').click();
|
||||||
await page.getByRole('link', { name: 'Tour', exact: true }).click();
|
await page.getByRole('link', { name: 'Tour', exact: true }).click();
|
||||||
await page.waitForURL('**/tour');
|
await page.waitForURL('**/tour');
|
||||||
|
|
||||||
|
await page.waitForSelector('#feedWidgetData');
|
||||||
|
|
||||||
await validateTourSteps(page);
|
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.getByText('Take a product tour to get started!').click();
|
||||||
await page.waitForURL('**/tour');
|
await page.waitForURL('**/tour');
|
||||||
|
|
||||||
|
await page.waitForSelector('#feedWidgetData');
|
||||||
|
|
||||||
await validateTourSteps(page);
|
await validateTourSteps(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Tour should work from URL directly', async ({ page }) => {
|
test('Tour should work from URL directly', async ({ page }) => {
|
||||||
await expect(page.getByTestId('global-search-selector')).toBeVisible();
|
|
||||||
|
|
||||||
await page.goto('/tour');
|
await page.goto('/tour');
|
||||||
await page.waitForURL('**/tour');
|
await page.waitForURL('**/tour');
|
||||||
|
|
||||||
|
await page.waitForSelector('#feedWidgetData');
|
||||||
|
|
||||||
await validateTourSteps(page);
|
await validateTourSteps(page);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -178,7 +178,7 @@ const MyFeedWidgetInternal = ({
|
|||||||
dataTestId="KnowledgePanel.ActivityFeed"
|
dataTestId="KnowledgePanel.ActivityFeed"
|
||||||
header={widgetHeader}
|
header={widgetHeader}
|
||||||
loading={loading}>
|
loading={loading}>
|
||||||
<div className="feed-widget-container">
|
<div className="feed-widget-container" id="feedWidgetData">
|
||||||
<div className="feed-content flex-1">
|
<div className="feed-content flex-1">
|
||||||
{widgetBody}
|
{widgetBody}
|
||||||
<WidgetFooter
|
<WidgetFooter
|
||||||
|
|||||||
@ -312,6 +312,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-menu {
|
.top-menu {
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Tour from '../../components/AppTour/Tour';
|
import Tour from '../../components/AppTour/Tour';
|
||||||
import { TOUR_SEARCH_TERM } from '../../constants/constants';
|
import { TOUR_SEARCH_TERM } from '../../constants/constants';
|
||||||
@ -31,6 +31,7 @@ const TourPage = () => {
|
|||||||
updateTourSearch,
|
updateTourSearch,
|
||||||
} = useTourProvider();
|
} = useTourProvider();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [isTourReady, setIsTourReady] = useState(false);
|
||||||
|
|
||||||
const clearSearchTerm = () => {
|
const clearSearchTerm = () => {
|
||||||
updateTourSearch('');
|
updateTourSearch('');
|
||||||
@ -38,6 +39,21 @@ const TourPage = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateIsTourOpen(true);
|
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(() => {
|
const currentPageComponent = useMemo(() => {
|
||||||
@ -58,15 +74,17 @@ const TourPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tour
|
|
||||||
steps={getTourSteps({
|
|
||||||
searchTerm: TOUR_SEARCH_TERM,
|
|
||||||
clearSearchTerm,
|
|
||||||
updateActiveTab,
|
|
||||||
updateTourPage,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{currentPageComponent}
|
{currentPageComponent}
|
||||||
|
{isTourReady && (
|
||||||
|
<Tour
|
||||||
|
steps={getTourSteps({
|
||||||
|
searchTerm: TOUR_SEARCH_TERM,
|
||||||
|
clearSearchTerm,
|
||||||
|
updateActiveTab,
|
||||||
|
updateTourPage,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,11 +17,18 @@ import TourPage from './TourPage.component';
|
|||||||
|
|
||||||
const mockUseTourProvider = {
|
const mockUseTourProvider = {
|
||||||
updateIsTourOpen: jest.fn(),
|
updateIsTourOpen: jest.fn(),
|
||||||
currentTourPage: '',
|
currentTourPage: CurrentTourPageType.MY_DATA_PAGE,
|
||||||
updateActiveTab: jest.fn(),
|
updateActiveTab: jest.fn(),
|
||||||
updateTourPage: jest.fn(),
|
updateTourPage: jest.fn(),
|
||||||
updateTourSearch: jest.fn(),
|
updateTourSearch: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockQuerySelector = jest.fn();
|
||||||
|
Object.defineProperty(document, 'querySelector', {
|
||||||
|
value: mockQuerySelector,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock('../../context/TourProvider/TourProvider', () => ({
|
jest.mock('../../context/TourProvider/TourProvider', () => ({
|
||||||
useTourProvider: jest.fn().mockImplementation(() => mockUseTourProvider),
|
useTourProvider: jest.fn().mockImplementation(() => mockUseTourProvider),
|
||||||
}));
|
}));
|
||||||
@ -53,6 +60,22 @@ jest.mock('../../utils/TourUtils', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe('TourPage component', () => {
|
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 () => {
|
it('should render correctly', async () => {
|
||||||
render(<TourPage />);
|
render(<TourPage />);
|
||||||
|
|
||||||
@ -69,7 +92,8 @@ describe('TourPage component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('MyDataPage Component should be visible, if currentTourPage is myDataPage', async () => {
|
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,
|
...mockUseTourProvider,
|
||||||
currentTourPage: CurrentTourPageType.MY_DATA_PAGE,
|
currentTourPage: CurrentTourPageType.MY_DATA_PAGE,
|
||||||
}));
|
}));
|
||||||
@ -79,7 +103,8 @@ describe('TourPage component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('ExplorePage Component should be visible, if currentTourPage is explorePage', async () => {
|
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,
|
...mockUseTourProvider,
|
||||||
currentTourPage: CurrentTourPageType.EXPLORE_PAGE,
|
currentTourPage: CurrentTourPageType.EXPLORE_PAGE,
|
||||||
}));
|
}));
|
||||||
@ -91,7 +116,8 @@ describe('TourPage component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('TableDetailsPage Component should be visible, if currentTourPage is datasetPage', async () => {
|
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,
|
...mockUseTourProvider,
|
||||||
currentTourPage: CurrentTourPageType.DATASET_PAGE,
|
currentTourPage: CurrentTourPageType.DATASET_PAGE,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export const getTourSteps = ({
|
|||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
selector: '#feedData',
|
selector: '#feedWidgetData',
|
||||||
stepInteraction: false,
|
stepInteraction: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user