diff --git a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TypeResourceIT.java b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TypeResourceIT.java index 8e6629876fa4..44d131cd46bb 100644 --- a/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TypeResourceIT.java +++ b/openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/TypeResourceIT.java @@ -32,7 +32,9 @@ import org.openmetadata.schema.type.CustomPropertyConfig; import org.openmetadata.schema.type.customProperties.EnumConfig; import org.openmetadata.sdk.client.OpenMetadataClient; +import org.openmetadata.sdk.exceptions.InvalidRequestException; import org.openmetadata.sdk.network.HttpMethod; +import org.openmetadata.sdk.network.RequestOptions; /** * Integration tests for Type entity operations. @@ -324,6 +326,224 @@ void test_typeWithInvalidName_fails(TestNamespace ns) { } } + @Test + void test_customPropertyNameAllowedCharacters_succeeds(TestNamespace ns) throws Exception { + OpenMetadataClient client = SdkClients.adminClient(); + UUID tableTypeId = TABLE_ENTITY_TYPE.getId(); + String prefix = ns.prefix("safe"); + + String[] allowedNames = { + prefix + "Plain", + prefix + "_underscore", + prefix + "-hyphen", + prefix + ".dot", + prefix + "with space", + prefix + "with/slash", + prefix + "with%percent", + prefix + "with#hash", + prefix + "with@at", + prefix + "with!bang", + prefix + "with,comma", + prefix + "with;semi", + prefix + "with=eq", + prefix + "with|pipe", + prefix + "with'quote", + prefix + "with(lparen", + prefix + "with)rparen", + prefix + "with[lbrack", + prefix + "with]rbrack", + prefix + "with{lbrace", + prefix + "with}rbrace", + prefix + "with+plus", + prefix + "with?question", + prefix + "with~tilde", + prefix + "with`backtick", + prefix + "withMatched(pair)", + prefix + "withDigits123", + }; + + for (String name : allowedNames) { + CustomProperty property = new CustomProperty(); + property.setName(name); + property.setDescription("Allowed-charset test for custom property name"); + property.setPropertyType(STRING_TYPE.getEntityReference()); + + Type updatedType = addCustomProperty(client, tableTypeId, property); + assertNotNull(updatedType, "Allowed name '" + name + "' must be accepted"); + + boolean present = + updatedType.getCustomProperties().stream().anyMatch(cp -> name.equals(cp.getName())); + assertTrue(present, "Custom property '" + name + "' should be saved on the type"); + } + } + + @Test + void test_customPropertyNameDisallowedCharacters_fails(TestNamespace ns) { + OpenMetadataClient client = SdkClients.adminClient(); + UUID tableTypeId = TABLE_ENTITY_TYPE.getId(); + String prefix = ns.prefix("bad"); + + String[] disallowedNames = { + prefix + "with\"dquote", + prefix + "with:colon", + prefix + "with^caret", + prefix + "with$dollar", + prefix + "with\\backslash", + prefix + "with&", + prefix + "withgt", + prefix + "with*asterisk", + }; + + for (String name : disallowedNames) { + CustomProperty property = new CustomProperty(); + property.setName(name); + property.setDescription("Disallowed-charset test for custom property name"); + property.setPropertyType(STRING_TYPE.getEntityReference()); + + assertThrows( + InvalidRequestException.class, + () -> addCustomProperty(client, tableTypeId, property), + "Custom property name '" + name + "' should be rejected with HTTP 400"); + } + } + + @Test + void test_customPropertyNameMustStartWithAlphanumeric_fails(TestNamespace ns) { + OpenMetadataClient client = SdkClients.adminClient(); + UUID tableTypeId = TABLE_ENTITY_TYPE.getId(); + String prefix = ns.prefix("lead"); + + String[] invalidLeads = { + "_" + prefix, "-" + prefix, "." + prefix, " " + prefix, "(" + prefix, + }; + + for (String name : invalidLeads) { + CustomProperty property = new CustomProperty(); + property.setName(name); + property.setDescription("Leading-character validation"); + property.setPropertyType(STRING_TYPE.getEntityReference()); + + assertThrows( + InvalidRequestException.class, + () -> addCustomProperty(client, tableTypeId, property), + "Custom property name '" + name + "' must start with alphanumeric (HTTP 400 expected)"); + } + } + + @Test + void test_customPropertyNameTooLong_fails(TestNamespace ns) { + OpenMetadataClient client = SdkClients.adminClient(); + UUID tableTypeId = TABLE_ENTITY_TYPE.getId(); + + StringBuilder longName = new StringBuilder(ns.prefix("long")); + while (longName.length() <= 256) { + longName.append('a'); + } + + CustomProperty property = new CustomProperty(); + property.setName(longName.toString()); + property.setDescription("Length validation"); + property.setPropertyType(STRING_TYPE.getEntityReference()); + + assertThrows( + InvalidRequestException.class, + () -> addCustomProperty(client, tableTypeId, property), + "Custom property name longer than 256 characters should be rejected with HTTP 400"); + } + + @Test + void test_customPropertyNameUnbalancedBrackets_succeeds(TestNamespace ns) throws Exception { + OpenMetadataClient client = SdkClients.adminClient(); + UUID tableTypeId = TABLE_ENTITY_TYPE.getId(); + String prefix = ns.prefix("bracket"); + + String[] unbalancedNames = { + prefix + "openParen(", + prefix + "closeParen)", + prefix + "openLbrack[", + prefix + "closeRbrack]", + prefix + "openLbrace{", + prefix + "closeRbrace}", + }; + + for (String name : unbalancedNames) { + CustomProperty property = new CustomProperty(); + property.setName(name); + property.setDescription("Unbalanced-bracket validation"); + property.setPropertyType(STRING_TYPE.getEntityReference()); + + Type updatedType = addCustomProperty(client, tableTypeId, property); + assertNotNull(updatedType); + + boolean present = + updatedType.getCustomProperties().stream().anyMatch(cp -> name.equals(cp.getName())); + assertTrue(present, "Unbalanced bracket name '" + name + "' should be saved"); + } + } + + @Test + void test_patchCannotAddCustomPropertyWithDisallowedName(TestNamespace ns) throws Exception { + OpenMetadataClient client = SdkClients.adminClient(); + Type fresh = createEntityTypeForTest(client, ns, "patchBadType"); + + String badName = ns.prefix("patched:bad"); + String patchJson = + String.format( + "[{\"op\":\"add\",\"path\":\"/customProperties\"," + + "\"value\":[{\"name\":\"%s\",\"description\":\"probe\"," + + "\"propertyType\":{\"id\":\"%s\",\"type\":\"type\",\"name\":\"string\"}}]}]", + badName, STRING_TYPE.getId()); + + assertThrows( + InvalidRequestException.class, + () -> + client + .getHttpClient() + .executeForString( + HttpMethod.PATCH, + "/v1/metadata/types/" + fresh.getId(), + patchJson, + RequestOptions.builder() + .header("Content-Type", "application/json-patch+json") + .build()), + "PATCH that adds a custom property with disallowed character must return 400"); + + Type after = getTypeById(client, fresh.getId(), "customProperties"); + boolean persisted = + after.getCustomProperties() != null + && after.getCustomProperties().stream().anyMatch(cp -> badName.equals(cp.getName())); + assertFalse(persisted, "Bad-name custom property must not be persisted via PATCH"); + } + + @Test + void test_patchCanAddCustomPropertyWithValidName(TestNamespace ns) throws Exception { + OpenMetadataClient client = SdkClients.adminClient(); + Type fresh = createEntityTypeForTest(client, ns, "patchGoodType"); + + String goodName = ns.prefix("patchedGood"); + String patchJson = + String.format( + "[{\"op\":\"add\",\"path\":\"/customProperties\"," + + "\"value\":[{\"name\":\"%s\",\"description\":\"probe\"," + + "\"propertyType\":{\"id\":\"%s\",\"type\":\"type\",\"name\":\"string\"}}]}]", + goodName, STRING_TYPE.getId()); + + client + .getHttpClient() + .executeForString( + HttpMethod.PATCH, + "/v1/metadata/types/" + fresh.getId(), + patchJson, + RequestOptions.builder().header("Content-Type", "application/json-patch+json").build()); + + Type after = getTypeById(client, fresh.getId(), "customProperties"); + boolean persisted = + after.getCustomProperties() != null + && after.getCustomProperties().stream().anyMatch(cp -> goodName.equals(cp.getName())); + assertTrue(persisted, "Valid-name custom property added via PATCH should be persisted"); + } + @Test void test_getEntityTypeFields() throws Exception { OpenMetadataClient client = SdkClients.adminClient(); @@ -770,6 +990,21 @@ private static Type createType(OpenMetadataClient client, CreateType createReque .execute(HttpMethod.POST, "/v1/metadata/types", createRequest, Type.class); } + /** + * Create a unique entity-category Type per test so PATCH-driven tests can mutate + * customProperties without racing against other tests on shared built-in types. + */ + private static Type createEntityTypeForTest( + OpenMetadataClient client, TestNamespace ns, String label) throws Exception { + CreateType req = new CreateType(); + req.setName(ns.prefix(label)); + req.setCategory(Category.Entity); + req.setDescription("Per-test entity type for PATCH IT"); + req.setNameSpace("data"); + req.setSchema("{}"); + return createType(client, req); + } + private static Type getTypeById(OpenMetadataClient client, UUID typeId) throws Exception { String response = client @@ -778,6 +1013,18 @@ private static Type getTypeById(OpenMetadataClient client, UUID typeId) throws E return OBJECT_MAPPER.readValue(response, Type.class); } + private static Type getTypeById(OpenMetadataClient client, UUID typeId, String fields) + throws Exception { + String response = + client + .getHttpClient() + .executeForString( + HttpMethod.GET, + "/v1/metadata/types/" + typeId.toString() + "?fields=" + fields, + null); + return OBJECT_MAPPER.readValue(response, Type.class); + } + private static Type getTypeByName(OpenMetadataClient client, String name) throws Exception { String response = client diff --git a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TypeRepository.java b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TypeRepository.java index 469f2da1589b..efc114cfccc2 100644 --- a/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TypeRepository.java +++ b/openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TypeRepository.java @@ -60,6 +60,7 @@ import org.openmetadata.service.util.EntityUtil.Fields; import org.openmetadata.service.util.EntityUtil.RelationIncludes; import org.openmetadata.service.util.RestUtil.PutResponse; +import org.openmetadata.service.util.ValidatorUtil; @Slf4j public class TypeRepository extends EntityRepository { @@ -344,6 +345,13 @@ private void updateCustomProperties() { List deleted = new ArrayList<>(); recordListChange( "customProperties", origProperties, updatedProperties, added, deleted, customFieldMatch); + // Legacy names from existing data are not re-validated; only newly added ones. + for (CustomProperty property : added) { + String violations = ValidatorUtil.validate(property); + if (violations != null) { + throw new IllegalArgumentException(violations); + } + } for (CustomProperty property : added) { storeCustomProperty(property); } diff --git a/openmetadata-spec/src/main/resources/json/schema/type/basic.json b/openmetadata-spec/src/main/resources/json/schema/type/basic.json index 37b6e0aa8f8f..21aaeea5d2ba 100644 --- a/openmetadata-spec/src/main/resources/json/schema/type/basic.json +++ b/openmetadata-spec/src/main/resources/json/schema/type/basic.json @@ -126,6 +126,13 @@ "maxLength": 256, "pattern": "^((?!::).)*$" }, + "customPropertyName": { + "description": "Name of a custom property. Allowed characters: alphanumeric, _ - . / % # @ ! , ; = | ' + ? ~ ` space ( ) [ ] { }. Must start with an alphanumeric character. Disallowed characters: \" * & < > : ^ $ \\", + "type": "string", + "minLength": 1, + "maxLength": 256, + "pattern": "^[A-Za-z0-9][A-Za-z0-9 _\\-.,;/%#@!'(){}\\[\\]|=+?~`]*$" + }, "testCaseEntityName": { "description": "Name that identifies a test definition and test case.", "type": "string", diff --git a/openmetadata-spec/src/main/resources/json/schema/type/customProperty.json b/openmetadata-spec/src/main/resources/json/schema/type/customProperty.json index 052e25586cf6..b02fd6087504 100644 --- a/openmetadata-spec/src/main/resources/json/schema/type/customProperty.json +++ b/openmetadata-spec/src/main/resources/json/schema/type/customProperty.json @@ -49,8 +49,8 @@ }, "properties": { "name": { - "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" + "description": "Name of the entity property. Must be unique for an entity. Allowed characters: alphanumeric, _ - . / % # @ ! , ; = | ' + ? ~ ` space ( ) [ ] { }. Must start with an alphanumeric character. Disallowed: \" * & < > : ^ $ \\.", + "$ref": "../type/basic.json#/definitions/customPropertyName" }, "displayName": { "description": "Display Name for the custom property.Must be unique for an entity.", diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/common.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/common.ts index bd8d46d0fd67..b5dacf2595e4 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/constant/common.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/common.ts @@ -31,6 +31,9 @@ export const NAME_MIN_MAX_LENGTH_VALIDATION_ERROR = export const NAME_MAX_LENGTH_VALIDATION_ERROR = 'Name size must be between 1 and 128'; +export const CP_NAME_MAX_LENGTH_VALIDATION_ERROR = + 'Name size must be between 1 and 256'; + export const DELETE_TERM = 'DELETE'; export const COMMON_TIER_TAG = [ diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/customProperty.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/customProperty.ts index 88c7781b38de..7d5a97a08e43 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/constant/customProperty.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/customProperty.ts @@ -212,5 +212,19 @@ export const CUSTOM_PROPERTIES_ENTITIES = { }, }; -export const CUSTOM_PROPERTY_NAME_VALIDATION_ERROR = - "Name must not contain '::'."; +export const CUSTOM_PROPERTY_NAME_VALIDATION_ERROR = String.raw`Name must start with a letter or number. Invalid characters: " * : ^ $ \ < > &`; + +export const CUSTOM_PROPERTY_INVALID_NAMES = { + STARTS_WITH_SPECIAL_CHAR: '_invalidName', + DISALLOWED_COLON: 'name:with:colon', + DISALLOWED_DOLLAR: 'name$invalid', + DISALLOWED_CARET: 'name^invalid', + DISALLOWED_QUOTE: 'name"invalid', + DISALLOWED_BACKSLASH: String.raw`name\invalid`, + DISALLOWED_LESS_THAN: 'name<>invalid', + DISALLOWED_AMPERSAND: 'name&invalid', + DISALLOWED_ASTERISK: 'name*invalid', +}; + +export const NAME_SUFFIX = ".!@#%`()_-=+{}[]~|;',.?/"; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts index 82cdfaebad5a..9d32a4b931ed 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/CustomProperties.spec.ts @@ -26,7 +26,16 @@ */ import { APIRequestContext, expect, test } from '@playwright/test'; -import { CUSTOM_PROPERTIES_ENTITIES } from '../../constant/customProperty'; +import { + CP_NAME_MAX_LENGTH_VALIDATION_ERROR, + INVALID_NAMES, +} from '../../constant/common'; +import { + CUSTOM_PROPERTIES_ENTITIES, + CUSTOM_PROPERTY_INVALID_NAMES, + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR, + NAME_SUFFIX, +} from '../../constant/customProperty'; import { CP_BASE_VALUES, CP_PARTIAL_SEARCH_VALUES, @@ -267,11 +276,15 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => { createdCPData: [], }; const propertyNames: Record = {}; - const dashboardSearchPropertyName = `cp-${uuid()}-${entity.name}`; + const dashboardSearchPropertyName = `cp-${uuid()}-${ + entity.name + }${NAME_SUFFIX}`; const dashboardPropertyValue = `EXECUTIVE_DASHBOARD_${uuid()}`; // Pipeline-specific state - const pipelineSearchPropertyName = `cp-${uuid()}-${entity.name}`; + const pipelineSearchPropertyName = `cp-${uuid()}-${ + entity.name + }${NAME_SUFFIX}`; const pipelinePropertyValue = `ETL_PRODUCTION_${uuid()}`; test.beforeAll(async ({ browser }) => { @@ -352,7 +365,7 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => { BASIC_PROPERTIES.forEach((property) => { test(property, async ({ page }) => { test.slow(); - const propertyName = `cp-${uuid()}-${entity.name}`; + const propertyName = `cp-${uuid()}-${entity.name}${NAME_SUFFIX}`; await settingClick( page, @@ -387,7 +400,7 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => { CONFIG_PROPERTIES.forEach((propertyConfig) => { test(propertyConfig.name, async ({ page }) => { test.slow(); - const propertyName = `cp-${uuid()}-${entity.name}`; + const propertyName = `cp-${uuid()}-${entity.name}${NAME_SUFFIX}`; await settingClick( page, @@ -3528,3 +3541,162 @@ ALL_ENTITIES.forEach(({ key, makeInstance }) => { } }); }); + +test.describe('Custom property name validation', () => { + test.use({ storageState: 'playwright/.auth/admin.json' }); + + test.beforeEach(async ({ page }) => { + test.slow(); + await redirectToHomePage(page); + await settingClick(page, GlobalSettingOptions.TABLES, true); + await page.click('[data-testid="add-field-button"]'); + }); + + const nameInput = '[data-testid="name"] input'; + const nameError = '#name_help'; + + test('should show error when name starts with a non-alphanumeric character', async ({ + page, + }) => { + await page.fill( + nameInput, + CUSTOM_PROPERTY_INVALID_NAMES.STARTS_WITH_SPECIAL_CHAR + ); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains a colon', async ({ page }) => { + await page.fill(nameInput, CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_COLON); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains a dollar sign', async ({ + page, + }) => { + await page.fill(nameInput, CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_DOLLAR); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains a caret', async ({ page }) => { + await page.fill(nameInput, CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_CARET); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains a double quote', async ({ + page, + }) => { + await page.fill(nameInput, CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_QUOTE); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains a backslash', async ({ page }) => { + await page.fill( + nameInput, + CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_BACKSLASH + ); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains a less-than sign', async ({ + page, + }) => { + test.slow(); + await page.fill( + nameInput, + CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_LESS_THAN + ); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains a greater-than sign', async ({ + page, + }) => { + test.slow(); + await page.fill( + nameInput, + CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_GREATER_THAN + ); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains an ampersand', async ({ + page, + }) => { + test.slow(); + await page.fill( + nameInput, + CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_AMPERSAND + ); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should show error when name contains an asterisk', async ({ page }) => { + test.slow(); + await page.fill( + nameInput, + CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_ASTERISK + ); + + await expect(page.locator(nameError)).toContainText( + CUSTOM_PROPERTY_NAME_VALIDATION_ERROR + ); + }); + + test('should accept a valid name starting with a letter', async ({ + page, + }) => { + await page.fill(nameInput, 'validName_123'); + + await expect(page.locator(nameError)).not.toBeVisible(); + }); + + test('should accept a valid name with allowed special characters', async ({ + page, + }) => { + test.slow(); + await page.fill(nameInput, "valid Name.!@#%`()_-=+{}[]~|;',.?/"); + + await expect(page.locator(nameError)).not.toBeVisible(); + }); + + test('should show error when name exceeds 256 characters', async ({ + page, + }) => { + test.slow(); + await page.fill( + nameInput, + `${INVALID_NAMES.MAX_LENGTH}${INVALID_NAMES.MAX_LENGTH}` + ); + + await expect(page.locator(nameError)).toContainText( + CP_NAME_MAX_LENGTH_VALIDATION_ERROR + ); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts index 021422169d1b..1496fbf66d18 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/advancedSearch.ts @@ -210,12 +210,7 @@ export const selectOption = async ( // Use .first() to handle multiple matches (acceptable when scoped to visible dropdown) const optionLocator = page .locator('.ant-select-dropdown:visible') - .locator('.ant-select-item-option') - .filter({ - hasText: new RegExp( - `^${optionTitle.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&')}$` - ), - }) + .getByTitle(optionTitle, { exact: true }) .first(); await expect(optionLocator).toBeVisible(); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts index c2e1040d6860..28d86710aa53 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/customProperty.ts @@ -11,10 +11,11 @@ * limitations under the License. */ import { APIRequestContext, expect, Page } from '@playwright/test'; -import { INVALID_NAMES } from '../constant/common'; import { + CUSTOM_PROPERTY_INVALID_NAMES, CUSTOM_PROPERTY_NAME_VALIDATION_ERROR, ENTITY_REFERENCE_PROPERTIES, + NAME_SUFFIX, } from '../constant/customProperty'; import { SidebarItem } from '../constant/sidebar'; import { @@ -530,7 +531,7 @@ export const createCustomPropertyForEntity = async ( }; for (const item of propertyList) { - const customPropertyName = `cp-${item.name}-${uuid()}`; + const customPropertyName = `cp-${item.name}-${uuid()}${NAME_SUFFIX}`; const payload = { name: customPropertyName, description: customPropertyName, @@ -660,7 +661,7 @@ export const addCustomPropertiesForEntity = async ({ // Validation check — only '::' is blocked await page.fill( '[data-testid="name"] input', - INVALID_NAMES.WITH_SPECIAL_CHARS + CUSTOM_PROPERTY_INVALID_NAMES.DISALLOWED_COLON ); await expect(page.locator('#name_help')).toContainText( @@ -772,12 +773,7 @@ export const addCustomPropertiesForEntity = async ({ expect(response.status()).toBe(200); await expect( - page.getByRole('row', { - name: new RegExp( - propertyName.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&'), - 'i' - ), - }) + page.locator('tr').filter({ hasText: propertyName }) ).toBeVisible(); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/CustomProperty/AddCustomProperty/AddCustomProperty.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/CustomProperty/AddCustomProperty/AddCustomProperty.tsx index 2a0385abf6c7..c8ea0a23cfe0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/CustomProperty/AddCustomProperty/AddCustomProperty.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/CustomProperty/AddCustomProperty/AddCustomProperty.tsx @@ -30,7 +30,7 @@ import { TABLE_TYPE_CUSTOM_PROPERTY, } from '../../../../constants/CustomProperty.constants'; import { GlobalSettingsMenuCategory } from '../../../../constants/GlobalSettings.constants'; -import { ENTITY_NAME_REGEX } from '../../../../constants/regex.constants'; +import { CUSTOM_PROPERTY_NAME_REGEX } from '../../../../constants/regex.constants'; import { CUSTOM_PROPERTY_CATEGORY, OPEN_METADATA, @@ -270,7 +270,15 @@ const AddCustomProperty = ({ placeholder: t('label.name'), rules: [ { - pattern: ENTITY_NAME_REGEX, + max: 256, + message: t('message.entity-size-in-between', { + entity: t('label.name'), + min: 1, + max: 256, + }), + }, + { + pattern: CUSTOM_PROPERTY_NAME_REGEX, message: t('message.custom-property-name-validation'), }, ], diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/regex.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/regex.constants.ts index be9ee2e167f7..0b8bf5550a4a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/regex.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/regex.constants.ts @@ -22,6 +22,15 @@ export const EMAIL_REG_EX = /^\S+@\S+\.\S+$/; */ export const ENTITY_NAME_REGEX = /^((?!::).)*$/; +/** + * Custom property name validation: + * - Must start with an alphanumeric character + * - Allowed characters: alphanumeric, _ - . / % # @ ! , ; = | ' + ? ~ ` space ( ) [ ] { } + * - Disallowed: " * : ^ $ \ < > & + */ +export const CUSTOM_PROPERTY_NAME_REGEX = + /^[A-Za-z0-9][A-Za-z0-9 _\-.,;/%#@!'(){}[\]|=+?~`]*$/; + /** * Matches any string that does NOT contain the following: * - Double colon (::) diff --git a/openmetadata-ui/src/main/resources/ui/src/generated/entity/type.ts b/openmetadata-ui/src/main/resources/ui/src/generated/entity/type.ts index 94f26411d9a7..2c7795c09d5f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/generated/entity/type.ts +++ b/openmetadata-ui/src/main/resources/ui/src/generated/entity/type.ts @@ -175,9 +175,9 @@ export interface CustomProperty { */ displayName?: string; /** - * 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. + * Name of the entity property. Must be unique for an entity. Allowed characters: + * alphanumeric, _ - . / % # @ ! , ; = | ' + ? ~ ` space ( ) [ ] { }. Must start with an + * alphanumeric character. Disallowed: " * & < > : ^ $ \. */ name: string; propertyType: EntityReference; diff --git a/openmetadata-ui/src/main/resources/ui/src/generated/type/customProperty.ts b/openmetadata-ui/src/main/resources/ui/src/generated/type/customProperty.ts index 618ded1dac49..a8608cb0613f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/generated/type/customProperty.ts +++ b/openmetadata-ui/src/main/resources/ui/src/generated/type/customProperty.ts @@ -21,9 +21,9 @@ export interface CustomProperty { */ displayName?: string; /** - * 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. + * Name of the entity property. Must be unique for an entity. Allowed characters: + * alphanumeric, _ - . / % # @ ! , ; = | ' + ? ~ ` space ( ) [ ] { }. Must start with an + * alphanumeric character. Disallowed: " * & < > : ^ $ \. */ name: string; propertyType: EntityReference; diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ar-sa.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ar-sa.json index 5e30920532c2..7754ad6a8834 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ar-sa.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ar-sa.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "مسار رابط شعار شريط التنقل.", "custom-properties-description": "التقط بيانات وصفية مخصصة لإثراء أصول البيانات الخاصة بك عن طريق توسيع السمات.", "custom-property-is-set-to-message": "تم تعيين {{fieldName}} إلى", - "custom-property-name-validation": "يجب ألا يحتوي الاسم على '::'.", + "custom-property-name-validation": "يجب أن يبدأ الاسم بحرف أو رقم. أحرف غير صالحة: \" * : ^ $ \\ < > &", "custom-property-update": "تحديث الخاصية المخصصة '{{propertyName}}' في {{entityName}} هو {{status}}", "customize-brand-description": "قم بتخصيص تجربة مستخدم {{brandName}} لتناسب احتياجات مؤسستك وفريقك.", "customize-entity-landing-page-header-for-persona": "تخصيص تجربة صفحة كيان {{entity}} لشخصية <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json index 3216325bd896..2ea2e3c765f1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/de-de.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "URL-Pfad für das Navbar-Logo.", "custom-properties-description": "Erfassen Sie benutzerdefinierte Metadaten, um Ihre Datenobjekte durch die Erweiterung der Attribute zu bereichern.", "custom-property-is-set-to-message": "{{fieldName}} ist gesetzt auf", - "custom-property-name-validation": "Der Name darf nicht '::' enthalten.", + "custom-property-name-validation": "Der Name muss mit einem Buchstaben oder einer Zahl beginnen. Ungültige Zeichen: \" * : ^ $ \\ < > &", "custom-property-update": "Aktualisierung der benutzerdefinierten Eigenschaft '{{propertyName}}' in {{entityName}} ist {{status}}", "customize-brand-description": "Passen Sie die {{brandName}}-Benutzeroberfläche an Ihre Organisations- und Teambedürfnisse an.", "customize-entity-landing-page-header-for-persona": "Personalisieren Sie die {{entity}}-Entitätsseite für die <0>{{persona}}-Persona", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 412decf4af00..0c23c36b5252 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "URL path for the navbar logo.", "custom-properties-description": " Capture custom metadata to enrich your data assets by extending the attributes.", "custom-property-is-set-to-message": "{{fieldName}} is set to", - "custom-property-name-validation": "Name must not contain '::'.", + "custom-property-name-validation": "Name must start with a letter or number. Invalid characters: \" * : ^ $ \\ < > &", "custom-property-update": "Custom property '{{propertyName}}' update in {{entityName}} is {{status}}", "customize-brand-description": "Tailor the {{brandName}} UX to suit your organizational and team needs.", "customize-entity-landing-page-header-for-persona": "Personalize the {{entity}} Entity Page experience for the <0>{{persona}} persona", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index 75a6ff1d1809..ff8d10f8a7b0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "Ruta URL para el logotipo de la barra de navegación.", "custom-properties-description": "Captura metadatos personalizados para enriquecer tus activos de datos mediante la extensión de los atributos.", "custom-property-is-set-to-message": "{{fieldName}} está configurada para", - "custom-property-name-validation": "El nombre no debe contener '::'.", + "custom-property-name-validation": "El nombre debe comenzar con una letra o número. Caracteres inválidos: \" * : ^ $ \\ < > &", "custom-property-update": "La actualización de la propiedad personalizada '{{propertyName}}' en {{entityName}} está {{status}}", "customize-brand-description": "Adapte la experiencia de usuario de {{brandName}} para satisfacer las necesidades de su organización y equipo.", "customize-entity-landing-page-header-for-persona": "Personaliza la experiencia de la Página de Entidad {{entity}} para la persona <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index 6b3863af98f8..110139d1086e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "Chemin (URL) du logo de la barre de navigation.", "custom-properties-description": " Capturer des métadonnées personnalisées pour enrichir vos actifs de données en étendant leurs attributs.", "custom-property-is-set-to-message": "{{fieldName}} est réglé sur", - "custom-property-name-validation": "Le nom ne doit pas contenir '::'.", + "custom-property-name-validation": "Le nom doit commencer par une lettre ou un chiffre. Caractères invalides : \" * : ^ $ \\ < > &", "custom-property-update": "La mise à jour de la propriété personnalisée '{{propertyName}}' dans {{entityName}} est {{status}}", "customize-brand-description": "Ajustez l'expérience utilisateur d'{{brandName}} pour répondre à vos besoins d'équipe et d'organisation.", "customize-entity-landing-page-header-for-persona": "Personnalisez l'expérience de la page d'entité {{entity}} pour la persona <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json index a886c032c103..11193c647357 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/gl-es.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "Ruta URL para o logotipo da barra de navegación.", "custom-properties-description": "Captura metadatos personalizados para enriquecer os teus activos de datos estendendo os atributos.", "custom-property-is-set-to-message": "{{fieldName}} está configurado para", - "custom-property-name-validation": "O nome non debe conter '::'.", + "custom-property-name-validation": "O nome debe comezar por letra ou número. Caracteres inválidos: \" * : ^ $ \\ < > &", "custom-property-update": "A actualización da propiedade personalizada '{{propertyName}}' en {{entityName}} está {{status}}", "customize-brand-description": "Adapta a experiencia de usuario de {{brandName}} ás necesidades da túa organización e equipo.", "customize-entity-landing-page-header-for-persona": "Personaliza a experiencia da páxina de entidade {{entity}} para a persoa <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json index 5fff7d7f2f72..5245b039e4a0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/he-he.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "נתיב URL עבור לוגו סרגל הניווט.", "custom-properties-description": " לכוד מטא-דאטה מותאמים אישית להעשרת נכסי הנתונים שלך על ידי הרחבת התכונות.", "custom-property-is-set-to-message": "{{fieldName}} מוגדר ל", - "custom-property-name-validation": "השם אינו יכול להכיל '::'.", + "custom-property-name-validation": "השם חייב להתחיל באות או במספר. תווים לא חוקיים: \" * : ^ $ \\ < > &", "custom-property-update": "עדכון המאפיין המותאם אישית '{{propertyName}}' ב{{entityName}} הוא {{status}}", "customize-brand-description": "התאם את חוויית המשתמש של {{brandName}} לצרכי הארגון והצוות שלך.", "customize-entity-landing-page-header-for-persona": "התאם אישית את חוויית דף הישות של {{entity}} עבור הפרסונה <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index 6a9b6a20597b..89843082f9ba 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "ナビゲーションバーのロゴ用の URL パス", "custom-properties-description": "属性を拡張して、データアセットに独自のメタデータを追加できます。", "custom-property-is-set-to-message": "{{fieldName}} に設定されています", - "custom-property-name-validation": "名前に '::' を含めることはできません。", + "custom-property-name-validation": "名前は文字または数字で始まる必要があります。無効な文字: \" * : ^ $ \\ < > &", "custom-property-update": "カスタムプロパティ '{{propertyName}}' の {{entityName}} に対する更新は {{status}} です", "customize-brand-description": "{{brandName}} の UX を組織やチームのニーズに合わせて調整します。", "customize-entity-landing-page-header-for-persona": "{{entity}} エンティティページの体験を <0>{{persona}} ペルソナ向けにパーソナライズします", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json index 4248bc8f0325..546f5c833628 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ko-kr.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "내비게이션 바 로고의 URL 경로입니다.", "custom-properties-description": "속성을 확장하여 데이터 자산을 풍부하게 하는 사용자 정의 메타데이터를 캡처합니다.", "custom-property-is-set-to-message": "{{fieldName}}이(가) 다음으로 설정되었습니다", - "custom-property-name-validation": "이름에는 '::'가 포함되어서는 안 됩니다.", + "custom-property-name-validation": "이름은 문자나 숫자로 시작해야 합니다. 잘못된 문자: \" * : ^ $ \\ < > &", "custom-property-update": "{{entityName}}의 사용자 정의 속성 '{{propertyName}}' 업데이트가 {{status}}입니다", "customize-brand-description": "조직 및 팀의 요구에 맞게 {{brandName}} UX를 맞춤 설정하세요.", "customize-entity-landing-page-header-for-persona": "<0>{{persona}} 페르소나를 위한 {{entity}} 엔티티 페이지 경험을 개인화하세요.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json index 2cdc55e2b5f3..3b5279eb8b1c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/mr-in.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "नेव्हबार लोगो साठी URL पथ.", "custom-properties-description": "तुमच्या डेटा ॲसेटंना समृद्ध करण्यासाठी सानुकूल मेटाडेटा कॅप्चर करा.", "custom-property-is-set-to-message": "{{fieldName}} सेट केले आहे", - "custom-property-name-validation": "नावामध्ये '::' नसावे.", + "custom-property-name-validation": "नाव अक्षर किंवा अंकाने सुरू झाले पाहिजे. अवैध वर्ण: \" * : ^ $ \\ < > &", "custom-property-update": "सानुकूल गुणधर्म '{{propertyName}}' चे {{entityName}} मधील अद्यतन {{status}} आहे", "customize-brand-description": "तुमच्या संस्थात्मक आणि टीमच्या गरजांसाठी {{brandName}} UX सानुकूलित करा.", "customize-entity-landing-page-header-for-persona": "{{persona}} व्यक्तिमत्वासाठी {{entity}} घटक पृष्ठ अनुभव वैयक्तिकृत करा", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json index 9c050bb5ae94..89e1041dc47c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/nl-nl.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "URL-pad voor het navigatiebalk-logo.", "custom-properties-description": "Leg aangepaste metadata vast om uw data-assets te verrijken door het uitbreiden van de attributen.", "custom-property-is-set-to-message": "{{fieldName}} is set to", - "custom-property-name-validation": "De naam mag geen '::' bevatten.", + "custom-property-name-validation": "De naam moet beginnen met een letter of cijfer. Ongeldige tekens: \" * : ^ $ \\ < > &", "custom-property-update": "Aangepaste eigenschap '{{propertyName}}' update in {{entityName}} is {{status}}", "customize-brand-description": "De {{brandName}} UX aanpassen aan de behoeften van uw organisatie en team.", "customize-entity-landing-page-header-for-persona": "Personaliseer de {{entity}} entiteitspagina-ervaring voor de <0>{{persona}} persona", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json index daaddfa0bce9..0204ce77ec44 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pr-pr.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "مسیر URL برای لوگوی نوار ناوبری.", "custom-properties-description": "متادیتای سفارشی را برای غنی‌سازی دارایی‌های داده‌ای خود با افزودن ویژگی‌های اضافی ضبط کنید.", "custom-property-is-set-to-message": "{{fieldName}} به مقدار", - "custom-property-name-validation": "نام نباید شامل '::' باشد.", + "custom-property-name-validation": "نام باید با یک حرف یا عدد شروع شود. نویسه‌های نامعتبر: \" * : ^ $ \\ < > &", "custom-property-update": "بروزرسانی ویژگی سفارشی '{{propertyName}}' در {{entityName}} {{status}} است", "customize-brand-description": "تجربه کاربری {{brandName}} را برای نیازهای سازمانی و تیمی خود تنظیم کنید.", "customize-entity-landing-page-header-for-persona": "تجربه صفحه موجودیت {{entity}} را برای شخصیت <0>{{persona}} شخصی‌سازی کنید", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index 7ba5339defad..26e4cd12ea73 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "Caminho da URL para o logotipo da barra de navegação.", "custom-properties-description": " Capture metadados personalizados para enriquecer seus ativos de dados ao estender os atributos.", "custom-property-is-set-to-message": "{{fieldName}} está definido para", - "custom-property-name-validation": "O nome não deve conter '::'.", + "custom-property-name-validation": "O nome deve começar com uma letra ou número. Caracteres inválidos: \" * : ^ $ \\ < > &", "custom-property-update": "Atualização da propriedade personalizada '{{propertyName}}' em {{entityName}} está {{status}}", "customize-brand-description": "Personalize o layout de {{brandName}} para atender às suas necessidades organizacionais e de equipe.", "customize-entity-landing-page-header-for-persona": "Personalize a experiência da Página de Entidade {{entity}} para a persona <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json index 499a2b29368f..23278f81f65b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-pt.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "Caminho da URL para o logotipo da barra de navegação.", "custom-properties-description": " Capture metadados personalizados para enriquecer os seus ativos de dados estendendo os atributos.", "custom-property-is-set-to-message": "{{fieldName}} está definido como", - "custom-property-name-validation": "O nome não deve conter '::'.", + "custom-property-name-validation": "O nome deve começar com uma letra ou número. Caracteres inválidos: \" * : ^ $ \\ < > &", "custom-property-update": "A atualização da propriedade personalizada '{{propertyName}}' em {{entityName}} está {{status}}", "customize-brand-description": "Personalize a experiência {{brandName}} para se adequar às necessidades da sua organização e equipa.", "customize-entity-landing-page-header-for-persona": "Personalize a experiência da Página da Entidade {{entity}} para a persona <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json index e40ae66e45ff..f4c141f7e993 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ru-ru.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "URL-путь для логотипа панели навигации.", "custom-properties-description": "Создавайте и настраивайте новые поля для объектов", "custom-property-is-set-to-message": "Значение поля {{fieldName}} изменено на", - "custom-property-name-validation": "Имя не должно содержать '::'.", + "custom-property-name-validation": "Имя должно начинаться с буквы или цифры. Недопустимые символы: \" * : ^ $ \\ < > &", "custom-property-update": "Обновление дополнительного поля '{{propertyName}}' в {{entityName}} в статусе {{status}}", "customize-brand-description": "Настройте UX {{brandName}} под ваши потребности", "customize-entity-landing-page-header-for-persona": "Персонализируйте страницу объекта «{{entity}}» для персоны <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json index bc455cd4ece5..c97d26d56aaf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/th-th.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "เส้นทาง URL สำหรับโลโก้ในแถบด้านบน", "custom-properties-description": "จับข้อมูลเมตาที่กำหนดเองเพื่อเสริมสินทรัพย์ข้อมูลของคุณโดยการขยายคุณสมบัติ", "custom-property-is-set-to-message": "{{fieldName}} ถูกตั้งค่าเป็น", - "custom-property-name-validation": "ชื่อต้องไม่มี '::'", + "custom-property-name-validation": "ชื่อต้องขึ้นต้นด้วยตัวอักษรหรือตัวเลข ตัวอักษรที่ไม่ถูกต้อง: \" * : ^ $ \\ < > &", "custom-property-update": "การอัปเดตคุณสมบัติที่กำหนดเอง '{{propertyName}}' ใน {{entityName}} คือ {{status}}", "customize-brand-description": "ปรับแต่ง UX ของ {{brandName}} ให้เหมาะสมกับความต้องการขององค์กรและทีมของคุณ", "customize-entity-landing-page-header-for-persona": "ปรับแต่งประสบการณ์หน้าเอนทิตี {{entity}} สำหรับบุคคล <0>{{persona}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json index 1d03830ba0a8..8845e5315761 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/tr-tr.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "Gezinme çubuğu logosu için URL yolu.", "custom-properties-description": "Nitelikleri genişleterek veri varlıklarınızı zenginleştirmek için özel metadata yakalayın.", "custom-property-is-set-to-message": "{{fieldName}} şuna ayarlandı:", - "custom-property-name-validation": "İsim '::' içermemelidir.", + "custom-property-name-validation": "İsim bir harf veya sayı ile başlamalıdır. Geçersiz karakterler: \" * : ^ $ \\ < > &", "custom-property-update": "{{entityName}} içindeki '{{propertyName}}' özel özelliği güncellemesi {{status}}", "customize-brand-description": "{{brandName}} kullanıcı deneyimini kurumsal ve ekip ihtiyaçlarınıza göre uyarlayın.", "customize-entity-landing-page-header-for-persona": "<0>{{persona}} personası için {{entity}} Varlık Sayfası deneyimini kişiselleştirin", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 8f2869c95e02..5d8826eb5dd6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "导航栏 Logo 指向的 URL 地址", "custom-properties-description": " 获取自定义元数据, 通过扩展属性来丰富数据资产", "custom-property-is-set-to-message": "{{fieldName}}设置为", - "custom-property-name-validation": "名称不能包含 '::'。", + "custom-property-name-validation": "名称必须以字母或数字开头。无效字符:\" * : ^ $ \\ < > &", "custom-property-update": "自定义属性'{{propertyName}}'在{{entityName}}中的更新{{status}}", "customize-brand-description": "自定义 {{brandName}}, 以满足您的组织和团队需求", "customize-entity-landing-page-header-for-persona": "为<0>{{persona}}角色个性化{{entity}}实体页面体验", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-tw.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-tw.json index da9fc64c0df9..d9da9a203aaf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-tw.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-tw.json @@ -2549,7 +2549,7 @@ "custom-monogram-url-path-message": "導覽列標誌的 URL 路徑。", "custom-properties-description": "透過擴充屬性來擷取自訂元資料,以豐富您的資料資產。", "custom-property-is-set-to-message": "{{fieldName}} 已設定為", - "custom-property-name-validation": "名稱不能包含 '::'。", + "custom-property-name-validation": "名稱必須以字母或數字開頭。無效字元:\" * : ^ $ \\ < > &", "custom-property-update": "{{entityName}} 中的自訂屬性 '{{propertyName}}' 更新 {{status}}", "customize-brand-description": "量身打造 {{brandName}} 的使用者體驗,以滿足您的組織和團隊需求。", "customize-entity-landing-page-header-for-persona": "為 <0>{{persona}} 角色個人化 {{entity}} 實體頁面體驗",