diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts index 607f839746e..76cce5dd52d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractsSemanticRules.spec.ts @@ -38,6 +38,7 @@ import { addOwner, removeOwnersFromList, updateDescription, + updateDisplayNameForEntity, updateOwner, } from '../../utils/entity'; import { test } from '../fixtures/pages'; @@ -2914,3 +2915,609 @@ test.describe('Data Contracts Semantics Rule DataProduct', () => { } ); }); + +test.describe('Data Contracts Semantics Rule DisplayName', () => { + test('Validate DisplayName Rule Is', async ({ page, browser }) => { + test.slow(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + const table = new TableClass(); + await table.create(apiContext); + await afterAction(); + + await test.step( + 'Open contract section and start adding contract', + async () => { + await redirectToHomePage(page); + await table.visitEntityPage(page); + await performInitialStepForRules(page); + } + ); + + await test.step('DisplayName with Is condition should passed', async () => { + await page.getByRole('tab', { name: 'Semantics' }).click(); + + await page.fill('#semantics_0_name', DATA_CONTRACT_SEMANTICS1.name); + await page.fill( + '#semantics_0_description', + DATA_CONTRACT_SEMANTICS1.description + ); + + const ruleLocator = page.locator('.group').nth(0); + await selectOption( + page, + ruleLocator.locator('.group--field .ant-select'), + 'Display Name', + true + ); + await selectOption( + page, + ruleLocator.locator('.rule--operator .ant-select'), + DATA_CONTRACT_SEMANTIC_OPERATIONS.is + ); + + await selectOption( + page, + ruleLocator.locator('.rule--value .ant-select'), + table.entityResponseData.displayName, + true + ); + + // save and trigger contract validation + await saveAndTriggerDataContractValidation(page, true); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Passed'); + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).not.toBeVisible(); + }); + + await test.step('DisplayName with Is condition should failed', async () => { + await updateDisplayNameForEntity( + page, + `new displayName updated`, + EntityTypeEndpoint.Table + ); + + await page.click('[data-testid="contract"]'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await page.getByTestId('manage-contract-actions').click(); + + await page.waitForSelector('.contract-action-dropdown', { + state: 'visible', + }); + + const runNowResponse = page.waitForResponse( + '/api/v1/dataContracts/*/validate' + ); + await page.getByTestId('contract-run-now-button').click(); + await runNowResponse; + + await page.reload(); + + await page.waitForLoadState('networkidle'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Failed'); + + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).toContainText('Contract Failed'); + }); + }); + + test('Validate DisplayName Rule Is Not', async ({ page, browser }) => { + test.slow(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + const table = new TableClass(); + await table.create(apiContext); + await afterAction(); + + await test.step( + 'Open contract section and start adding contract', + async () => { + await redirectToHomePage(page); + await table.visitEntityPage(page); + await performInitialStepForRules(page); + } + ); + + await test.step( + 'DisplayName with Is Not condition should failed', + async () => { + await page.getByRole('tab', { name: 'Semantics' }).click(); + + await page.fill('#semantics_0_name', DATA_CONTRACT_SEMANTICS1.name); + await page.fill( + '#semantics_0_description', + DATA_CONTRACT_SEMANTICS1.description + ); + + const ruleLocator = page.locator('.group').nth(0); + await selectOption( + page, + ruleLocator.locator('.group--field .ant-select'), + 'Display Name', + true + ); + await selectOption( + page, + ruleLocator.locator('.rule--operator .ant-select'), + DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not + ); + + await selectOption( + page, + ruleLocator.locator('.rule--value .ant-select'), + table.entityResponseData.displayName, + true + ); + + // save and trigger contract validation + await saveAndTriggerDataContractValidation(page, true); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Failed'); + + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).toContainText('Contract Failed'); + } + ); + + await test.step( + 'DisplayName with Is Not condition should passed', + async () => { + await updateDisplayNameForEntity( + page, + `new displayName updated`, + EntityTypeEndpoint.Table + ); + + await page.click('[data-testid="contract"]'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await page.getByTestId('manage-contract-actions').click(); + + await page.waitForSelector('.contract-action-dropdown', { + state: 'visible', + }); + + const runNowResponse = page.waitForResponse( + '/api/v1/dataContracts/*/validate' + ); + await page.getByTestId('contract-run-now-button').click(); + await runNowResponse; + + await page.reload(); + + await page.waitForLoadState('networkidle'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Passed'); + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).not.toBeVisible(); + } + ); + }); + + test('Validate DisplayName Rule Any_In', async ({ page, browser }) => { + test.slow(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + const table = new TableClass(); + await table.create(apiContext); + await afterAction(); + + await test.step( + 'Open contract section and start adding contract', + async () => { + await redirectToHomePage(page); + await table.visitEntityPage(page); + await performInitialStepForRules(page); + } + ); + + await test.step( + 'DisplayName with Any In condition should passed', + async () => { + await page.getByRole('tab', { name: 'Semantics' }).click(); + + await page.fill('#semantics_0_name', DATA_CONTRACT_SEMANTICS1.name); + await page.fill( + '#semantics_0_description', + DATA_CONTRACT_SEMANTICS1.description + ); + + const ruleLocator = page.locator('.group').nth(0); + await selectOption( + page, + ruleLocator.locator('.group--field .ant-select'), + 'Display Name', + true + ); + await selectOption( + page, + ruleLocator.locator('.rule--operator .ant-select'), + DATA_CONTRACT_SEMANTIC_OPERATIONS.any_in + ); + await selectOption( + page, + ruleLocator.locator('.rule--value .ant-select'), + table.entityResponseData.displayName, + true + ); + + // save and trigger contract validation + await saveAndTriggerDataContractValidation(page, true); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Passed'); + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).not.toBeVisible(); + } + ); + + await test.step( + 'DisplayName with Any In condition should failed', + async () => { + await updateDisplayNameForEntity( + page, + `new displayName updated`, + EntityTypeEndpoint.Table + ); + + await page.click('[data-testid="contract"]'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await page.getByTestId('manage-contract-actions').click(); + + await page.waitForSelector('.contract-action-dropdown', { + state: 'visible', + }); + + const runNowResponse = page.waitForResponse( + '/api/v1/dataContracts/*/validate' + ); + await page.getByTestId('contract-run-now-button').click(); + await runNowResponse; + + await page.reload(); + + await page.waitForLoadState('networkidle'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Failed'); + + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).toContainText('Contract Failed'); + } + ); + }); + + test('Validate DisplayName Rule Not_In', async ({ page, browser }) => { + test.slow(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + const table = new TableClass(); + await table.create(apiContext); + await afterAction(); + + await test.step( + 'Open contract section and start adding contract', + async () => { + await redirectToHomePage(page); + await table.visitEntityPage(page); + await performInitialStepForRules(page); + } + ); + + await test.step( + 'DisplayName with Not In condition should failed', + async () => { + await page.getByRole('tab', { name: 'Semantics' }).click(); + + await page.fill('#semantics_0_name', DATA_CONTRACT_SEMANTICS1.name); + await page.fill( + '#semantics_0_description', + DATA_CONTRACT_SEMANTICS1.description + ); + + const ruleLocator = page.locator('.group').nth(0); + await selectOption( + page, + ruleLocator.locator('.group--field .ant-select'), + 'Display Name', + true + ); + await selectOption( + page, + ruleLocator.locator('.rule--operator .ant-select'), + DATA_CONTRACT_SEMANTIC_OPERATIONS.not_in + ); + await selectOption( + page, + ruleLocator.locator('.rule--value .ant-select'), + table.entityResponseData.displayName, + true + ); + + // save and trigger contract validation + await saveAndTriggerDataContractValidation(page, true); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Failed'); + + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).toContainText('Contract Failed'); + } + ); + + await test.step( + 'DisplayName with Not In condition should passed', + async () => { + await updateDisplayNameForEntity( + page, + `New displayName updated`, + EntityTypeEndpoint.Table + ); + + await page.click('[data-testid="contract"]'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await page.getByTestId('manage-contract-actions').click(); + + await page.waitForSelector('.contract-action-dropdown', { + state: 'visible', + }); + + const runNowResponse = page.waitForResponse( + '/api/v1/dataContracts/*/validate' + ); + await page.getByTestId('contract-run-now-button').click(); + await runNowResponse; + + await page.reload(); + + await page.waitForLoadState('networkidle'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Passed'); + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).not.toBeVisible(); + } + ); + }); + + test('Validate DisplayName Rule Is_Set', async ({ page, browser }) => { + test.slow(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + const table = new TableClass(); + await table.create(apiContext); + await afterAction(); + + await test.step( + 'Open contract section and start adding contract', + async () => { + await redirectToHomePage(page); + await table.visitEntityPage(page); + await performInitialStepForRules(page); + } + ); + + await test.step( + 'DisplayName with IsSet condition should passed', + async () => { + await page.getByRole('tab', { name: 'Semantics' }).click(); + + await page.fill('#semantics_0_name', DATA_CONTRACT_SEMANTICS1.name); + await page.fill( + '#semantics_0_description', + DATA_CONTRACT_SEMANTICS1.description + ); + + const ruleLocator = page.locator('.group').nth(0); + await selectOption( + page, + ruleLocator.locator('.group--field .ant-select'), + 'Display Name', + true + ); + await selectOption( + page, + ruleLocator.locator('.rule--operator .ant-select'), + DATA_CONTRACT_SEMANTIC_OPERATIONS.is_set + ); + + // save and trigger contract validation + await saveAndTriggerDataContractValidation(page, true); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Passed'); + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).not.toBeVisible(); + } + ); + + await test.step( + 'DisplayName with IsSet condition should failed', + async () => { + await updateDisplayNameForEntity( + page, + ``, + EntityTypeEndpoint.Table, + true + ); + + await page.click('[data-testid="contract"]'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await page.getByTestId('manage-contract-actions').click(); + + await page.waitForSelector('.contract-action-dropdown', { + state: 'visible', + }); + + const runNowResponse = page.waitForResponse( + '/api/v1/dataContracts/*/validate' + ); + await page.getByTestId('contract-run-now-button').click(); + await runNowResponse; + + await page.reload(); + + await page.waitForLoadState('networkidle'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Failed'); + + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).toContainText('Contract Failed'); + } + ); + }); + + test('Validate DisplayName Rule Is_Not_Set', async ({ page, browser }) => { + test.slow(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + const table = new TableClass(); + await table.create(apiContext); + await afterAction(); + + await test.step( + 'Open contract section and start adding contract', + async () => { + await redirectToHomePage(page); + await table.visitEntityPage(page); + await performInitialStepForRules(page); + } + ); + + await test.step( + 'DisplayName with IsNotSet condition should failed', + async () => { + await page.getByRole('tab', { name: 'Semantics' }).click(); + + await page.fill('#semantics_0_name', DATA_CONTRACT_SEMANTICS1.name); + await page.fill( + '#semantics_0_description', + DATA_CONTRACT_SEMANTICS1.description + ); + + const ruleLocator = page.locator('.group').nth(0); + await selectOption( + page, + ruleLocator.locator('.group--field .ant-select'), + 'Display Name', + true + ); + await selectOption( + page, + ruleLocator.locator('.rule--operator .ant-select'), + DATA_CONTRACT_SEMANTIC_OPERATIONS.is_not_set + ); + + // save and trigger contract validation + await saveAndTriggerDataContractValidation(page, true); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Failed'); + + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).toContainText('Contract Failed'); + } + ); + + await test.step( + 'DisplayName with IsNotSet condition should passed', + async () => { + await updateDisplayNameForEntity( + page, + ``, + EntityTypeEndpoint.Table, + true + ); + + await page.click('[data-testid="contract"]'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await page.getByTestId('manage-contract-actions').click(); + + await page.waitForSelector('.contract-action-dropdown', { + state: 'visible', + }); + + const runNowResponse = page.waitForResponse( + '/api/v1/dataContracts/*/validate' + ); + await page.getByTestId('contract-run-now-button').click(); + await runNowResponse; + + await page.reload(); + + await page.waitForLoadState('networkidle'); + await page.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + await expect( + page.getByTestId('contract-status-card-item-semantics-status') + ).toContainText('Passed'); + await expect( + page.getByTestId('data-contract-latest-result-btn') + ).not.toBeVisible(); + } + ); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts index 12259f74672..3689050e1d4 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts @@ -1342,7 +1342,8 @@ export const createInactiveAnnouncement = async ( export const updateDisplayNameForEntity = async ( page: Page, displayName: string, - endPoint: string + endPoint: string, + isRemoved?: boolean ) => { await page.click('[data-testid="manage-button"]'); await page.click('[data-testid="rename-button"]'); @@ -1360,9 +1361,15 @@ export const updateDisplayNameForEntity = async ( await page.click('[data-testid="save-button"]'); await updateNameResponse; - await expect( - page.locator('[data-testid="entity-header-display-name"]') - ).toHaveText(displayName); + if (isRemoved) { + await expect( + page.locator('[data-testid="entity-header-display-name"]') + ).not.toBeVisible(); + } else { + await expect( + page.locator('[data-testid="entity-header-display-name"]') + ).toHaveText(displayName); + } }; export const updateDisplayNameForEntityChildren = async ( diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx index f57eecddfca..3f4119c141a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx @@ -15,7 +15,7 @@ import { Col, Row, Tabs, Tooltip } from 'antd'; import { AxiosError } from 'axios'; import { compare } from 'fast-json-patch'; -import { isUndefined } from 'lodash'; +import { isEmpty, isUndefined } from 'lodash'; import { EntityTags } from 'Models'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -463,7 +463,10 @@ const TableDetailsPageV1: React.FC = () => { if (!tableDetails) { return; } - const updatedTable = { ...tableDetails, displayName: data.displayName }; + const updatedTable = { + ...tableDetails, + displayName: isEmpty(data.displayName) ? undefined : data.displayName, + }; await onTableUpdate(updatedTable, 'displayName'); };