GEN-1894 GEN-771 : Improvement - Add displayName field for custom property (#18496)

This commit is contained in:
sonika-shah 2024-11-07 16:59:59 +05:30 committed by GitHub
parent 729a06b5f0
commit e0d175f78e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 137 additions and 11 deletions

View File

@ -300,6 +300,7 @@ public class TypeRepository extends EntityRepository<Type> {
continue;
}
updateCustomPropertyDescription(updated, storedProperty, updateProperty);
updateDisplayName(updated, storedProperty, updateProperty);
updateCustomPropertyConfig(updated, storedProperty, updateProperty);
}
}
@ -373,6 +374,32 @@ public class TypeRepository extends EntityRepository<Type> {
}
}
private void updateDisplayName(
Type entity, CustomProperty origProperty, CustomProperty updatedProperty) {
String fieldName = getCustomField(origProperty, "displayName");
if (recordChange(
fieldName, origProperty.getDisplayName(), updatedProperty.getDisplayName())) {
String customPropertyFQN =
getCustomPropertyFQN(entity.getName(), updatedProperty.getName());
EntityReference propertyType =
updatedProperty.getPropertyType(); // Don't store entity reference
String customPropertyJson = JsonUtils.pojoToJson(updatedProperty.withPropertyType(null));
updatedProperty.withPropertyType(propertyType); // Restore entity reference
daoCollection
.fieldRelationshipDAO()
.upsert(
customPropertyFQN,
updatedProperty.getPropertyType().getName(),
customPropertyFQN,
updatedProperty.getPropertyType().getName(),
Entity.TYPE,
Entity.TYPE,
Relationship.HAS.ordinal(),
"customProperty",
customPropertyJson);
}
}
private void updateCustomPropertyConfig(
Type entity, CustomProperty origProperty, CustomProperty updatedProperty) {
String fieldName = getCustomField(origProperty, "customPropertyConfig");

View File

@ -1,5 +1,6 @@
package org.openmetadata.service.migration.mysql.v160;
import static org.openmetadata.service.migration.utils.v160.MigrationUtil.addDisplayNameToCustomProperty;
import static org.openmetadata.service.migration.utils.v160.MigrationUtil.addViewAllRuleToOrgPolicy;
import static org.openmetadata.service.migration.utils.v160.MigrationUtil.migrateServiceTypesAndConnections;
@ -18,5 +19,6 @@ public class Migration extends MigrationProcessImpl {
public void runDataMigration() {
migrateServiceTypesAndConnections(handle, false);
addViewAllRuleToOrgPolicy(collectionDAO);
addDisplayNameToCustomProperty(handle, false);
}
}

View File

@ -1,5 +1,6 @@
package org.openmetadata.service.migration.postgres.v160;
import static org.openmetadata.service.migration.utils.v160.MigrationUtil.addDisplayNameToCustomProperty;
import static org.openmetadata.service.migration.utils.v160.MigrationUtil.addViewAllRuleToOrgPolicy;
import static org.openmetadata.service.migration.utils.v160.MigrationUtil.migrateServiceTypesAndConnections;
@ -18,5 +19,6 @@ public class Migration extends MigrationProcessImpl {
public void runDataMigration() {
migrateServiceTypesAndConnections(handle, true);
addViewAllRuleToOrgPolicy(collectionDAO);
addDisplayNameToCustomProperty(handle, true);
}
}

View File

@ -8,6 +8,7 @@ import org.openmetadata.schema.entity.policies.Policy;
import org.openmetadata.schema.entity.policies.accessControl.Rule;
import org.openmetadata.schema.type.Include;
import org.openmetadata.schema.type.MetadataOperation;
import org.openmetadata.schema.type.Relationship;
import org.openmetadata.service.Entity;
import org.openmetadata.service.exception.EntityNotFoundException;
import org.openmetadata.service.jdbi3.CollectionDAO;
@ -138,4 +139,39 @@ public class MigrationUtil {
LOG.error("Error updating", e);
}
}
public static void addDisplayNameToCustomProperty(Handle handle, boolean postgresql) {
String query;
if (postgresql) {
query =
"UPDATE field_relationship "
+ "SET json = CASE "
+ " WHEN json->>'displayName' IS NULL OR json->'displayName' = '\"\"' "
+ " THEN jsonb_set(json, '{displayName}', json->'name', true) "
+ " ELSE json "
+ " END "
+ "WHERE fromType = :fromType AND toType = :toType AND relation = :relation;";
} else {
query =
"UPDATE field_relationship "
+ "SET json = CASE "
+ " WHEN JSON_UNQUOTE(JSON_EXTRACT(json, '$.displayName')) IS NULL "
+ " OR JSON_UNQUOTE(JSON_EXTRACT(json, '$.displayName')) = '' "
+ " THEN JSON_SET(json, '$.displayName', JSON_EXTRACT(json, '$.name')) "
+ " ELSE json "
+ " END "
+ "WHERE fromType = :fromType AND toType = :toType AND relation = :relation;";
}
try {
handle
.createUpdate(query)
.bind("fromType", Entity.TYPE)
.bind("toType", Entity.TYPE)
.bind("relation", Relationship.HAS.ordinal())
.execute();
} catch (Exception e) {
LOG.error("Error updating displayName of custom properties", e);
}
}
}

