From ee9a7c7dee1f38bbf1b58af96c735334293a0030 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Thu, 5 Sep 2024 23:09:40 +0530 Subject: [PATCH 1/4] test: migrate recently viewed spec to playwright --- .../e2e/Features/RecentlyViewed.spec.ts | 141 ------------------ .../e2e/Features/RecentlyViewed.spec.ts | 67 +++++++++ 2 files changed, 67 insertions(+), 141 deletions(-) delete mode 100644 openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/RecentlyViewed.spec.ts create mode 100644 openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/RecentlyViewed.spec.ts b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/RecentlyViewed.spec.ts deleted file mode 100644 index 9ca58b4a4d91..000000000000 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/RecentlyViewed.spec.ts +++ /dev/null @@ -1,141 +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 { interceptURL, verifyResponseStatusCode } from '../../common/common'; -import { - createEntityTable, - createSingleLevelEntity, - hardDeleteService, -} from '../../common/EntityUtils'; -import { visitEntityDetailsPage } from '../../common/Utils/Entity'; -import { getToken } from '../../common/Utils/LocalStorage'; -import { - DATABASE_SERVICE, - SINGLE_LEVEL_SERVICE, - STORED_PROCEDURE_DETAILS, - VISIT_ENTITIES_DATA, -} from '../../constants/EntityConstant'; -import { SERVICE_CATEGORIES } from '../../constants/service.constants'; - -// Update list if we support this for other entities too -const RECENTLY_VIEW_ENTITIES = [ - VISIT_ENTITIES_DATA.table, - VISIT_ENTITIES_DATA.dashboard, - VISIT_ENTITIES_DATA.topic, - VISIT_ENTITIES_DATA.pipeline, - VISIT_ENTITIES_DATA.mlmodel, - // ES issue - // VISIT_ENTITIES_DATA.storedProcedure, -]; - -describe('Recently viwed data assets', { tags: 'DataAssets' }, () => { - before(() => { - cy.login(); - cy.getAllLocalStorage().then((data) => { - const token = getToken(data); - - createEntityTable({ - token, - ...DATABASE_SERVICE, - tables: [DATABASE_SERVICE.entity], - }); - SINGLE_LEVEL_SERVICE.forEach((data) => { - createSingleLevelEntity({ - token, - ...data, - entity: [data.entity], - }); - }); - - // creating stored procedure - cy.request({ - method: 'POST', - url: `/api/v1/storedProcedures`, - headers: { Authorization: `Bearer ${token}` }, - body: STORED_PROCEDURE_DETAILS, - }); - }); - }); - - after(() => { - cy.login(); - cy.getAllLocalStorage().then((data) => { - const token = getToken(data); - - hardDeleteService({ - token, - serviceFqn: DATABASE_SERVICE.service.name, - serviceType: SERVICE_CATEGORIES.DATABASE_SERVICES, - }); - SINGLE_LEVEL_SERVICE.forEach((data) => { - hardDeleteService({ - token, - serviceFqn: data.service.name, - serviceType: data.serviceType, - }); - }); - }); - }); - - beforeEach(() => { - cy.login(); - }); - - it('recently view section should be present', () => { - cy.get('[data-testid="recently-viewed-widget"]') - .scrollIntoView() - .should('be.visible'); - - cy.get( - `[data-testid="recently-viewed-widget"] .right-panel-list-item` - ).should('have.length', 0); - }); - - it(`recently view section should have at max list of 5 entity`, () => { - RECENTLY_VIEW_ENTITIES.map((entity, index) => { - visitEntityDetailsPage({ - term: entity.term, - serviceName: entity.serviceName, - entity: entity.entity, - }); - cy.get('[data-testid="entity-header-display-name"]').should( - 'contain', - entity.term - ); - interceptURL( - 'GET', - '/api/v1/feed?type=Announcement&activeAnnouncement=true', - 'getAnnouncements' - ); - - cy.clickOnLogo(); - verifyResponseStatusCode('@getAnnouncements', 200); - - // need to add manual wait as we are dependant on local storage for recently view data - cy.wait(500); - cy.get('[data-testid="recently-viewed-widget"]') - .scrollIntoView() - .should('be.visible'); - cy.get( - `[data-testid="recently-viewed-widget"] [title="${entity.displayName}"]`, - { timeout: 10000 } - ) - .scrollIntoView() - .should('be.visible'); - - // Checking count since we will only show max 5 not more than that - cy.get( - `[data-testid="recently-viewed-widget"] .right-panel-list-item` - ).should('have.length', index + 1); - }); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts new file mode 100644 index 000000000000..66323d4ed87d --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts @@ -0,0 +1,67 @@ +/* + * 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, test } from '@playwright/test'; +import { ApiEndpointClass } from '../../support/entity/ApiEndpointClass'; +import { ContainerClass } from '../../support/entity/ContainerClass'; +import { DashboardClass } from '../../support/entity/DashboardClass'; +import { DashboardDataModelClass } from '../../support/entity/DashboardDataModelClass'; +import { MlModelClass } from '../../support/entity/MlModelClass'; +import { PipelineClass } from '../../support/entity/PipelineClass'; +import { SearchIndexClass } from '../../support/entity/SearchIndexClass'; +import { StoredProcedureClass } from '../../support/entity/StoredProcedureClass'; +import { TableClass } from '../../support/entity/TableClass'; +import { TopicClass } from '../../support/entity/TopicClass'; +import { getApiContext, redirectToHomePage } from '../../utils/common'; + +const entities = [ + ApiEndpointClass, + TableClass, + StoredProcedureClass, + DashboardClass, + PipelineClass, + TopicClass, + MlModelClass, + ContainerClass, + SearchIndexClass, + DashboardDataModelClass, +] as const; + +// use the admin user to login +test.use({ storageState: 'playwright/.auth/admin.json' }); + +test.describe('Recently viewed data assets', () => { + test.beforeEach(async ({ page }) => { + await redirectToHomePage(page); + }); + + entities.forEach((Entity) => { + const entity = new Entity(); + + test(`${entity.getType()} should be added to the recently viewed list`, async ({ + page, + }) => { + const { afterAction, apiContext } = await getApiContext(page); + await entity.create(apiContext); + await entity.visitEntityPage(page); + await redirectToHomePage(page); + + const selector = `[data-testid="recently-viewed-widget"] [title="${entity.entity.name}"]`; + + await expect(page.locator(selector)).toBeVisible(); + + await entity.delete(apiContext); + + await afterAction(); + }); + }); +}); From d8070dff6ef8fb136e422fe68c7392cd5f8861ca Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Fri, 6 Sep 2024 14:09:37 +0530 Subject: [PATCH 2/4] fix: test --- .../e2e/Features/RecentlyViewed.spec.ts | 73 ++++++++++++------- .../support/entity/ContainerClass.ts | 5 +- .../support/entity/DashboardClass.ts | 5 +- .../support/entity/DashboardDataModelClass.ts | 5 +- .../playwright/support/entity/MlModelClass.ts | 5 +- .../support/entity/PipelineClass.ts | 5 +- .../support/entity/SearchIndexClass.ts | 2 +- .../DataModelPage/DataModelPage.component.tsx | 13 +++- 8 files changed, 76 insertions(+), 37 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts index 66323d4ed87d..56ab8f38b3c9 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts @@ -21,19 +21,19 @@ import { SearchIndexClass } from '../../support/entity/SearchIndexClass'; import { StoredProcedureClass } from '../../support/entity/StoredProcedureClass'; import { TableClass } from '../../support/entity/TableClass'; import { TopicClass } from '../../support/entity/TopicClass'; -import { getApiContext, redirectToHomePage } from '../../utils/common'; +import { createNewPage, redirectToHomePage } from '../../utils/common'; const entities = [ - ApiEndpointClass, - TableClass, - StoredProcedureClass, - DashboardClass, - PipelineClass, - TopicClass, - MlModelClass, - ContainerClass, - SearchIndexClass, - DashboardDataModelClass, + new ApiEndpointClass(), + new TableClass(), + new StoredProcedureClass(), + new DashboardClass(), + new PipelineClass(), + new TopicClass(), + new MlModelClass(), + new ContainerClass(), + new SearchIndexClass(), + new DashboardDataModelClass(), ] as const; // use the admin user to login @@ -44,24 +44,47 @@ test.describe('Recently viewed data assets', () => { await redirectToHomePage(page); }); - entities.forEach((Entity) => { - const entity = new Entity(); - - test(`${entity.getType()} should be added to the recently viewed list`, async ({ - page, - }) => { - const { afterAction, apiContext } = await getApiContext(page); + test.beforeAll(async ({ browser }) => { + const { afterAction, apiContext } = await createNewPage(browser); + for await (const entity of entities) { await entity.create(apiContext); - await entity.visitEntityPage(page); - await redirectToHomePage(page); + } + await afterAction(); + }); - const selector = `[data-testid="recently-viewed-widget"] [title="${entity.entity.name}"]`; + test.afterAll(async ({ browser }) => { + const { afterAction, apiContext } = await createNewPage(browser); + for await (const entity of entities) { + await entity.delete(apiContext); + } + await afterAction(); + }); - await expect(page.locator(selector)).toBeVisible(); + test('Recently viewed widget should be visible on the home page', async ({ + page, + }) => { + test.slow(true); - await entity.delete(apiContext); + for await (const entity of entities) { + await test.step( + `Check ${entity.getType()} in recently viewed widget `, + async () => { + const entityPromise = page.waitForResponse('/api/v1/*/name/*'); + await entity.visitEntityPage(page); + + await entityPromise; + + await page.waitForSelector(`[data-testid="breadcrumb"]`); + + await redirectToHomePage(page); + + await page.waitForSelector(`[data-testid="recently-viewed-widget"]`); + + const selector = `[data-testid="recently-viewed-widget"] [title="${entity.entity.name}"]`; - await afterAction(); - }); + await expect(page.locator(selector)).toBeVisible(); + } + ); + } }); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts index 0e7a0f2b936b..c0fdbe948ebe 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts @@ -18,6 +18,7 @@ import { EntityTypeEndpoint } from './Entity.interface'; import { EntityClass } from './EntityClass'; export class ContainerClass extends EntityClass { + private containerName = `pw-container-${uuid()}`; service = { name: `pw-storage-service-${uuid()}`, serviceType: 'S3', @@ -35,8 +36,8 @@ export class ContainerClass extends EntityClass { }, }; entity = { - name: `pw-container-${uuid()}`, - displayName: `pw-container-${uuid()}`, + name: this.containerName, + displayName: this.containerName, service: this.service.name, }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts index 2549903d3e37..c904ffd67176 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts @@ -18,6 +18,7 @@ import { EntityTypeEndpoint } from './Entity.interface'; import { EntityClass } from './EntityClass'; export class DashboardClass extends EntityClass { + private dashboardName = `pw-dashboard-${uuid()}`; service = { name: `pw-dashboard-service-${uuid()}`, serviceType: 'Superset', @@ -40,8 +41,8 @@ export class DashboardClass extends EntityClass { service: this.service.name, }; entity = { - name: `pw-dashboard-${uuid()}`, - displayName: `pw-dashboard-${uuid()}`, + name: this.dashboardName, + displayName: this.dashboardName, service: this.service.name, }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts index d197d4afedc6..b8401b62aa84 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts @@ -18,6 +18,7 @@ import { EntityTypeEndpoint } from './Entity.interface'; import { EntityClass } from './EntityClass'; export class DashboardDataModelClass extends EntityClass { + private dashboardDataModelName = `pw-dashboard-data-model-${uuid()}`; service = { name: `pw-dashboard-service-${uuid()}`, serviceType: 'Superset', @@ -46,8 +47,8 @@ export class DashboardDataModelClass extends EntityClass { ]; entity = { - name: `pw-dashboard-data-model-${uuid()}`, - displayName: `pw-dashboard-data-model-${uuid()}`, + name: this.dashboardDataModelName, + displayName: this.dashboardDataModelName, service: this.service.name, columns: this.children, dataModelType: 'SupersetDataModel', diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts index 6e18b61ab735..c0d3771bd612 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts @@ -27,6 +27,7 @@ type ResponseDataType = { }; export class MlModelClass extends EntityClass { + private mlModelName = `pw-mlmodel-${uuid()}`; service = { name: `pw-ml-model-service-${uuid()}`, serviceType: 'Mlflow', @@ -49,8 +50,8 @@ export class MlModelClass extends EntityClass { ]; entity = { - name: `pw-mlmodel-${uuid()}`, - displayName: `pw-mlmodel-${uuid()}`, + name: this.mlModelName, + displayName: this.mlModelName, service: this.service.name, algorithm: 'Time Series', mlFeatures: this.children, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts index 91fb398e1aba..ea0e69ed78f4 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts @@ -18,6 +18,7 @@ import { EntityTypeEndpoint } from './Entity.interface'; import { EntityClass } from './EntityClass'; export class PipelineClass extends EntityClass { + private pipelineName = `pw-pipeline-${uuid()}`; service = { name: `pw-pipeline-service-${uuid()}`, serviceType: 'Dagster', @@ -35,8 +36,8 @@ export class PipelineClass extends EntityClass { children = [{ name: 'snowflake_task' }]; entity = { - name: `pw-pipeline-${uuid()}`, - displayName: `pw-pipeline-${uuid()}`, + name: this.pipelineName, + displayName: this.pipelineName, service: this.service.name, tasks: this.children, }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts index 8d06218706fc..a4fecb322f6b 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts @@ -84,7 +84,7 @@ export class SearchIndexClass extends EntityClass { entity = { name: this.searchIndexName, - displayName: `pw-search-index-${uuid()}`, + displayName: this.searchIndexName, service: this.service.name, fields: this.children, }; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx index 599189024377..9922778fd8d9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.component.tsx @@ -37,7 +37,7 @@ import { } from '../../context/PermissionProvider/PermissionProvider.interface'; import { ClientErrors } from '../../enums/Axios.enum'; import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum'; -import { TabSpecificField } from '../../enums/entity.enum'; +import { EntityType, TabSpecificField } from '../../enums/entity.enum'; import { CreateThread } from '../../generated/api/feed/createThread'; import { Tag } from '../../generated/entity/classification/tag'; import { DashboardDataModel } from '../../generated/entity/data/dashboardDataModel'; @@ -53,10 +53,12 @@ import { } from '../../rest/dataModelsAPI'; import { postThread } from '../../rest/feedsAPI'; import { + addToRecentViewed, getEntityMissingError, sortTagsCaseInsensitive, } from '../../utils/CommonUtils'; import { getSortedDataModelColumnTags } from '../../utils/DataModelsUtils'; +import { getEntityName } from '../../utils/EntityUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { getTierTags } from '../../utils/TableUtils'; import { updateTierTag } from '../../utils/TagsUtils'; @@ -134,6 +136,15 @@ const DataModelsPage = () => { include: Include.All, }); setDataModelData(response); + + addToRecentViewed({ + displayName: getEntityName(response), + entityType: EntityType.DASHBOARD_DATA_MODEL, + fqn: response.fullyQualifiedName ?? '', + serviceType: response.serviceType, + timestamp: 0, + id: response.id, + }); } catch (error) { showErrorToast(error as AxiosError); setHasError(true); From 187113c1b28349d49d483a2d551df7dbb4cca6a9 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Fri, 6 Sep 2024 15:02:37 +0530 Subject: [PATCH 3/4] fix : unit test --- .../resources/ui/src/pages/DataModelPage/DataModelPage.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.test.tsx index 84ad84e97427..03e4496e6648 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataModelPage/DataModelPage.test.tsx @@ -125,6 +125,7 @@ jest.mock('../../rest/feedsAPI', () => ({ })); jest.mock('../../utils/CommonUtils', () => ({ + addToRecentViewed: jest.fn(), getEntityMissingError: jest.fn(() => ENTITY_MISSING_ERROR), sortTagsCaseInsensitive: jest.fn((tags) => tags), })); From b3577c7bffb86272ce1abc8e5cd0650b6217b27b Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Fri, 6 Sep 2024 16:53:37 +0530 Subject: [PATCH 4/4] refactor: Remove unnecessary code for recently viewed widget --- .../ui/playwright/e2e/Features/RecentlyViewed.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts index 56ab8f38b3c9..3d91dc884b18 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/RecentlyViewed.spec.ts @@ -69,11 +69,8 @@ test.describe('Recently viewed data assets', () => { await test.step( `Check ${entity.getType()} in recently viewed widget `, async () => { - const entityPromise = page.waitForResponse('/api/v1/*/name/*'); await entity.visitEntityPage(page); - await entityPromise; - await page.waitForSelector(`[data-testid="breadcrumb"]`); await redirectToHomePage(page);