mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-11 08:43:31 +00:00
fix contract status button not visible in persona having contract tab (#23237)
* fix contract status button not visible in persona having contract tab * supported playwright test for the same * fix the unit test failing
This commit is contained in:
parent
1f4186b661
commit
999af800b3
@ -54,7 +54,6 @@ const test = base.extend<{ page: Page }>({
|
||||
|
||||
test.describe('Data Contracts', () => {
|
||||
const table = new TableClass();
|
||||
const table2 = new TableClass();
|
||||
const testClassification = new ClassificationClass();
|
||||
const testTag = new TagClass({
|
||||
classification: testClassification.data.name,
|
||||
@ -68,7 +67,6 @@ test.describe('Data Contracts', () => {
|
||||
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await table.create(apiContext);
|
||||
await table2.create(apiContext);
|
||||
await testClassification.create(apiContext);
|
||||
await testTag.create(apiContext);
|
||||
await testGlossary.create(apiContext);
|
||||
@ -111,7 +109,6 @@ test.describe('Data Contracts', () => {
|
||||
|
||||
const { apiContext, afterAction } = await performAdminLogin(browser);
|
||||
await table.delete(apiContext);
|
||||
await table2.delete(apiContext);
|
||||
await testClassification.delete(apiContext);
|
||||
await testTag.delete(apiContext);
|
||||
await testGlossary.delete(apiContext);
|
||||
@ -541,7 +538,7 @@ test.describe('Data Contracts', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('Contract Status badge should not be visible if Contract Tab is hidden by Person', async ({
|
||||
test('Contract Status badge should be visible on condition if Contract Tab is present/hidden by Persona', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.slow(true);
|
||||
@ -549,7 +546,7 @@ test.describe('Data Contracts', () => {
|
||||
await test.step(
|
||||
'Create Data Contract in Table and validate it fails',
|
||||
async () => {
|
||||
await table2.visitEntityPage(page);
|
||||
await table.visitEntityPage(page);
|
||||
|
||||
// Open contract section and start adding contract
|
||||
await page.click('[data-testid="contract"]');
|
||||
@ -679,6 +676,35 @@ test.describe('Data Contracts', () => {
|
||||
await personaResponse;
|
||||
});
|
||||
|
||||
await test.step(
|
||||
'Verify Contract tab and status badge are visible if persona is set',
|
||||
async () => {
|
||||
await redirectToHomePage(page);
|
||||
await table.visitEntityPage(page);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('[data-testid="loader"]', {
|
||||
state: 'detached',
|
||||
});
|
||||
|
||||
// Verify Contract tab is not visible (should be hidden by persona customization)
|
||||
await expect(page.getByTestId('contract')).toBeVisible();
|
||||
|
||||
// Verify Contract status badge is not visible in header
|
||||
await expect(
|
||||
page.getByTestId('data-contract-latest-result-btn')
|
||||
).toBeVisible();
|
||||
|
||||
// Additional verification: Check that other tabs are still visible
|
||||
await expect(page.getByTestId('schema')).toBeVisible();
|
||||
await expect(page.getByTestId('activity_feed')).toBeVisible();
|
||||
await expect(page.getByTestId('sample_data')).toBeVisible();
|
||||
await expect(page.getByTestId('table_queries')).toBeVisible();
|
||||
await expect(page.getByTestId('profiler')).toBeVisible();
|
||||
await expect(page.getByTestId('lineage')).toBeVisible();
|
||||
await expect(page.getByTestId('custom_properties')).toBeVisible();
|
||||
}
|
||||
);
|
||||
|
||||
await test.step('Customize Table page to hide Contract tab', async () => {
|
||||
await settingClick(page, GlobalSettingOptions.PERSONA);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
@ -429,14 +429,14 @@ export const DataAssetsHeader = ({
|
||||
]);
|
||||
|
||||
const dataContractLatestResultButton = useMemo(() => {
|
||||
const entityContainContractTab =
|
||||
isUndefined(customizedPage?.tabs) ??
|
||||
const entityContainContractTabVisible =
|
||||
isUndefined(customizedPage?.tabs) ||
|
||||
Boolean(
|
||||
customizedPage?.tabs?.find((item) => item.id === EntityTabs.CONTRACT)
|
||||
);
|
||||
|
||||
if (
|
||||
entityContainContractTab &&
|
||||
entityContainContractTabVisible &&
|
||||
dataContract?.latestResult?.status &&
|
||||
[
|
||||
ContractExecutionStatus.Aborted,
|
||||
|
@ -12,21 +12,26 @@
|
||||
*/
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { AUTO_PILOT_APP_NAME } from '../../../constants/Applications.constant';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { EntityTabs, EntityType } from '../../../enums/entity.enum';
|
||||
import { ServiceCategory } from '../../../enums/service.enum';
|
||||
import {
|
||||
Container,
|
||||
StorageServiceType,
|
||||
} from '../../../generated/entity/data/container';
|
||||
import { ContractExecutionStatus } from '../../../generated/entity/data/dataContract';
|
||||
import { DatabaseServiceType } from '../../../generated/entity/services/databaseService';
|
||||
import { LabelType, State, TagSource } from '../../../generated/tests/testCase';
|
||||
import { AssetCertification } from '../../../generated/type/assetCertification';
|
||||
import { useCustomPages } from '../../../hooks/useCustomPages';
|
||||
import { MOCK_DATA_CONTRACT } from '../../../mocks/DataContract.mock';
|
||||
import { MOCK_TIER_DATA } from '../../../mocks/TableData.mock';
|
||||
import { triggerOnDemandApp } from '../../../rest/applicationAPI';
|
||||
import { getDataQualityLineage } from '../../../rest/lineageAPI';
|
||||
import { getContainerByName } from '../../../rest/storageAPI';
|
||||
import { ExtraInfoLink } from '../../../utils/DataAssetsHeader.utils';
|
||||
import { getDataContractStatusIcon } from '../../../utils/DataContract/DataContractUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../../utils/PermissionsUtils';
|
||||
import { getEntityDetailsPath } from '../../../utils/RouterUtils';
|
||||
import { useRequiredParams } from '../../../utils/useRequiredParams';
|
||||
import { DataAssetsHeader } from './DataAssetsHeader.component';
|
||||
import { DataAssetsHeaderProps } from './DataAssetsHeader.interface';
|
||||
@ -58,9 +63,11 @@ const mockProps: DataAssetsHeaderProps = {
|
||||
onOwnerUpdate: jest.fn(),
|
||||
};
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useNavigate: jest.fn().mockReturnValue(jest.fn()),
|
||||
useNavigate: jest.fn().mockImplementation(() => mockNavigate),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/useRequiredParams', () => ({
|
||||
@ -179,6 +186,18 @@ jest.mock('../../../rest/lineageAPI', () => ({
|
||||
getDataQualityLineage: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/DataContract/DataContractUtils', () => ({
|
||||
getDataContractStatusIcon: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../hooks/useCustomPages', () => ({
|
||||
useCustomPages: jest.fn().mockReturnValue({ customizedPage: null }),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/RouterUtils', () => ({
|
||||
getEntityDetailsPath: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('ExtraInfoLink component', () => {
|
||||
const mockProps = {
|
||||
label: 'myLabel',
|
||||
@ -475,4 +494,220 @@ describe('DataAssetsHeader component', () => {
|
||||
|
||||
expect(button).toBeEnabled();
|
||||
});
|
||||
|
||||
describe('dataContractLatestResultButton', () => {
|
||||
const mockGetDataContractStatusIcon =
|
||||
getDataContractStatusIcon as jest.Mock;
|
||||
const mockUseCustomPages = useCustomPages as jest.Mock;
|
||||
const mockGetEntityDetailsPath = getEntityDetailsPath as jest.Mock;
|
||||
|
||||
it('should render data contract button when contract tab is visible and status is in allowed list', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: {
|
||||
tabs: [{ id: EntityTabs.CONTRACT }],
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<DataAssetsHeader {...mockProps} dataContract={MOCK_DATA_CONTRACT} />
|
||||
);
|
||||
|
||||
const button = screen.getByTestId('data-contract-latest-result-btn');
|
||||
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toHaveClass('data-contract-latest-result-button');
|
||||
expect(button).toHaveClass('failed');
|
||||
});
|
||||
|
||||
it('should render data contract button when customizedPage tabs is undefined', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: {
|
||||
tabs: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const mockDataContract = MOCK_DATA_CONTRACT;
|
||||
|
||||
render(
|
||||
<DataAssetsHeader {...mockProps} dataContract={mockDataContract} />
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByTestId('data-contract-latest-result-btn')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should navigate to contract tab when button is clicked', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: {
|
||||
tabs: [{ id: EntityTabs.CONTRACT }],
|
||||
},
|
||||
});
|
||||
|
||||
const mockDataContract = {
|
||||
...MOCK_DATA_CONTRACT,
|
||||
latestResult: {
|
||||
status: ContractExecutionStatus.Running,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<DataAssetsHeader {...mockProps} dataContract={mockDataContract} />
|
||||
);
|
||||
|
||||
const button = screen.getByTestId('data-contract-latest-result-btn');
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(mockGetEntityDetailsPath).toHaveBeenCalledWith(
|
||||
EntityType.CONTAINER,
|
||||
'fullyQualifiedName',
|
||||
EntityTabs.CONTRACT
|
||||
);
|
||||
expect(mockNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not render data contract button when contract tab is not visible', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: {
|
||||
tabs: [{ id: EntityTabs.ACTIVITY_FEED }],
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
<DataAssetsHeader {...mockProps} dataContract={MOCK_DATA_CONTRACT} />
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('data-contract-latest-result-btn')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render data contract button when status is not in allowed list', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: {
|
||||
tabs: [{ id: EntityTabs.CONTRACT }],
|
||||
},
|
||||
});
|
||||
|
||||
const mockDataContract = {
|
||||
...MOCK_DATA_CONTRACT,
|
||||
latestResult: {
|
||||
status: ContractExecutionStatus.Success,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<DataAssetsHeader {...mockProps} dataContract={mockDataContract} />
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('data-contract-latest-result-btn')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render data contract button when dataContract is undefined', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: {
|
||||
tabs: [{ id: EntityTabs.CONTRACT }],
|
||||
},
|
||||
});
|
||||
|
||||
render(<DataAssetsHeader {...mockProps} dataContract={undefined} />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('data-contract-latest-result-btn')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render data contract button when latestResult is undefined', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: {
|
||||
tabs: [{ id: EntityTabs.CONTRACT }],
|
||||
},
|
||||
});
|
||||
|
||||
const mockDataContract = {
|
||||
...MOCK_DATA_CONTRACT,
|
||||
latestResult: undefined,
|
||||
};
|
||||
|
||||
render(
|
||||
<DataAssetsHeader {...mockProps} dataContract={mockDataContract} />
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('data-contract-latest-result-btn')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render button with correct class names for each status', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: { tabs: [{ id: EntityTabs.CONTRACT }] },
|
||||
});
|
||||
|
||||
const statuses = [
|
||||
ContractExecutionStatus.Failed,
|
||||
ContractExecutionStatus.Aborted,
|
||||
ContractExecutionStatus.Running,
|
||||
];
|
||||
|
||||
statuses.forEach((status) => {
|
||||
const { unmount } = render(
|
||||
<DataAssetsHeader
|
||||
{...mockProps}
|
||||
dataContract={{ ...MOCK_DATA_CONTRACT, latestResult: { status } }}
|
||||
/>
|
||||
);
|
||||
|
||||
const button = screen.getByTestId('data-contract-latest-result-btn');
|
||||
|
||||
expect(button).toHaveClass(`data-contract-latest-result-button`);
|
||||
expect(button).toHaveClass(status.toLowerCase());
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render button with icon when getDataContractStatusIcon returns an icon', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: { tabs: [{ id: EntityTabs.CONTRACT }] },
|
||||
});
|
||||
mockGetDataContractStatusIcon.mockReturnValue('TestIcon');
|
||||
|
||||
render(
|
||||
<DataAssetsHeader
|
||||
{...mockProps}
|
||||
dataContract={{
|
||||
...MOCK_DATA_CONTRACT,
|
||||
latestResult: { status: ContractExecutionStatus.Failed },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const button = screen.getByTestId('data-contract-latest-result-btn');
|
||||
|
||||
expect(button.querySelector('.anticon')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render button without icon when getDataContractStatusIcon returns null', () => {
|
||||
mockUseCustomPages.mockReturnValue({
|
||||
customizedPage: { tabs: [{ id: EntityTabs.CONTRACT }] },
|
||||
});
|
||||
mockGetDataContractStatusIcon.mockReturnValue(null);
|
||||
|
||||
render(
|
||||
<DataAssetsHeader
|
||||
{...mockProps}
|
||||
dataContract={{
|
||||
...MOCK_DATA_CONTRACT,
|
||||
latestResult: { status: ContractExecutionStatus.Failed },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const button = screen.getByTestId('data-contract-latest-result-btn');
|
||||
|
||||
expect(button.querySelector('.anticon')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 {
|
||||
Constraint,
|
||||
DataType,
|
||||
LabelType,
|
||||
State,
|
||||
} from '../generated/entity/data/table';
|
||||
import { ContractExecutionStatus } from '../generated/type/contractExecutionStatus';
|
||||
import { TagSource } from '../generated/type/tagLabel';
|
||||
|
||||
export const MOCK_DATA_CONTRACT = {
|
||||
id: 'bf7b3a0d-6a85-4dd6-95e3-243a1769d1a9',
|
||||
name: 'Customer 360',
|
||||
fullyQualifiedName:
|
||||
'redshift prod.dev.dbt_jaffle.customers.dataContract_Customer 360',
|
||||
description: '<strong>Customer 360 Data Contract</strong> ',
|
||||
version: 0.2,
|
||||
updatedAt: 1755860378527,
|
||||
updatedBy: 'joseph',
|
||||
status: 'Active',
|
||||
entity: {
|
||||
id: 'ee9d44a0-815d-4ac9-8422-4f9d02ddf04d',
|
||||
type: 'table',
|
||||
href: 'https://demo.getcollate.io/v1/tables/ee9d44a0-815d-4ac9-8422-4f9d02ddf04d',
|
||||
},
|
||||
testSuite: {
|
||||
id: '24859b7c-a2ef-4e0e-b3b7-67a61ed14bc9',
|
||||
type: 'testSuite',
|
||||
fullyQualifiedName: 'bf7b3a0d-6a85-4dd6-95e3-243a1769d1a9',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
name: 'customer_id',
|
||||
dataType: DataType.Array,
|
||||
dataLength: 1,
|
||||
dataTypeDisplay: 'integer',
|
||||
description: 'Unique identifier for each customer',
|
||||
fullyQualifiedName: 'redshift prod.dev.dbt_jaffle.customers.customer_id',
|
||||
tags: [
|
||||
{
|
||||
tagFQN: 'PII.NonSensitive',
|
||||
name: 'NonSensitive',
|
||||
displayName: 'Non Sensitive ',
|
||||
description:
|
||||
'PII which is easily accessible from public sources and can include zip code, race, gender, and date of birth.',
|
||||
style: {},
|
||||
source: TagSource.Classification,
|
||||
labelType: LabelType.Automated,
|
||||
state: State.Suggested,
|
||||
},
|
||||
],
|
||||
constraint: Constraint.Null,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
semantics: [
|
||||
{
|
||||
name: 'Tiering Elements',
|
||||
description: '',
|
||||
// eslint-disable-next-line max-len
|
||||
rule: '{"and":[{"some":[{"var":"owners"},{"==":[{"var":"fullyQualifiedName"},"customer team"]}]},{"some":[{"var":"dataProduct"},{"==":[{"var":"fullyQualifiedName"},"C360"]}]},{"==":[{"var":"tier.tagFQN"},"Tier.Tier1"]}]}',
|
||||
enabled: true,
|
||||
ignoredEntities: [],
|
||||
},
|
||||
],
|
||||
qualityExpectations: [
|
||||
{
|
||||
id: 'f496f0d9-58c3-4a0e-a836-2479b457c68e',
|
||||
type: 'testCase',
|
||||
name: 'CLV Must be Positive',
|
||||
description:
|
||||
'<p>The customer lifetime value must always be greater or equal to zero </p>',
|
||||
},
|
||||
{
|
||||
id: 'e57ffd73-b8f1-4f5f-91b1-7ebe614dc26e',
|
||||
type: 'testCase',
|
||||
name: 'Customer ID To Be Unique',
|
||||
},
|
||||
{
|
||||
id: '484e016e-ed87-4f6c-ae77-7d5b16f07ad0',
|
||||
type: 'testCase',
|
||||
name: 'Table Row Count To Equal',
|
||||
},
|
||||
],
|
||||
reviewers: [],
|
||||
changeDescription: {
|
||||
fieldsAdded: [
|
||||
{
|
||||
name: 'latestResult',
|
||||
newValue: {
|
||||
status: ContractExecutionStatus.Failed,
|
||||
resultId: '04551202-c2b9-4d7b-aecf-6c9c8fe22c1c',
|
||||
timestamp: 1756944000095,
|
||||
},
|
||||
},
|
||||
],
|
||||
fieldsUpdated: [],
|
||||
fieldsDeleted: [],
|
||||
previousVersion: 0.1,
|
||||
changeSummary: {},
|
||||
},
|
||||
incrementalChangeDescription: {
|
||||
fieldsAdded: [],
|
||||
fieldsUpdated: [],
|
||||
fieldsDeleted: [],
|
||||
previousVersion: 0.2,
|
||||
},
|
||||
deleted: false,
|
||||
latestResult: {
|
||||
timestamp: 1756944000095,
|
||||
status: ContractExecutionStatus.Failed,
|
||||
resultId: '04551202-c2b9-4d7b-aecf-6c9c8fe22c1c',
|
||||
},
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user