check on label metadata (#12852)
Better catching label input - there were absolutely no check on label when creating the target field while doing a relation : we crearted these checks here. - We keep the label quite open to special char as discussed with Felix. so mostly checking length of label. - We check that label does not already exists on the targetted object - making sure the Target fieldinput label is checked before we create it. The previous checks are not enough since the label goes through anoteher merthod before going in the database - validate-metadata-name-is-camel-case.utils.ts : making sure we can use this error message for metadata name and for target label --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@ -44,6 +44,7 @@ import { isSelectOrMultiSelectFieldMetadata } from 'src/engine/metadata-modules/
|
|||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
|
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/relation-metadata/relation-on-delete-action.type';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||||
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
||||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||||
@ -82,6 +83,7 @@ type ValidateFieldMetadataArgs<T extends UpdateFieldInput | CreateFieldInput> =
|
|||||||
fieldMetadataInput: T;
|
fieldMetadataInput: T;
|
||||||
objectMetadata: ObjectMetadataItemWithFieldMaps;
|
objectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||||
existingFieldMetadata?: FieldMetadataInterface;
|
existingFieldMetadata?: FieldMetadataInterface;
|
||||||
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -208,6 +210,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
existingFieldMetadata,
|
existingFieldMetadata,
|
||||||
fieldMetadataInput: fieldMetadataForUpdate,
|
fieldMetadataInput: fieldMetadataForUpdate,
|
||||||
objectMetadata: objectMetadataItemWithFieldMaps,
|
objectMetadata: objectMetadataItemWithFieldMaps,
|
||||||
|
objectMetadataMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLabelSyncedWithName =
|
const isLabelSyncedWithName =
|
||||||
@ -528,6 +531,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
fieldMetadataType,
|
fieldMetadataType,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
existingFieldMetadata,
|
existingFieldMetadata,
|
||||||
|
objectMetadataMaps,
|
||||||
}: ValidateFieldMetadataArgs<T>): Promise<T> {
|
}: ValidateFieldMetadataArgs<T>): Promise<T> {
|
||||||
if (fieldMetadataInput.name) {
|
if (fieldMetadataInput.name) {
|
||||||
try {
|
try {
|
||||||
@ -602,6 +606,21 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
await this.fieldMetadataValidationService.validateRelationCreationPayloadOrThrow(
|
await this.fieldMetadataValidationService.validateRelationCreationPayloadOrThrow(
|
||||||
relationCreationPayload,
|
relationCreationPayload,
|
||||||
);
|
);
|
||||||
|
const computedMetadataNameFromLabel = computeMetadataNameFromLabel(
|
||||||
|
relationCreationPayload.targetFieldLabel,
|
||||||
|
);
|
||||||
|
|
||||||
|
validateMetadataNameOrThrow(computedMetadataNameFromLabel);
|
||||||
|
|
||||||
|
const objectMetadataTarget =
|
||||||
|
objectMetadataMaps.byId[
|
||||||
|
relationCreationPayload.targetObjectMetadataId
|
||||||
|
];
|
||||||
|
|
||||||
|
validateFieldNameAvailabilityOrThrow(
|
||||||
|
computedMetadataNameFromLabel,
|
||||||
|
objectMetadataTarget,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -728,6 +747,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
fieldMetadataInput: CreateFieldInput,
|
fieldMetadataInput: CreateFieldInput,
|
||||||
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||||
fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||||
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
): Promise<FieldMetadataEntity[]> {
|
): Promise<FieldMetadataEntity[]> {
|
||||||
if (!fieldMetadataInput.isRemoteCreation) {
|
if (!fieldMetadataInput.isRemoteCreation) {
|
||||||
assertMutationNotOnRemoteObject(objectMetadata);
|
assertMutationNotOnRemoteObject(objectMetadata);
|
||||||
@ -747,6 +767,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
relationCreationPayload: fieldMetadataInput.relationCreationPayload,
|
relationCreationPayload: fieldMetadataInput.relationCreationPayload,
|
||||||
},
|
},
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
|
objectMetadataMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (fieldMetadataForCreate.isLabelSyncedWithName === true) {
|
if (fieldMetadataForCreate.isLabelSyncedWithName === true) {
|
||||||
@ -773,13 +794,15 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetFieldMetadataName = computeMetadataNameFromLabel(
|
||||||
|
relationCreationPayload.targetFieldLabel,
|
||||||
|
);
|
||||||
|
|
||||||
const targetFieldMetadataToCreate =
|
const targetFieldMetadataToCreate =
|
||||||
this.prepareCustomFieldMetadataForCreation({
|
this.prepareCustomFieldMetadataForCreation({
|
||||||
objectMetadataId: relationCreationPayload.targetObjectMetadataId,
|
objectMetadataId: relationCreationPayload.targetObjectMetadataId,
|
||||||
type: FieldMetadataType.RELATION,
|
type: FieldMetadataType.RELATION,
|
||||||
name: computeMetadataNameFromLabel(
|
name: targetFieldMetadataName,
|
||||||
relationCreationPayload.targetFieldLabel,
|
|
||||||
),
|
|
||||||
label: relationCreationPayload.targetFieldLabel,
|
label: relationCreationPayload.targetFieldLabel,
|
||||||
icon: relationCreationPayload.targetFieldIcon,
|
icon: relationCreationPayload.targetFieldIcon,
|
||||||
workspaceId: fieldMetadataForCreate.workspaceId,
|
workspaceId: fieldMetadataForCreate.workspaceId,
|
||||||
@ -900,6 +923,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
fieldMetadataInput,
|
fieldMetadataInput,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
fieldMetadataRepository,
|
fieldMetadataRepository,
|
||||||
|
objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
createdFieldMetadatas.push(...createdFieldMetadataItems);
|
createdFieldMetadatas.push(...createdFieldMetadataItems);
|
||||||
|
|||||||
@ -8,7 +8,7 @@ exports[`validateObjectMetadataInputOrThrow should fail when namePlural is a res
|
|||||||
|
|
||||||
exports[`validateObjectMetadataInputOrThrow should fail when namePlural is an empty string 1`] = `"Input is too short: """`;
|
exports[`validateObjectMetadataInputOrThrow should fail when namePlural is an empty string 1`] = `"Input is too short: """`;
|
||||||
|
|
||||||
exports[`validateObjectMetadataInputOrThrow should fail when namePlural is not camelCased 1`] = `"Name should be in camelCase: Not_Camel_Case"`;
|
exports[`validateObjectMetadataInputOrThrow should fail when namePlural is not camelCased 1`] = `"Not_Camel_Case should be in camelCase"`;
|
||||||
|
|
||||||
exports[`validateObjectMetadataInputOrThrow should fail when nameSingular has invalid characters 1`] = `"String "μ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
exports[`validateObjectMetadataInputOrThrow should fail when nameSingular has invalid characters 1`] = `"String "μ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
||||||
|
|
||||||
@ -16,4 +16,4 @@ exports[`validateObjectMetadataInputOrThrow should fail when nameSingular is a r
|
|||||||
|
|
||||||
exports[`validateObjectMetadataInputOrThrow should fail when nameSingular is an empty string 1`] = `"Input is too short: """`;
|
exports[`validateObjectMetadataInputOrThrow should fail when nameSingular is an empty string 1`] = `"Input is too short: """`;
|
||||||
|
|
||||||
exports[`validateObjectMetadataInputOrThrow should fail when nameSingular is not camelCased 1`] = `"Name should be in camelCase: Not_Camel_Case"`;
|
exports[`validateObjectMetadataInputOrThrow should fail when nameSingular is not camelCased 1`] = `"Not_Camel_Case should be in camelCase"`;
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throw error when string is not in camel case 1`] = `"Name should be in camelCase: TestName"`;
|
exports[`validateMetadataNameOrThrow throw error when string is not in camel case 1`] = `"TestName should be in camelCase"`;
|
||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when starts with digits 1`] = `"Name should be in camelCase: 123string"`;
|
exports[`validateMetadataNameOrThrow throws error when starts with digits 1`] = `"123string should be in camelCase"`;
|
||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when string has non latin characters 1`] = `"String "בְרִבְרִ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
exports[`validateMetadataNameOrThrow throws error when string has non latin characters 1`] = `"String "בְרִבְרִ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when string has spaces 1`] = `"Name should be in camelCase: name with spaces"`;
|
exports[`validateMetadataNameOrThrow throws error when string has spaces 1`] = `"name with spaces should be in camelCase"`;
|
||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when string is a reserved word 1`] = `"The name "role" is not available"`;
|
exports[`validateMetadataNameOrThrow throws error when string is a reserved word 1`] = `"The name "role" is not available"`;
|
||||||
|
|
||||||
@ -14,4 +14,4 @@ exports[`validateMetadataNameOrThrow throws error when string is above 63 charac
|
|||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when string is empty 1`] = `"Input is too short: """`;
|
exports[`validateMetadataNameOrThrow throws error when string is empty 1`] = `"Input is too short: """`;
|
||||||
|
|
||||||
exports[`validateMetadataNameOrThrow throws error when string starts with capital letter 1`] = `"Name should be in camelCase: StringStartingWithCapitalLetter"`;
|
exports[`validateMetadataNameOrThrow throws error when string starts with capital letter 1`] = `"StringStartingWithCapitalLetter should be in camelCase"`;
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export enum InvalidMetadataExceptionCode {
|
|||||||
EXCEEDS_MAX_LENGTH = 'Exceeds max length',
|
EXCEEDS_MAX_LENGTH = 'Exceeds max length',
|
||||||
RESERVED_KEYWORD = 'Reserved keyword',
|
RESERVED_KEYWORD = 'Reserved keyword',
|
||||||
NOT_CAMEL_CASE = 'Not camel case',
|
NOT_CAMEL_CASE = 'Not camel case',
|
||||||
|
NOT_FIRST_LETTER_UPPER_CASE = 'Not first letter upper case',
|
||||||
INVALID_LABEL = 'Invalid label',
|
INVALID_LABEL = 'Invalid label',
|
||||||
NAME_NOT_SYNCED_WITH_LABEL = 'Name not synced with label',
|
NAME_NOT_SYNCED_WITH_LABEL = 'Name not synced with label',
|
||||||
INVALID_STRING = 'Invalid string',
|
INVALID_STRING = 'Invalid string',
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
export const validateMetadataNameIsCamelCaseOrThrow = (name: string) => {
|
export const validateMetadataNameIsCamelCaseOrThrow = (name: string) => {
|
||||||
if (name !== camelCase(name)) {
|
if (name !== camelCase(name)) {
|
||||||
throw new InvalidMetadataException(
|
throw new InvalidMetadataException(
|
||||||
`Name should be in camelCase: ${name}`,
|
`${name} should be in camelCase`,
|
||||||
InvalidMetadataExceptionCode.NOT_CAMEL_CASE,
|
InvalidMetadataExceptionCode.NOT_CAMEL_CASE,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,11 +24,11 @@ exports[`Object metadata creation should fail when namePlural is a reserved keyw
|
|||||||
|
|
||||||
exports[`Object metadata creation should fail when namePlural is an empty string 1`] = `"Input is too short: """`;
|
exports[`Object metadata creation should fail when namePlural is an empty string 1`] = `"Input is too short: """`;
|
||||||
|
|
||||||
exports[`Object metadata creation should fail when namePlural is not camelCased 1`] = `"Name should be in camelCase: Not_Camel_Case"`;
|
exports[`Object metadata creation should fail when namePlural is not camelCased 1`] = `"Not_Camel_Case should be in camelCase"`;
|
||||||
|
|
||||||
exports[`Object metadata creation should fail when nameSingular contains only one char and whitespaces 1`] = `"Name should be in camelCase: a a "`;
|
exports[`Object metadata creation should fail when nameSingular contains only one char and whitespaces 1`] = `" a a should be in camelCase"`;
|
||||||
|
|
||||||
exports[`Object metadata creation should fail when nameSingular contains only whitespaces 1`] = `"Name should be in camelCase: "`;
|
exports[`Object metadata creation should fail when nameSingular contains only whitespaces 1`] = `" should be in camelCase"`;
|
||||||
|
|
||||||
exports[`Object metadata creation should fail when nameSingular has invalid characters 1`] = `"String "μ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
exports[`Object metadata creation should fail when nameSingular has invalid characters 1`] = `"String "μ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
||||||
|
|
||||||
@ -36,8 +36,8 @@ exports[`Object metadata creation should fail when nameSingular is a reserved ke
|
|||||||
|
|
||||||
exports[`Object metadata creation should fail when nameSingular is an empty string 1`] = `"Input is too short: """`;
|
exports[`Object metadata creation should fail when nameSingular is an empty string 1`] = `"Input is too short: """`;
|
||||||
|
|
||||||
exports[`Object metadata creation should fail when nameSingular is not camelCased 1`] = `"Name should be in camelCase: Not_Camel_Case"`;
|
exports[`Object metadata creation should fail when nameSingular is not camelCased 1`] = `"Not_Camel_Case should be in camelCase"`;
|
||||||
|
|
||||||
exports[`Object metadata creation should fail when names are identical 1`] = `"The singular and plural names cannot be the same for an object"`;
|
exports[`Object metadata creation should fail when names are identical 1`] = `"The singular and plural names cannot be the same for an object"`;
|
||||||
|
|
||||||
exports[`Object metadata creation should fail when names with whitespaces result to be identical 1`] = `"Name should be in camelCase: fooBar "`;
|
exports[`Object metadata creation should fail when names with whitespaces result to be identical 1`] = `" fooBar should be in camelCase"`;
|
||||||
|
|||||||
Reference in New Issue
Block a user