diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/EntityVersionPages.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/EntityVersionPages.spec.ts index d7537fe8f5f..5ae091aad1a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/EntityVersionPages.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/VersionPages/EntityVersionPages.spec.ts @@ -22,7 +22,11 @@ import { redirectToHomePage, toastNotification, } from '../../utils/common'; -import { addMultiOwner, assignTier } from '../../utils/entity'; +import { + addMultiOwner, + assignTier, + getEntityDataTypeDisplayPatch, +} from '../../utils/entity'; const entityCreationConfig: EntityDataClassCreationConfig = { apiEndpoint: true, @@ -78,6 +82,7 @@ test.describe('Entity Version pages', () => { const domain = EntityDataClass.domain1.responseData; for (const entity of entities) { + const dataTypeDisplayPath = getEntityDataTypeDisplayPatch(entity); await entity.patch({ apiContext, patchData: [ @@ -116,6 +121,15 @@ test.describe('Entity Version pages', () => { description: domain.description, }, }, + ...(dataTypeDisplayPath + ? [ + { + op: 'add' as const, + path: dataTypeDisplayPath, + value: 'OBJECT', + }, + ] + : []), ], }); } 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 4cd3c61ddba..9aa1b5f00c3 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/entity.ts @@ -1728,3 +1728,22 @@ export const checkExploreSearchFilter = async ( await entity?.visitEntityPage(page); }; + +export const getEntityDataTypeDisplayPatch = (entity: EntityClass) => { + switch (entity.getType()) { + case 'Table': + case 'Dashboard Data Model': + return '/columns/0/dataTypeDisplay'; + case 'ApiEndpoint': + return '/requestSchema/schemaFields/0/dataTypeDisplay'; + case 'Topic': + return '/messageSchema/schemaFields/0/dataTypeDisplay'; + case 'Container': + return '/dataModel/columns/0/dataTypeDisplay'; + case 'SearchIndex': + return '/fields/0/dataTypeDisplay'; + + default: + return undefined; + } +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/Feeds.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/Feeds.constants.ts index d6d40952d3b..8d1b163d2ec 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/Feeds.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/Feeds.constants.ts @@ -90,6 +90,7 @@ export enum EntityField { EXPERTS = 'experts', FIELDS = 'fields', PARAMETER_VALUES = 'parameterValues', + DATA_TYPE_DISPLAY = 'dataTypeDisplay', } export const ANNOUNCEMENT_BG = '#FFFDF8'; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityVersionUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityVersionUtils.test.tsx new file mode 100644 index 00000000000..6afaaff69ee --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityVersionUtils.test.tsx @@ -0,0 +1,426 @@ +/* + * 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 { EntityField } from '../constants/Feeds.constants'; +import { + Column as ContainerColumn, + DataType as ContainerDataType, +} from '../generated/entity/data/container'; +import { + Column as TableColumn, + DataType as TableDataType, +} from '../generated/entity/data/table'; +import { DataTypeTopic, Field } from '../generated/entity/data/topic'; +import { FieldChange } from '../generated/entity/services/databaseService'; +import { getStringEntityDiff } from './EntityVersionUtils'; + +// Mock data for testing +const createMockTableColumn = ( + name: string, + displayName?: string +): TableColumn => ({ + name, + displayName, + dataType: TableDataType.String, + dataTypeDisplay: 'string', + fullyQualifiedName: `test.table.${name}`, + tags: [], + children: [], +}); + +const createMockContainerColumn = ( + name: string, + displayName?: string +): ContainerColumn => ({ + name, + displayName, + dataType: ContainerDataType.String, + dataTypeDisplay: 'string', + fullyQualifiedName: `test.container.${name}`, + tags: [], + children: [], +}); + +const createMockField = (name: string, displayName?: string): Field => ({ + name, + displayName, + dataType: DataTypeTopic.String, + dataTypeDisplay: 'string', + fullyQualifiedName: `test.topic.${name}`, + tags: [], + children: [], +}); + +const createMockFieldChange = ( + name: string, + oldValue: string, + newValue: string +): FieldChange => ({ + name, + oldValue, + newValue, +}); + +const createMockEntityDiff = ( + added?: FieldChange, + deleted?: FieldChange, + updated?: FieldChange +) => ({ + added, + deleted, + updated, +}); + +describe('getStringEntityDiff', () => { + describe('TableColumn entity', () => { + it('should update displayName with diff when entity name matches', () => { + const oldDisplayName = 'Old Display Name'; + const newDisplayName = 'New Display Name'; + const entityName = 'testColumn'; + + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('displayName', oldDisplayName, newDisplayName) + ); + + const columns = [ + createMockTableColumn(entityName, oldDisplayName), + createMockTableColumn('otherColumn', 'Other Display Name'), + ]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + entityName, + columns + ); + + expect(result).toHaveLength(2); + expect(result[0].displayName).toContain('diff-removed'); + expect(result[0].displayName).toContain('diff-added'); + expect(result[1].displayName).toBe('Other Display Name'); + }); + + it('should update description field when DESCRIPTION field is passed', () => { + const oldDescription = 'Old description'; + const newDescription = 'New description'; + const entityName = 'testColumn'; + + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('description', oldDescription, newDescription) + ); + + const columns = [ + { ...createMockTableColumn(entityName), description: oldDescription }, + createMockTableColumn('otherColumn', 'Other Display Name'), + ]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DESCRIPTION, + entityName, + columns + ); + + expect(result).toHaveLength(2); + expect(result[0].description).toContain('diff-removed'); + expect(result[0].description).toContain('diff-added'); + expect(result[1].description).toBeUndefined(); + }); + + it('should handle nested children entities', () => { + const oldDisplayName = 'Old Child Display Name'; + const newDisplayName = 'New Child Display Name'; + const childEntityName = 'childColumn'; + + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('displayName', oldDisplayName, newDisplayName) + ); + + const childColumn = createMockTableColumn( + childEntityName, + oldDisplayName + ); + const parentColumn = { + ...createMockTableColumn('parentColumn', 'Parent Display Name'), + children: [childColumn], + }; + + const columns = [parentColumn]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + childEntityName, + columns + ); + + expect(result).toHaveLength(1); + expect(result[0].children?.[0].displayName).toContain('diff-removed'); + expect(result[0].children?.[0].displayName).toContain('diff-added'); + expect(result[0].displayName).toBe('Parent Display Name'); + }); + + it('should not modify entities when name does not match', () => { + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('displayName', 'Old Name', 'New Name') + ); + + const columns = [ + createMockTableColumn('differentColumn', 'Original Display Name'), + ]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + 'nonExistentColumn', + columns + ); + + expect(result).toHaveLength(1); + expect(result[0].displayName).toBe('Original Display Name'); + }); + }); + + describe('ContainerColumn entity', () => { + it('should update displayName for ContainerColumn', () => { + const oldDisplayName = 'Old Container Display Name'; + const newDisplayName = 'New Container Display Name'; + const entityName = 'containerColumn'; + + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('displayName', oldDisplayName, newDisplayName) + ); + + const columns = [createMockContainerColumn(entityName, oldDisplayName)]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + entityName, + columns + ); + + expect(result).toHaveLength(1); + expect(result[0].displayName).toContain('diff-removed'); + expect(result[0].displayName).toContain('diff-added'); + }); + }); + + describe('Field entity', () => { + it('should update displayName for Field', () => { + const oldDisplayName = 'Old Field Display Name'; + const newDisplayName = 'New Field Display Name'; + const entityName = 'fieldName'; + + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('displayName', oldDisplayName, newDisplayName) + ); + + const fields = [createMockField(entityName, oldDisplayName)]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + entityName, + fields + ); + + expect(result).toHaveLength(1); + expect(result[0].displayName).toContain('diff-removed'); + expect(result[0].displayName).toContain('diff-added'); + }); + }); + + describe('Edge cases', () => { + it('should handle empty entity list', () => { + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('displayName', 'Old', 'New') + ); + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + 'anyEntity', + [] + ); + + expect(result).toHaveLength(0); + }); + + it('should handle undefined changedEntityName', () => { + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('displayName', 'Old', 'New') + ); + + const columns = [ + createMockTableColumn('testColumn', 'Test Display Name'), + ]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + undefined, + columns + ); + + expect(result).toHaveLength(1); + expect(result[0].displayName).toBe('Test Display Name'); + }); + + it('should handle empty old and new values', () => { + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('displayName', '', '') + ); + + const columns = [ + createMockTableColumn('testColumn', 'Existing Display Name'), + ]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + 'testColumn', + columns + ); + + expect(result).toHaveLength(1); + // Should preserve the existing display name when both old and new are empty + expect(result[0].displayName).toBe('Existing Display Name'); + }); + + it('should handle added field change', () => { + const newDisplayName = 'New Added Display Name'; + const entityName = 'testColumn'; + + const entityDiff = createMockEntityDiff( + createMockFieldChange('displayName', '', newDisplayName), + undefined, + undefined + ); + + const columns = [createMockTableColumn(entityName, '')]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + entityName, + columns + ); + + expect(result).toHaveLength(1); + expect(result[0].displayName).toContain('diff-added'); + }); + + it('should handle deleted field change', () => { + const oldDisplayName = 'Deleted Display Name'; + const entityName = 'testColumn'; + + const entityDiff = createMockEntityDiff( + undefined, + createMockFieldChange('displayName', oldDisplayName, ''), + undefined + ); + + const columns = [createMockTableColumn(entityName, oldDisplayName)]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DISPLAYNAME, + entityName, + columns + ); + + expect(result).toHaveLength(1); + expect(result[0].displayName).toContain('diff-removed'); + }); + }); + + describe('Different EntityField values', () => { + it('should handle DATA_TYPE_DISPLAY field', () => { + const oldDataTypeDisplay = 'VARCHAR(255)'; + const newDataTypeDisplay = 'TEXT'; + const entityName = 'testColumn'; + + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange( + EntityField.DATA_TYPE_DISPLAY, + oldDataTypeDisplay, + newDataTypeDisplay + ) + ); + + const columns = [ + { + ...createMockTableColumn(entityName), + dataTypeDisplay: oldDataTypeDisplay, + }, + ]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.DATA_TYPE_DISPLAY, + entityName, + columns + ); + + expect(result).toHaveLength(1); + expect(result[0].dataTypeDisplay).toContain('diff-removed'); + expect(result[0].dataTypeDisplay).toContain('diff-added'); + }); + + it('should handle NAME field', () => { + const oldName = 'oldColumnName'; + const newName = 'newColumnName'; + const entityName = 'oldColumnName'; + + const entityDiff = createMockEntityDiff( + undefined, + undefined, + createMockFieldChange('name', oldName, newName) + ); + + const columns = [{ ...createMockTableColumn(entityName), name: oldName }]; + + const result = getStringEntityDiff( + entityDiff, + EntityField.NAME, + entityName, + columns + ); + + expect(result).toHaveLength(1); + expect(result[0].name).toContain('diff-removed'); + expect(result[0].name).toContain('diff-added'); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityVersionUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityVersionUtils.tsx index fe237601f0b..f7d545efbcf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityVersionUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityVersionUtils.tsx @@ -70,6 +70,8 @@ import { t } from './i18next/LocalUtil'; import { getJSONFromString, isValidJSONString } from './StringsUtils'; import { getTagsWithoutTier, getTierTags } from './TableUtils'; +type EntityColumn = TableColumn | ContainerColumn | Field; + export const getChangedEntityName = (diffObject?: EntityDiffProps) => diffObject?.added?.name ?? diffObject?.deleted?.name ?? @@ -506,10 +508,9 @@ export function getEntityDescriptionDiff( return entityList; } -export function getEntityDisplayNameDiff< - A extends TableColumn | ContainerColumn | Field ->( +export function getStringEntityDiff( entityDiff: EntityDiffProps, + key: EntityField, changedEntityName?: string, entityList: A[] = [] ) { @@ -519,11 +520,11 @@ export function getEntityDisplayNameDiff< const formatEntityData = (arr: Array) => { arr?.forEach((i) => { if (isEqual(i.name, changedEntityName)) { - i.displayName = getTextDiff( + i[key as keyof A] = getTextDiff( oldDisplayName ?? '', newDisplayName ?? '', - i.displayName - ); + i[key as keyof typeof i] as unknown as string + ) as unknown as A[keyof A]; } else { formatEntityData(i?.children as Array); } @@ -535,9 +536,11 @@ export function getEntityDisplayNameDiff< return entityList; } -export function getEntityTagDiff< - A extends TableColumn | ContainerColumn | Field ->(entityDiff: EntityDiffProps, changedEntityName?: string, entityList?: A[]) { +export function getEntityTagDiff( + entityDiff: EntityDiffProps, + changedEntityName?: string, + entityList?: A[] +) { const oldTags: TagLabel[] = JSON.parse( getChangedEntityOldValue(entityDiff) ?? '[]' ); @@ -822,7 +825,23 @@ export function getColumnsDataWithVersionChanges< ]; } else if (isEndsWithField(EntityField.DISPLAYNAME, changedEntityName)) { newColumnsList = [ - ...getEntityDisplayNameDiff(columnDiff, changedColName, colList), + ...getStringEntityDiff( + columnDiff, + EntityField.DISPLAYNAME, + changedColName, + colList + ), + ]; + } else if ( + isEndsWithField(EntityField.DATA_TYPE_DISPLAY, changedEntityName) + ) { + newColumnsList = [ + ...getStringEntityDiff( + columnDiff, + EntityField.DATA_TYPE_DISPLAY, + changedColName, + colList + ), ]; } else if (!isEndsWithField(EntityField.CONSTRAINT, changedEntityName)) { const changedEntity = changedEntityName diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx index c7eb49a1922..36376fb2891 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx @@ -626,6 +626,7 @@ export const getFeedChangeFieldLabel = (fieldName?: EntityField) => { [EntityField.EXPERTS]: t('label.expert-plural'), [EntityField.FIELDS]: t('label.field-plural'), [EntityField.PARAMETER_VALUES]: t('label.parameter-plural'), + [EntityField.DATA_TYPE_DISPLAY]: t('label.data-type-display'), }; return isUndefined(fieldName) ? '' : fieldNameLabelMapping[fieldName]; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SchemaVersionUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/SchemaVersionUtils.test.ts new file mode 100644 index 00000000000..b2596d62114 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SchemaVersionUtils.test.ts @@ -0,0 +1,313 @@ +/* + * 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 { + ChangeDescription, + DataTypeTopic, + Field, + MessageSchemaObject, + SchemaType, +} from '../generated/entity/data/topic'; +import { FieldChange } from '../generated/entity/services/databaseService'; +import { getVersionedSchema } from './SchemaVersionUtils'; + +// Mock data for testing +const createMockField = ( + name: string, + dataTypeDisplay?: string, + dataType: DataTypeTopic = DataTypeTopic.String +): Field => ({ + name, + dataType, + dataTypeDisplay, + fullyQualifiedName: `test.topic.schema.${name}`, + tags: [], + children: [], +}); + +const createMockMessageSchema = (fields: Field[]): MessageSchemaObject => ({ + schemaFields: fields, + schemaType: SchemaType.Avro, +}); + +const createMockFieldChange = ( + name: string, + oldValue: string, + newValue: string +): FieldChange => ({ + name, + oldValue, + newValue, +}); + +const createMockChangeDescription = ( + fieldsAdded?: FieldChange[], + fieldsDeleted?: FieldChange[], + fieldsUpdated?: FieldChange[] +): ChangeDescription => ({ + fieldsAdded: fieldsAdded || [], + fieldsDeleted: fieldsDeleted || [], + fieldsUpdated: fieldsUpdated || [], +}); + +describe('SchemaVersionUtils', () => { + describe('getVersionedSchema', () => { + describe('DATA_TYPE_DISPLAY changes', () => { + it('should update dataTypeDisplay with diff highlighting when field is changed', () => { + const oldDataTypeDisplay = 'VARCHAR(255)'; + const newDataTypeDisplay = 'TEXT'; + const fieldName = 'testField'; + + // Create mock schema with a field that has dataTypeDisplay + const originalSchema = createMockMessageSchema([ + createMockField(fieldName, oldDataTypeDisplay), + createMockField('otherField', 'INT'), + ]); + + // Create change description for dataTypeDisplay update + const changeDescription = createMockChangeDescription( + undefined, + undefined, + [ + createMockFieldChange( + `schemaFields.${fieldName}.dataTypeDisplay`, + oldDataTypeDisplay, + newDataTypeDisplay + ), + ] + ); + + // Execute the function + const result = getVersionedSchema(originalSchema, changeDescription); + + // Verify the result + expect(result.schemaFields).toHaveLength(2); + expect(result.schemaFields?.[0].name).toBe(fieldName); + expect(result.schemaFields?.[0].dataTypeDisplay).toContain( + 'diff-removed' + ); + expect(result.schemaFields?.[0].dataTypeDisplay).toContain( + 'diff-added' + ); + expect(result.schemaFields?.[0].dataTypeDisplay).toContain( + oldDataTypeDisplay + ); + expect(result.schemaFields?.[0].dataTypeDisplay).toContain( + newDataTypeDisplay + ); + + // Other field should remain unchanged + expect(result.schemaFields?.[1].dataTypeDisplay).toBe('INT'); + }); + + it('should handle nested field dataTypeDisplay changes', () => { + const oldDataTypeDisplay = 'STRUCT'; + const newDataTypeDisplay = 'STRUCT'; + const parentFieldName = 'parentField'; + const childFieldName = 'childField'; + + // Create mock schema with nested fields + const childField = createMockField(childFieldName, oldDataTypeDisplay); + const parentField = { + ...createMockField(parentFieldName, 'STRUCT'), + children: [childField], + }; + const originalSchema = createMockMessageSchema([parentField]); + + // Create change description for nested field dataTypeDisplay update + const changeDescription = createMockChangeDescription( + undefined, + undefined, + [ + createMockFieldChange( + `schemaFields.${parentFieldName}.children.${childFieldName}.dataTypeDisplay`, + oldDataTypeDisplay, + newDataTypeDisplay + ), + ] + ); + + // Execute the function + const result = getVersionedSchema(originalSchema, changeDescription); + + // Verify the result + expect(result.schemaFields).toHaveLength(1); + expect(result.schemaFields?.[0].children).toHaveLength(1); + expect( + result.schemaFields?.[0].children?.[0].dataTypeDisplay + ).toContain('diff-removed'); + expect( + result.schemaFields?.[0].children?.[0].dataTypeDisplay + ).toContain('diff-added'); + }); + + it('should not modify fields when dataTypeDisplay change does not match', () => { + const fieldName = 'testField'; + const originalDataTypeDisplay = 'VARCHAR(255)'; + + // Create mock schema + const originalSchema = createMockMessageSchema([ + createMockField(fieldName, originalDataTypeDisplay), + ]); + + // Create change description for a different field + const changeDescription = createMockChangeDescription( + undefined, + undefined, + [ + createMockFieldChange( + `schemaFields.differentField.dataTypeDisplay`, + 'OLD_VALUE', + 'NEW_VALUE' + ), + ] + ); + + // Execute the function + const result = getVersionedSchema(originalSchema, changeDescription); + + // Verify the result - should remain unchanged + expect(result.schemaFields).toHaveLength(1); + expect(result.schemaFields?.[0].dataTypeDisplay).toBe( + originalDataTypeDisplay + ); + }); + + it('should handle multiple dataTypeDisplay changes', () => { + const field1Name = 'field1'; + const field2Name = 'field2'; + const oldDataType1 = 'INT'; + const newDataType1 = 'BIGINT'; + const oldDataType2 = 'STRING'; + const newDataType2 = 'TEXT'; + + // Create mock schema with multiple fields + const originalSchema = createMockMessageSchema([ + createMockField(field1Name, oldDataType1), + createMockField(field2Name, oldDataType2), + ]); + + // Create change description for multiple dataTypeDisplay updates + const changeDescription = createMockChangeDescription( + undefined, + undefined, + [ + createMockFieldChange( + `schemaFields.${field1Name}.dataTypeDisplay`, + oldDataType1, + newDataType1 + ), + createMockFieldChange( + `schemaFields.${field2Name}.dataTypeDisplay`, + oldDataType2, + newDataType2 + ), + ] + ); + + // Execute the function + const result = getVersionedSchema(originalSchema, changeDescription); + + // Verify both fields are updated + expect(result.schemaFields).toHaveLength(2); + expect(result.schemaFields?.[0].dataTypeDisplay).toContain( + 'diff-removed' + ); + expect(result.schemaFields?.[0].dataTypeDisplay).toContain( + 'diff-added' + ); + expect(result.schemaFields?.[1].dataTypeDisplay).toContain( + 'diff-removed' + ); + expect(result.schemaFields?.[1].dataTypeDisplay).toContain( + 'diff-added' + ); + }); + + it('should preserve other schema properties when updating dataTypeDisplay', () => { + const fieldName = 'testField'; + const originalSchema = createMockMessageSchema([ + createMockField(fieldName, 'VARCHAR(255)'), + ]); + + // Add additional schema properties + const schemaWithExtraProps = { + ...originalSchema, + schemaType: SchemaType.JSON, + schemaText: 'original schema text', + }; + + const changeDescription = createMockChangeDescription( + undefined, + undefined, + [ + createMockFieldChange( + `schemaFields.${fieldName}.dataTypeDisplay`, + 'VARCHAR(255)', + 'TEXT' + ), + ] + ); + + // Execute the function + const result = getVersionedSchema( + schemaWithExtraProps, + changeDescription + ); + + // Verify schema properties are preserved + expect(result.schemaType).toBe(SchemaType.JSON); + expect(result.schemaText).toBe('original schema text'); + expect(result.schemaFields?.[0].dataTypeDisplay).toContain( + 'diff-removed' + ); + expect(result.schemaFields?.[0].dataTypeDisplay).toContain( + 'diff-added' + ); + }); + }); + + describe('Edge cases', () => { + it('should handle empty schema fields', () => { + const originalSchema = createMockMessageSchema([]); + const changeDescription = createMockChangeDescription( + undefined, + undefined, + [ + createMockFieldChange( + 'schemaFields.nonExistentField.dataTypeDisplay', + 'OLD', + 'NEW' + ), + ] + ); + + const result = getVersionedSchema(originalSchema, changeDescription); + + expect(result.schemaFields).toHaveLength(0); + }); + + it('should handle missing changeDescription fields', () => { + const originalSchema = createMockMessageSchema([ + createMockField('testField', 'VARCHAR(255)'), + ]); + const changeDescription = createMockChangeDescription(); + + const result = getVersionedSchema(originalSchema, changeDescription); + + expect(result.schemaFields).toHaveLength(1); + expect(result.schemaFields?.[0].dataTypeDisplay).toBe('VARCHAR(255)'); + }); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SchemaVersionUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/SchemaVersionUtils.ts index a8d47e59bb1..d8308fa26cc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SchemaVersionUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SchemaVersionUtils.ts @@ -30,6 +30,7 @@ import { getDiffByFieldName, getEntityDescriptionDiff, getEntityTagDiff, + getStringEntityDiff, getTextDiff, isEndsWithField, } from './EntityVersionUtils'; @@ -83,6 +84,7 @@ export function createAddedSchemasDiff( const newField: Array = JSON.parse( schemaFieldsDiff.added?.newValue ?? '[]' ); + newField.forEach((field) => { const formatSchemaFieldsData = (arr: Array, updateAll?: boolean) => { arr?.forEach((i) => { @@ -217,6 +219,32 @@ export const getVersionedSchema = ( schemaFields ); + clonedMessageSchema = { + ...clonedMessageSchema, + schemaFields: formattedSchema, + }; + } else if (isEndsWithField(EntityField.DISPLAYNAME, changedEntityName)) { + const formattedSchema = getStringEntityDiff( + schemaFieldDiff, + EntityField.DISPLAYNAME, + changedSchemaFieldName, + schemaFields + ); + + clonedMessageSchema = { + ...clonedMessageSchema, + schemaFields: formattedSchema, + }; + } else if ( + isEndsWithField(EntityField.DATA_TYPE_DISPLAY, changedEntityName) + ) { + const formattedSchema = getStringEntityDiff( + schemaFieldDiff, + EntityField.DATA_TYPE_DISPLAY, + changedSchemaFieldName, + schemaFields + ); + clonedMessageSchema = { ...clonedMessageSchema, schemaFields: formattedSchema,