[Fix] isLabelSyncedWithName should be nullable (#9028)
isLabelSyncedWithName should be nullable for fieldMetadata, as it is for objectMetadata. + Adding missing validation on label and name sync in fieldMetadataService for creation and update + adding metadata tests
This commit is contained in:
@ -147,7 +147,7 @@ export class FieldMetadataDTO<
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@Field()
|
||||
@Field({ nullable: true })
|
||||
isLabelSyncedWithName?: boolean;
|
||||
|
||||
@IsDateString()
|
||||
|
||||
@ -32,6 +32,7 @@ import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-
|
||||
import { isSelectFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-select-field-metadata-type.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||
import {
|
||||
RelationMetadataEntity,
|
||||
RelationMetadataType,
|
||||
@ -175,6 +176,13 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
objectMetadata,
|
||||
);
|
||||
|
||||
if (fieldMetadataForCreate.isLabelSyncedWithName === true) {
|
||||
validateNameAndLabelAreSyncOrThrow(
|
||||
fieldMetadataForCreate.label,
|
||||
fieldMetadataForCreate.name,
|
||||
);
|
||||
}
|
||||
|
||||
console.time('createOne save');
|
||||
const createdFieldMetadata = await fieldMetadataRepository.save(
|
||||
fieldMetadataForCreate,
|
||||
@ -407,6 +415,17 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
objectMetadata,
|
||||
);
|
||||
|
||||
const isLabelSyncedWithName =
|
||||
fieldMetadataForUpdate.isLabelSyncedWithName ??
|
||||
existingFieldMetadata.isLabelSyncedWithName;
|
||||
|
||||
if (isLabelSyncedWithName) {
|
||||
validateNameAndLabelAreSyncOrThrow(
|
||||
fieldMetadataForUpdate.label ?? existingFieldMetadata.label,
|
||||
fieldMetadataForUpdate.name ?? existingFieldMetadata.name,
|
||||
);
|
||||
}
|
||||
|
||||
// We're running field update under a transaction, so we can rollback if migration fails
|
||||
await fieldMetadataRepository.update(id, fieldMetadataForUpdate);
|
||||
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
describe('createOne', () => {
|
||||
describe('FieldMetadataService name/label sync', () => {
|
||||
let listingObjectId = '';
|
||||
|
||||
beforeEach(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
});
|
||||
afterEach(async () => {
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
});
|
||||
it('should create a field when name and label are synced correctly', async () => {
|
||||
// Arrange
|
||||
const FIELD_NAME = 'testField';
|
||||
const createFieldInput = {
|
||||
name: FIELD_NAME,
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: listingObjectId,
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.createOneField.name).toBe(FIELD_NAME);
|
||||
});
|
||||
|
||||
it('should set isLabelSyncWithName to false if not in input', async () => {
|
||||
// Arrange
|
||||
const createFieldInput = {
|
||||
name: 'testField',
|
||||
label: 'Test Field',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: listingObjectId,
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.createOneField.isLabelSyncedWithName).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an error when name and label are not synced but isLabelSyncedWithName is true', async () => {
|
||||
// Arrange
|
||||
const createFieldInput = {
|
||||
name: 'testField',
|
||||
label: 'Different Label',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: listingObjectId,
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
// Act
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.errors[0].message).toBe(
|
||||
'Name is not synced with label. Expected name: "differentLabel", got testField',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,103 @@
|
||||
import { createTestTextFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-test-field-metadata.util';
|
||||
import { deleteFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util';
|
||||
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util';
|
||||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
|
||||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
|
||||
describe('updateOne', () => {
|
||||
describe('FieldMetadataService name/label sync', () => {
|
||||
let listingObjectId = '';
|
||||
let testFieldId = '';
|
||||
|
||||
beforeEach(async () => {
|
||||
const { objectMetadataId: createdObjectId } =
|
||||
await createListingCustomObject();
|
||||
|
||||
listingObjectId = createdObjectId;
|
||||
|
||||
const { fieldMetadataId: createdFieldMetadaId } =
|
||||
await createTestTextFieldMetadata(createdObjectId);
|
||||
|
||||
testFieldId = createdFieldMetadaId;
|
||||
});
|
||||
afterEach(async () => {
|
||||
await deleteFieldMetadata(testFieldId);
|
||||
await deleteOneObjectMetadataItem(listingObjectId);
|
||||
});
|
||||
|
||||
it('should update a field name and label when they are synced correctly', async () => {
|
||||
// Arrange
|
||||
const updateFieldInput = {
|
||||
name: 'newName',
|
||||
label: 'New name',
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
||||
input: { id: testFieldId, update: updateFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.updateOneField.name).toBe('newName');
|
||||
});
|
||||
|
||||
it('should update a field name and label when they are not synced correctly and labelSync is false', async () => {
|
||||
// Arrange
|
||||
const updateFieldInput = {
|
||||
name: 'differentName',
|
||||
label: 'New name',
|
||||
isLabelSyncedWithName: false,
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
||||
input: { id: testFieldId, update: updateFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.data.updateOneField.name).toBe('differentName');
|
||||
});
|
||||
|
||||
it('should not update a field name if it is not synced correctly with label and labelSync is true', async () => {
|
||||
// Arrange
|
||||
const updateFieldInput = {
|
||||
name: 'newName',
|
||||
};
|
||||
|
||||
// Act
|
||||
const graphqlOperation = updateOneFieldMetadataFactory({
|
||||
input: { id: testFieldId, update: updateFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
// Assert
|
||||
expect(response.body.errors[0].message).toBe(
|
||||
'Name is not synced with label. Expected name: "testName", got newName',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,24 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
|
||||
type CreateOneFieldFactoryParams = {
|
||||
gqlFields: string;
|
||||
input?: { field: Omit<CreateFieldInput, 'workspaceId' | 'dataSourceId'> };
|
||||
};
|
||||
|
||||
export const createOneFieldMetadataFactory = ({
|
||||
gqlFields,
|
||||
input,
|
||||
}: CreateOneFieldFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {
|
||||
createOneField(input: $input) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,31 @@
|
||||
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
const FIELD_NAME = 'testName';
|
||||
|
||||
export const createTestTextFieldMetadata = async (
|
||||
objectMetadataItemId: string,
|
||||
) => {
|
||||
const createFieldInput = {
|
||||
name: FIELD_NAME,
|
||||
label: 'Test name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
objectMetadataId: objectMetadataItemId,
|
||||
isLabelSyncedWithName: true,
|
||||
};
|
||||
const graphqlOperation = createOneFieldMetadataFactory({
|
||||
input: { field: createFieldInput },
|
||||
gqlFields: `
|
||||
id
|
||||
name
|
||||
label
|
||||
isLabelSyncedWithName
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(graphqlOperation);
|
||||
|
||||
return { fieldMetadataId: response.body.data.createOneField.id };
|
||||
};
|
||||
@ -0,0 +1,20 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
type DeleteOneFieldFactoryParams = {
|
||||
idToDelete: string;
|
||||
};
|
||||
|
||||
export const deleteOneFieldMetadataItemFactory = ({
|
||||
idToDelete,
|
||||
}: DeleteOneFieldFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {
|
||||
deleteOneField(input: { id: $idToDelete }) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
idToDelete,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { deleteOneFieldMetadataItemFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util';
|
||||
|
||||
export const deleteFieldMetadata = async (fieldMetadataId: string) => {
|
||||
const graphqlOperation = deleteOneFieldMetadataItemFactory({
|
||||
idToDelete: fieldMetadataId,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(graphqlOperation);
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||
|
||||
type UpdateOneFieldFactoryParams = {
|
||||
gqlFields: string;
|
||||
input: { id: string; update: Omit<UpdateFieldInput, 'workspaceId' | 'id'> };
|
||||
};
|
||||
|
||||
export const updateOneFieldMetadataFactory = ({
|
||||
gqlFields,
|
||||
input,
|
||||
}: UpdateOneFieldFactoryParams) => ({
|
||||
query: gql`
|
||||
mutation UpdateOneFieldMetadataItem($idToUpdate: UUID!, $updatePayload: UpdateFieldInput!) {
|
||||
updateOneField(input: {id: $idToUpdate, update: $updatePayload}) {
|
||||
${gqlFields}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
idToUpdate: input.id,
|
||||
updatePayload: input.update,
|
||||
},
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/utils/create-one-object-metadata-factory.util';
|
||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
|
||||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util';
|
||||
import { createOneRelationMetadataFactory } from 'test/integration/metadata/suites/utils/create-one-relation-metadata-factory.util';
|
||||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/utils/delete-one-object-metadata-factory.util';
|
||||
import { deleteOneRelationMetadataItemFactory } from 'test/integration/metadata/suites/utils/delete-one-relation-metadata-factory.util';
|
||||
import { fieldsMetadataFactory } from 'test/integration/metadata/suites/utils/fields-metadata-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util';
|
||||
import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util';
|
||||
|
||||
const LISTING_NAME_SINGULAR = 'listing';
|
||||
|
||||
const LISTING_OBJECT = {
|
||||
namePlural: 'listings',
|
||||
nameSingular: LISTING_NAME_SINGULAR,
|
||||
labelPlural: 'Listings',
|
||||
labelSingular: 'Listing',
|
||||
description: 'Listing object',
|
||||
icon: 'IconListNumbers',
|
||||
isLabelSyncedWithName: false,
|
||||
};
|
||||
|
||||
export const createListingCustomObject = async () => {
|
||||
const createObjectOperation = createOneObjectMetadataFactory({
|
||||
input: { object: LISTING_OBJECT },
|
||||
gqlFields: `
|
||||
id
|
||||
nameSingular
|
||||
`,
|
||||
});
|
||||
|
||||
const response = await makeMetadataAPIRequest(createObjectOperation);
|
||||
|
||||
return {
|
||||
objectMetadataId: response.body.data.createOneObject.id,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util';
|
||||
|
||||
export const deleteOneObjectMetadataItem = async (
|
||||
objectMetadataItemId: string,
|
||||
) => {
|
||||
const graphqlOperation = deleteOneObjectMetadataItemFactory({
|
||||
idToDelete: objectMetadataItemId,
|
||||
});
|
||||
|
||||
await makeGraphqlAPIRequest(graphqlOperation);
|
||||
};
|
||||
Reference in New Issue
Block a user