diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts index b57f7761ac6b..4857ee9c3747 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContractInheritance.spec.ts @@ -71,7 +71,8 @@ const DATA_PRODUCT_SLA = { const fillContractDetailsForm = async ( page: Page, contractName: string, - description: string + description: string, + status?: 'Draft' | 'In Review' | 'Approved' ) => { await page.getByTestId('contract-name').fill(contractName); await page.fill('.om-block-editor[contenteditable="true"]', description); @@ -82,6 +83,14 @@ const fillContractDetailsForm = async ( await firstOwner.click(); await expect(page.getByTestId('user-tag')).toBeVisible(); + + if (status) { + await page.getByTestId('contract-status').click(); + await expect( + page.locator(`.contract-status-dropdown [title="${status}"]`) + ).toBeVisible(); + await page.locator(`.contract-status-dropdown [title="${status}"]`).click(); + } }; const fillTermsOfServiceForm = async (page: Page, termsContent: string) => { @@ -440,7 +449,8 @@ test.describe('Data Contract Inheritance', () => { await fillContractDetailsForm( page, DATA_PRODUCT_CONTRACT_DETAILS.name, - DATA_PRODUCT_CONTRACT_DETAILS.description + DATA_PRODUCT_CONTRACT_DETAILS.description, + 'Approved' ); }); @@ -565,7 +575,8 @@ test.describe('Data Contract Inheritance', () => { await fillContractDetailsForm( page, `dp_partial_${uuid()}`, - 'Data Product contract for partial inheritance' + 'Data Product contract for partial inheritance', + 'Approved' ); }); @@ -683,7 +694,8 @@ test.describe('Data Contract Inheritance', () => { await fillContractDetailsForm( page, `dp_sla_edit_test_${uuid()}`, - 'Data Product contract with SLA for edit test' + 'Data Product contract with SLA for edit test', + 'Approved' ); }); @@ -860,7 +872,8 @@ test.describe('Data Contract Inheritance', () => { await fillContractDetailsForm( page, DP_CONTRACT_DETAILS.name, - DP_CONTRACT_DETAILS.description + DP_CONTRACT_DETAILS.description, + 'Approved' ); await fillTermsOfServiceForm(page, DP_CONTRACT_DETAILS.termsOfService); @@ -1000,7 +1013,8 @@ test.describe('Data Contract Inheritance', () => { await fillContractDetailsForm( page, DP_CONTRACT_DETAILS.name, - DP_CONTRACT_DETAILS.description + DP_CONTRACT_DETAILS.description, + 'Approved' ); await saveContract(page); @@ -1069,7 +1083,8 @@ test.describe('Data Contract Inheritance', () => { await fillContractDetailsForm( page, DP_CONTRACT_DETAILS.name, - DP_CONTRACT_DETAILS.description + DP_CONTRACT_DETAILS.description, + 'Approved' ); await saveContract(page); @@ -1150,7 +1165,8 @@ test.describe('Data Contract Inheritance', () => { await fillContractDetailsForm( page, DP_CONTRACT_DETAILS.name, - DP_CONTRACT_DETAILS.description + DP_CONTRACT_DETAILS.description, + 'Approved' ); await saveContract(page); @@ -1250,7 +1266,8 @@ test.describe('Data Contract Inheritance', () => { await fillContractDetailsForm( page, DP_CONTRACT_DETAILS.name, - DP_CONTRACT_DETAILS.description + DP_CONTRACT_DETAILS.description, + 'Approved' ); await fillTermsOfServiceForm(page, DP_CONTRACT_DETAILS.termsOfService); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/AddDataContract.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/AddDataContract.test.tsx index e297b9fe9a79..5ffeb2ee7ecc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/AddDataContract.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/AddDataContract/AddDataContract.test.tsx @@ -17,10 +17,10 @@ import { EDataContractTab } from '../../../constants/DataContract.constants'; import { EntityType } from '../../../enums/entity.enum'; import { DataContract, + EntityStatus, SemanticsRule, } from '../../../generated/entity/data/dataContract'; import { Column, Table } from '../../../generated/entity/data/table'; -import { EntityStatus } from '../../../generated/entity/domains/dataProduct'; import { EntityReference } from '../../../generated/entity/type'; import { createContract, updateContract } from '../../../rest/contractAPI'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; @@ -96,6 +96,10 @@ jest.mock('../ContractDetailFormTab/ContractDetailFormTab', () => ({ + )), @@ -442,7 +446,7 @@ describe('AddDataContract', () => { type: EntityType.TABLE, }, semantics: undefined, // validSemantics - undefined when no semantics provided - entityStatus: EntityStatus.Approved, + entityStatus: EntityStatus.Draft, }) ); expect(showSuccessToast).toHaveBeenCalledWith( @@ -474,7 +478,7 @@ describe('AddDataContract', () => { type: EntityType.TABLE, }, semantics: undefined, // validSemantics - undefined when no semantics provided - entityStatus: EntityStatus.Approved, + entityStatus: EntityStatus.Draft, }) ); expect(showSuccessToast).toHaveBeenCalledWith( @@ -483,6 +487,32 @@ describe('AddDataContract', () => { expect(mockOnSave).toHaveBeenCalled(); }); + it('should use selected entity status when creating a contract', async () => { + render(); + + const changeButton = screen.getByText('Change'); + await act(async () => { + fireEvent.click(changeButton); + }); + + const statusButton = screen.getByText('Change Status'); + await act(async () => { + fireEvent.click(statusButton); + }); + + const saveButton = screen.getByTestId('save-contract-btn'); + + await act(async () => { + fireEvent.click(saveButton); + }); + + expect(createContract).toHaveBeenCalledWith( + expect.objectContaining({ + entityStatus: EntityStatus.InReview, + }) + ); + }); + it('should call updateContract for existing contract with JSON patch', async () => { render( ({ @@ -47,6 +51,10 @@ jest.mock('react-i18next', () => ({ const translations: Record = { 'label.contract-title': 'Contract Title', 'label.owner-plural': 'Owners', + 'label.status': 'Status', + 'label.draft': 'Draft label', + 'label.in-review': 'In Review label', + 'label.approved': 'Approved label', 'label.description': 'Description', 'label.contract-detail-plural': 'Contract Details', 'message.contract-detail-plural-description': 'Enter contract details', @@ -99,6 +107,7 @@ describe('ContractDetailFormTab', () => { expect(screen.getByText('Enter contract details')).toBeInTheDocument(); expect(screen.getByText('Contract Title')).toBeInTheDocument(); + expect(screen.getByText('Status')).toBeInTheDocument(); expect(screen.getByText('Owners')).toBeInTheDocument(); expect(screen.getByText('Description')).toBeInTheDocument(); }); @@ -127,6 +136,7 @@ describe('ContractDetailFormTab', () => { render(); expect(screen.getByText('Contract Title')).toBeInTheDocument(); + expect(screen.getByText('Status')).toBeInTheDocument(); expect(screen.getByText('Description')).toBeInTheDocument(); expect(screen.getByText('Owners')).toBeInTheDocument(); }); @@ -261,6 +271,21 @@ describe('ContractDetailFormTab', () => { name: 'owners', label: 'Owners', }), + expect.objectContaining({ + formItemProps: { + initialValue: EntityStatus.Draft, + }, + name: 'entityStatus', + label: 'Status', + props: expect.objectContaining({ + options: [ + { label: 'Draft label', value: EntityStatus.Draft }, + { label: 'In Review label', value: EntityStatus.InReview }, + { label: 'Approved label', value: EntityStatus.Approved }, + ], + }), + type: FieldTypes.SELECT, + }), expect.objectContaining({ name: 'description', label: 'Description', diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailFormTab/ContractDetailFormTab.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailFormTab/ContractDetailFormTab.tsx index 9dd6684c712b..cf547a7a8f02 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailFormTab/ContractDetailFormTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataContract/ContractDetailFormTab/ContractDetailFormTab.tsx @@ -16,13 +16,31 @@ import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as RightIcon } from '../../../assets/svg/right-arrow.svg'; import { EntityType } from '../../../enums/entity.enum'; -import { DataContract } from '../../../generated/entity/data/dataContract'; +import { + DataContract, + EntityStatus, +} from '../../../generated/entity/data/dataContract'; import { useEntityRules } from '../../../hooks/useEntityRules'; import { FieldProp, FieldTypes } from '../../../interface/FormUtils.interface'; import { getEntityName } from '../../../utils/EntityNameUtils'; import { generateFormFields } from '../../../utils/formUtils'; import './contract-detail-form-tab.less'; +const DATA_CONTRACT_STATUS_OPTION_KEYS = [ + { + labelKey: 'label.draft', + value: EntityStatus.Draft, + }, + { + labelKey: 'label.in-review', + value: EntityStatus.InReview, + }, + { + labelKey: 'label.approved', + value: EntityStatus.Approved, + }, +]; + export const ContractDetailFormTab: React.FC<{ initialValues?: Partial; onNext: () => void; @@ -40,6 +58,12 @@ export const ContractDetailFormTab: React.FC<{ const { t } = useTranslation(); const [form] = Form.useForm(); const { entityRules } = useEntityRules(EntityType.TABLE); + const dataContractStatusOptions = DATA_CONTRACT_STATUS_OPTION_KEYS.map( + ({ labelKey, value }) => ({ + label: t(labelKey), + value, + }) + ); const fields: FieldProp[] = [ { @@ -55,6 +79,22 @@ export const ContractDetailFormTab: React.FC<{ 'data-testid': 'contract-name', }, }, + { + label: t('label.status'), + id: 'entityStatus', + name: 'entityStatus', + type: FieldTypes.SELECT, + required: false, + placeholder: t('label.select-field', { field: t('label.status') }), + props: { + 'data-testid': 'contract-status', + options: dataContractStatusOptions, + popupClassName: 'contract-status-dropdown', + }, + formItemProps: { + initialValue: initialValues?.entityStatus ?? EntityStatus.Draft, + }, + }, { label: t('label.owner-plural'), name: 'owners', @@ -95,6 +135,7 @@ export const ContractDetailFormTab: React.FC<{ form.setFieldsValue({ name: getEntityName(initialValues), description: initialValues.description, + entityStatus: initialValues.entityStatus ?? EntityStatus.Draft, owners: initialValues.owners, }); }