View File

@ -116,7 +116,8 @@ public class TypeResourceTest extends EntityResourceTest<Type, CreateType> {
new CustomProperty()
.withName("intA")
.withDescription("intA")
.withPropertyType(INT_TYPE.getEntityReference());
.withPropertyType(INT_TYPE.getEntityReference())
.withDisplayName("Integer A");
ChangeDescription change = getChangeDescription(topicEntity, MINOR_UPDATE);
fieldAdded(change, "customProperties", new ArrayList<>(List.of(fieldA)));
topicEntity =
@ -124,22 +125,29 @@ public class TypeResourceTest extends EntityResourceTest<Type, CreateType> {
topicEntity.getId(), fieldA, ADMIN_AUTH_HEADERS, MINOR_UPDATE, change);
assertCustomProperties(new ArrayList<>(List.of(fieldA)), topicEntity.getCustomProperties());
// Changing custom property description with PUT
fieldA.withDescription("updated");
// Changing custom property description and displayName with PUT
fieldA.withDescription("updated").withDisplayName("Updated Integer A");
change = getChangeDescription(topicEntity, MINOR_UPDATE);
fieldUpdated(change, EntityUtil.getCustomField(fieldA, "description"), "intA", "updated");
fieldUpdated(
change, EntityUtil.getCustomField(fieldA, "displayName"), "Integer A", "Updated Integer A");
topicEntity =
addCustomPropertyAndCheck(
topicEntity.getId(), fieldA, ADMIN_AUTH_HEADERS, MINOR_UPDATE, change);
assertCustomProperties(new ArrayList<>(List.of(fieldA)), topicEntity.getCustomProperties());
// Changing custom property description with PATCH
// Changing custom property description and displayName with PATCH
// Changes from this PATCH is consolidated with the previous changes
fieldA.withDescription("updated2");
fieldA.withDescription("updated2").withDisplayName("Updated Integer A 2");
String json = JsonUtils.pojoToJson(topicEntity);
topicEntity.setCustomProperties(List.of(fieldA));
change = getChangeDescription(topicEntity, CHANGE_CONSOLIDATED);
fieldUpdated(change, EntityUtil.getCustomField(fieldA, "description"), "intA", "updated2");
fieldUpdated(
change,
EntityUtil.getCustomField(fieldA, "displayName"),
"Integer A",
"Updated Integer A 2");
topicEntity =
patchEntityAndCheck(topicEntity, json, ADMIN_AUTH_HEADERS, CHANGE_CONSOLIDATED, change);
@ -150,7 +158,11 @@ public class TypeResourceTest extends EntityResourceTest<Type, CreateType> {
.withType(INT_TYPE.getEntityReference().getType())
.withId(INT_TYPE.getEntityReference().getId());
CustomProperty fieldB =
new CustomProperty().withName("intB").withDescription("intB").withPropertyType(typeRef);
new CustomProperty()
.withName("intB")
.withDescription("intB")
.withPropertyType(typeRef)
.withDisplayName("Integer B");
change = getChangeDescription(topicEntity, MINOR_UPDATE);
fieldAdded(change, "customProperties", new ArrayList<>(List.of(fieldB)));
topicEntity =

View File

@ -52,6 +52,10 @@
"description": "Name of the entity property. Note a property name must be unique for an entity. Property name must follow camelCase naming adopted by openMetadata - must start with lower case with no space, underscore, or dots.",
"$ref": "../type/basic.json#/definitions/entityName"
},
"displayName": {
"description": "Display Name for the custom property.Must be unique for an entity.",
"type": "string"
},
"description": {
"$ref": "../type/basic.json#/definitions/markdown"
},

View File

@ -17,8 +17,8 @@ import {
ENTITY_REFERENCE_PROPERTIES,
} from '../constant/customProperty';
import {
EntityTypeEndpoint,
ENTITY_PATH,
EntityTypeEndpoint,
} from '../support/entity/Entity.interface';
import { UserClass } from '../support/user/UserClass';
import { clickOutside, descriptionBox, uuid } from './common';
@ -614,6 +614,9 @@ export const addCustomPropertiesForEntity = async ({
// Correct name
await page.fill('[data-testid="name"]', propertyName);
// displayName
await page.fill('[data-testid="display-name"]', propertyName);
// Select custom type
await page.locator('[id="root\\/propertyType"]').fill(customType);
await page.getByTitle(`${customType}`, { exact: true }).click();
@ -713,6 +716,10 @@ export const editCreatedProperty = async (
await editButton.click();
// displayName
await page.fill('[data-testid="display-name"]', '');
await page.fill('[data-testid="display-name"]', propertyName);
await page.locator(descriptionBox).fill('');
await page.locator(descriptionBox).fill('This is new description');

View File

@ -8,6 +8,12 @@ $$section
The name must start with a lowercase letter, as preferred in the camelCase format. Uppercase letters and numbers can be included in the field name; but spaces, underscores, and dots are not supported.
$$
$$section
### Display Name $(id="displayName")
Display Name that identifies this custom property.
$$
$$section
### Type $(id="propertyType")

View File

@ -282,6 +282,17 @@ const AddCustomProperty = () => {
},
],
},
{
name: 'displayName',
id: 'root/displayName',
label: t('label.display-name'),
required: false,
placeholder: t('label.display-name'),
type: FieldTypes.TEXT,
props: {
'data-testid': 'display-name',
},
},
{
name: 'propertyType',
required: true,

View File

@ -75,6 +75,7 @@ export const CustomPropertyTable: FC<CustomPropertyTableProp> = ({
return {
...property,
description: data.description,
displayName: data.displayName,
...(config
? {
customPropertyConfig: {

View File

@ -33,6 +33,7 @@ export interface FormData {
description: string;
customPropertyConfig: string[];
multiSelect?: boolean;
displayName?: string;
}
interface EditCustomPropertyModalProps {
@ -71,6 +72,17 @@ const EditCustomPropertyModal: FC<EditCustomPropertyModalProps> = ({
}, [customProperty]);
const formFields: FieldProp[] = [
{
name: 'displayName',
id: 'root/displayName',
label: t('label.display-name'),
required: false,
placeholder: t('label.display-name'),
type: FieldTypes.TEXT,
props: {
'data-testid': 'display-name',
},
},
{
name: 'description',
required: true,
@ -164,12 +176,14 @@ const EditCustomPropertyModal: FC<EditCustomPropertyModalProps> = ({
description: customProperty.description,
customPropertyConfig: enumConfig?.values ?? [],
multiSelect: Boolean(enumConfig?.multiSelect),
displayName: customProperty.displayName,
};
}
return {
description: customProperty.description,
customPropertyConfig: customProperty.customPropertyConfig?.config,
displayName: customProperty.displayName,
};
}, [customProperty, hasEnumConfig]);

View File

@ -212,7 +212,7 @@ export const PropertyValue: FC<PropertyValueProps> = ({
case 'markdown': {
const header = t('label.edit-entity-name', {
entityType: t('label.property'),
entityName: propertyName,
entityName: getEntityName(property),
});
return (
@ -1000,7 +1000,9 @@ export const PropertyValue: FC<PropertyValueProps> = ({
{hasEditPermissions && !showInput && (
<Tooltip
placement="left"
title={t('label.edit-entity', { entity: propertyName })}>
title={t('label.edit-entity', {
entity: getEntityName(property),
})}>
<Icon
component={EditIconComponent}
data-testid={`edit-icon${

View File

@ -18,6 +18,7 @@ import { isEmpty, omit } from 'lodash';
import React, { FC, MutableRefObject, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CustomProperty } from '../../../../generated/type/customProperty';
import { getEntityName } from '../../../../utils/EntityUtils';
import { TableTypePropertyValueType } from '../CustomPropertyTable.interface';
import './edit-table-type-property.less';
import TableTypePropertyView from './TableTypePropertyView';
@ -198,7 +199,7 @@ const EditTableTypePropertyModal: FC<EditTableTypePropertyModalProps> = ({
<Typography.Text>
{t('label.edit-entity-name', {
entityType: t('label.property'),
entityName: property.name,
entityName: getEntityName(property),
})}
</Typography.Text>
}

View File

@ -21,6 +21,7 @@ import Loader from '../../../components/common/Loader/Loader';
import NextPrevious from '../../../components/common/NextPrevious/NextPrevious';
import { PagingHandlerParams } from '../../../components/common/NextPrevious/NextPrevious.interface';
import { OwnerLabel } from '../../../components/common/OwnerLabel/OwnerLabel.component';
import RichTextEditorPreviewer from '../../../components/common/RichTextEditor/RichTextEditorPreviewer';
import TableTags from '../../../components/Database/TableTags/TableTags.component';
import PageHeader from '../../../components/PageHeader/PageHeader.component';
import PageLayoutV1 from '../../../components/PageLayoutV1/PageLayoutV1';
@ -168,7 +169,7 @@ const MetricListPage = () => {
})}
</Typography.Text>
) : (
description
<RichTextEditorPreviewer markdown={description} />
),
},
{