diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f3f9645e0..986bad155 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -204,7 +204,7 @@ export type CreateFieldInput = { icon?: InputMaybe; isActive?: InputMaybe; isCustom?: InputMaybe; - isLabelSyncedWithName: Scalars['Boolean']['input']; + isLabelSyncedWithName?: InputMaybe; isNullable?: InputMaybe; isRemoteCreation?: InputMaybe; isSystem?: InputMaybe; @@ -1569,10 +1569,10 @@ export type UpdateServerlessFunctionInput = { }; export type UpdateWorkflowVersionStepInput = { - /** Step to update in JSON format */ - step: Scalars['JSON']['input']; /** Boolean to check if we need to update stepOutput */ shouldUpdateStepOutput?: InputMaybe; + /** Step to update in JSON format */ + step: Scalars['JSON']['input']; /** Workflow version ID */ workflowVersionId: Scalars['String']['input']; }; @@ -1707,6 +1707,7 @@ export type Workspace = { __typename?: 'Workspace'; activationStatus: WorkspaceActivationStatus; allowImpersonation: Scalars['Boolean']['output']; + billingCustomers?: Maybe>; billingEntitlements?: Maybe>; billingSubscriptions?: Maybe>; createdAt: Scalars['DateTime']['output']; @@ -1732,6 +1733,12 @@ export type Workspace = { }; +export type WorkspaceBillingCustomersArgs = { + filter?: BillingCustomerFilter; + sorting?: Array; +}; + + export type WorkspaceBillingEntitlementsArgs = { filter?: BillingEntitlementFilter; sorting?: Array; @@ -1819,6 +1826,27 @@ export type WorkspaceNameAndId = { id: Scalars['String']['output']; }; +export type BillingCustomer = { + __typename?: 'billingCustomer'; + id: Scalars['UUID']['output']; +}; + +export type BillingCustomerFilter = { + and?: InputMaybe>; + id?: InputMaybe; + or?: InputMaybe>; +}; + +export type BillingCustomerSort = { + direction: SortDirection; + field: BillingCustomerSortFields; + nulls?: InputMaybe; +}; + +export enum BillingCustomerSortFields { + Id = 'id' +} + export type BillingEntitlement = { __typename?: 'billingEntitlement'; id: Scalars['UUID']['output']; @@ -1853,7 +1881,7 @@ export type Field = { id: Scalars['UUID']['output']; isActive?: Maybe; isCustom?: Maybe; - isLabelSyncedWithName: Scalars['Boolean']['output']; + isLabelSyncedWithName?: Maybe; isNullable?: Maybe; isSystem?: Maybe; isUnique?: Maybe; @@ -2120,7 +2148,7 @@ export type UpdateOneFieldMetadataItemMutationVariables = Exact<{ }>; -export type UpdateOneFieldMetadataItemMutation = { __typename?: 'Mutation', updateOneField: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any, settings?: any | null, isLabelSyncedWithName: boolean } }; +export type UpdateOneFieldMetadataItemMutation = { __typename?: 'Mutation', updateOneField: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isNullable?: boolean | null, createdAt: any, updatedAt: any, settings?: any | null, isLabelSyncedWithName?: boolean | null } }; export type UpdateOneObjectMetadataItemMutationVariables = Exact<{ idToUpdate: Scalars['UUID']['input']; @@ -2157,7 +2185,7 @@ export type ObjectMetadataItemsQueryVariables = Exact<{ }>; -export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'objectEdge', node: { __typename?: 'object', id: any, dataSourceId: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, createdAt: any, updatedAt: any, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, indexMetadatas: { __typename?: 'ObjectIndexMetadatasConnection', edges: Array<{ __typename?: 'indexEdge', node: { __typename?: 'index', id: any, createdAt: any, updatedAt: any, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, indexFieldMetadatas: { __typename?: 'IndexIndexFieldMetadatasConnection', edges: Array<{ __typename?: 'indexFieldEdge', node: { __typename?: 'indexField', id: any, createdAt: any, updatedAt: any, order: number, fieldMetadataId: any } }> } } }> }, fields: { __typename?: 'ObjectFieldsConnection', edges: Array<{ __typename?: 'fieldEdge', node: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: any, updatedAt: any, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName: boolean, relationDefinition?: { __typename?: 'RelationDefinition', relationId: any, direction: RelationDefinitionType, sourceObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'field', id: any, name: string }, targetObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, targetFieldMetadata: { __typename?: 'field', id: any, name: string } } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; +export type ObjectMetadataItemsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', edges: Array<{ __typename?: 'objectEdge', node: { __typename?: 'object', id: any, dataSourceId: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, createdAt: any, updatedAt: any, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, indexMetadatas: { __typename?: 'ObjectIndexMetadatasConnection', edges: Array<{ __typename?: 'indexEdge', node: { __typename?: 'index', id: any, createdAt: any, updatedAt: any, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, indexFieldMetadatas: { __typename?: 'IndexIndexFieldMetadatasConnection', edges: Array<{ __typename?: 'indexFieldEdge', node: { __typename?: 'indexField', id: any, createdAt: any, updatedAt: any, order: number, fieldMetadataId: any } }> } } }> }, fields: { __typename?: 'ObjectFieldsConnection', edges: Array<{ __typename?: 'fieldEdge', node: { __typename?: 'field', id: any, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: any, updatedAt: any, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relationDefinition?: { __typename?: 'RelationDefinition', relationId: any, direction: RelationDefinitionType, sourceObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'field', id: any, name: string }, targetObjectMetadata: { __typename?: 'object', id: any, nameSingular: string, namePlural: string }, targetFieldMetadata: { __typename?: 'field', id: any, name: string } } | null } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } } }>, pageInfo: { __typename?: 'PageInfo', hasNextPage?: boolean | null, hasPreviousPage?: boolean | null, startCursor?: any | null, endCursor?: any | null } } }; export type ServerlessFunctionFieldsFragment = { __typename?: 'ServerlessFunction', id: any, name: string, description?: string | null, runtime: string, syncStatus: ServerlessFunctionSyncStatus, latestVersion?: string | null, latestVersionInputSchema?: any | null, publishedVersions: Array, createdAt: any, updatedAt: any }; @@ -2251,4 +2279,4 @@ export const UpdateOneServerlessFunctionDocument = {"kind":"Document","definitio export const FindManyAvailablePackagesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyAvailablePackages"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunctionIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAvailablePackages"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const GetManyServerlessFunctionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetManyServerlessFunctions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyServerlessFunctions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersionInputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"publishedVersions"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const GetOneServerlessFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetOneServerlessFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunctionIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneServerlessFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ServerlessFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerlessFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerlessFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersion"}},{"kind":"Field","name":{"kind":"Name","value":"latestVersionInputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"publishedVersions"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; -export const FindOneServerlessFunctionSourceCodeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneServerlessFunctionSourceCode"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GetServerlessFunctionSourceCodeInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getServerlessFunctionSourceCode"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; +export const FindOneServerlessFunctionSourceCodeDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneServerlessFunctionSourceCode"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GetServerlessFunctionSourceCodeInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getServerlessFunctionSourceCode"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts index 7e71cedbf..d14dce5de 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/FieldMetadataItem.ts @@ -39,5 +39,5 @@ export type FieldMetadataItem = Omit< settings?: { displayAsRelativeDate?: boolean; }; - isLabelSyncedWithName?: boolean; + isLabelSyncedWithName?: boolean | null; }; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx index f2ab1045d..1af2f9fe9 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldIconLabelForm.tsx @@ -164,7 +164,9 @@ export const SettingsDataModelFieldIconLabelForm = ({ placeholder="employees" value={value} onChange={onChange} - disabled={disabled || isLabelSyncedWithName} + disabled={ + disabled || (isLabelSyncedWithName ?? false) + } fullWidth maxLength={DATABASE_IDENTIFIER_MAXIMUM_LENGTH} RightIcon={() => diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts index 1a674b7bf..fb51feaf0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto.ts @@ -147,7 +147,7 @@ export class FieldMetadataDTO< @IsBoolean() @IsOptional() - @Field() + @Field({ nullable: true }) isLabelSyncedWithName?: boolean; @IsDateString() diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index 5c811ebf2..b9cc6065e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -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 { + 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', + ); + }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata.integration-spec.ts new file mode 100644 index 000000000..2989f8ea4 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/update-one-field-metadata.integration-spec.ts @@ -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', + ); + }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util.ts new file mode 100644 index 000000000..04dbc7e50 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util.ts @@ -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 }; +}; + +export const createOneFieldMetadataFactory = ({ + gqlFields, + input, +}: CreateOneFieldFactoryParams) => ({ + query: gql` + mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) { + createOneField(input: $input) { + ${gqlFields} + } + } + `, + variables: { + input, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/create-test-field-metadata.util.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/create-test-field-metadata.util.ts new file mode 100644 index 000000000..7d5af0133 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/create-test-field-metadata.util.ts @@ -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 }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util.ts new file mode 100644 index 000000000..b1f75592b --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util.ts @@ -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, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util.ts new file mode 100644 index 000000000..991c7cf43 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util.ts @@ -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); +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util.ts new file mode 100644 index 000000000..ddbd843d2 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util.ts @@ -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 }; +}; + +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, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts index 15507473f..9eb5773b2 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/rename-custom-object.integration-spec.ts @@ -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'; diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/create-one-object-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util.ts similarity index 100% rename from packages/twenty-server/test/integration/metadata/suites/utils/create-one-object-metadata-factory.util.ts rename to packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util.ts diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util.ts new file mode 100644 index 000000000..bc64f863f --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util.ts @@ -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, + }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/delete-one-object-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util.ts similarity index 100% rename from packages/twenty-server/test/integration/metadata/suites/utils/delete-one-object-metadata-factory.util.ts rename to packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util.ts diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util.ts new file mode 100644 index 000000000..8b0b561f8 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util.ts @@ -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); +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/objects-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/objects-metadata-factory.util.ts similarity index 100% rename from packages/twenty-server/test/integration/metadata/suites/utils/objects-metadata-factory.util.ts rename to packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/objects-metadata-factory.util.ts diff --git a/packages/twenty-server/test/integration/metadata/suites/utils/update-one-object-metadata-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-factory.util.ts similarity index 100% rename from packages/twenty-server/test/integration/metadata/suites/utils/update-one-object-metadata-factory.util.ts rename to packages/twenty-server/test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-factory.util.ts