diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts index 47b934522d4..fcfd5718b53 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -5,6 +5,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; +import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; @@ -165,6 +166,11 @@ export const useUpdateOneRecord = < const record = data?.[mutationResponseField]; if (!isDefined(record)) return; + const recordToUpsert = getRecordFromRecordNode({ + recordNode: record, + }); + upsertRecordsInStore({ partialRecords: [recordToUpsert] }); + triggerUpdateRecordOptimisticEffect({ cache, objectMetadataItem, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/__tests__/activity-query-result-getter.handler.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/__tests__/activity-query-result-getter.handler.spec.ts index b1b29544ef2..adc590d680c 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/__tests__/activity-query-result-getter.handler.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/__tests__/activity-query-result-getter.handler.spec.ts @@ -20,6 +20,12 @@ const baseNote = { workspaceMemberId: '1', context: {}, }, + updatedBy: { + name: 'Test', + source: FieldActorSource.MANUAL, + workspaceMemberId: '1', + context: {}, + }, createdAt: '2021-01-01', updatedAt: '2021-01-01', noteTargets: [], diff --git a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts index 76b8ff288b0..e9c4e96b601 100644 --- a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts @@ -15,7 +15,7 @@ import { RestToCommonSelectedFieldsHandler } from 'src/engine/api/rest/core/rest import { parseCorePath } from 'src/engine/api/rest/input-request-parsers/path-parser-utils/parse-core-path.utils'; import { Depth } from 'src/engine/api/rest/input-request-parsers/types/depth.type'; import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request'; -import { CreatedByFromAuthContextService } from 'src/engine/core-modules/actor/services/created-by-from-auth-context.service'; +import { ActorFromAuthContextService } from 'src/engine/core-modules/actor/services/actor-from-auth-context.service'; import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service'; import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service'; @@ -57,7 +57,7 @@ export abstract class RestApiBaseHandler { @Inject() protected readonly workspaceCacheService: WorkspaceCacheService; @Inject() - protected readonly createdByFromAuthContextService: CreatedByFromAuthContextService; + protected readonly actorFromAuthContextService: ActorFromAuthContextService; @Inject() protected readonly workspaceCacheStorageService: WorkspaceCacheStorageService; @Inject() diff --git a/packages/twenty-server/src/engine/core-modules/actor/actor.module.ts b/packages/twenty-server/src/engine/core-modules/actor/actor.module.ts index 1b686f7c38c..a1cb20919ae 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/actor.module.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/actor.module.ts @@ -3,10 +3,12 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { CreatedByCreateManyPreQueryHook } from 'src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook'; import { CreatedByCreateOnePreQueryHook } from 'src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook'; +import { UpdatedByUpdateManyPreQueryHook } from 'src/engine/core-modules/actor/query-hooks/updated-by.update-many.pre-query-hook'; +import { UpdatedByUpdateOnePreQueryHook } from 'src/engine/core-modules/actor/query-hooks/updated-by.update-one.pre-query-hook'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; -import { CreatedByFromAuthContextService } from './services/created-by-from-auth-context.service'; +import { ActorFromAuthContextService } from './services/actor-from-auth-context.service'; @Module({ imports: [ @@ -16,12 +18,16 @@ import { CreatedByFromAuthContextService } from './services/created-by-from-auth providers: [ CreatedByCreateManyPreQueryHook, CreatedByCreateOnePreQueryHook, - CreatedByFromAuthContextService, + UpdatedByUpdateManyPreQueryHook, + UpdatedByUpdateOnePreQueryHook, + ActorFromAuthContextService, ], exports: [ CreatedByCreateManyPreQueryHook, CreatedByCreateOnePreQueryHook, - CreatedByFromAuthContextService, + UpdatedByUpdateManyPreQueryHook, + UpdatedByUpdateOnePreQueryHook, + ActorFromAuthContextService, ], }) export class ActorModule {} diff --git a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook.ts b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook.ts index 21ccf7ebb51..901aedbc642 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-many.pre-query-hook.ts @@ -9,9 +9,9 @@ import { } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { - CreatedByFromAuthContextService, - type CreateInput, -} from 'src/engine/core-modules/actor/services/created-by-from-auth-context.service'; + ActorFromAuthContextService, + type RecordInput, +} from 'src/engine/core-modules/actor/services/actor-from-auth-context.service'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; @WorkspaceQueryHook(`*.createMany`) @@ -19,14 +19,14 @@ export class CreatedByCreateManyPreQueryHook implements WorkspacePreQueryHookInstance { constructor( - private readonly createdByFromAuthContextService: CreatedByFromAuthContextService, + private readonly actorFromAuthContextService: ActorFromAuthContextService, ) {} async execute( authContext: AuthContext, objectName: string, - payload: CreateManyResolverArgs, - ): Promise> { + payload: CreateManyResolverArgs, + ): Promise> { if (!isDefined(payload.data)) { throw new GraphqlQueryRunnerException( 'Payload data is required', @@ -36,11 +36,11 @@ export class CreatedByCreateManyPreQueryHook return { ...payload, - data: await this.createdByFromAuthContextService.injectCreatedBy( - payload.data, - objectName, + data: await this.actorFromAuthContextService.injectActorFieldsOnCreate({ + records: payload.data, + objectMetadataNameSingular: objectName, authContext, - ), + }), }; } } diff --git a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook.ts b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook.ts index 68fb9d5e9f9..e56a7621b1e 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/created-by.create-one.pre-query-hook.ts @@ -1,4 +1,4 @@ -import { isDefined } from 'class-validator'; +import { isDefined } from 'twenty-shared/utils'; import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { type CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; @@ -9,9 +9,9 @@ import { } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { - CreatedByFromAuthContextService, - type CreateInput, -} from 'src/engine/core-modules/actor/services/created-by-from-auth-context.service'; + ActorFromAuthContextService, + type RecordInput, +} from 'src/engine/core-modules/actor/services/actor-from-auth-context.service'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; @WorkspaceQueryHook(`*.createOne`) @@ -19,14 +19,14 @@ export class CreatedByCreateOnePreQueryHook implements WorkspacePreQueryHookInstance { constructor( - private readonly createdByFromAuthContextService: CreatedByFromAuthContextService, + private readonly actorFromAuthContextService: ActorFromAuthContextService, ) {} async execute( authContext: AuthContext, objectName: string, - payload: CreateOneResolverArgs, - ): Promise> { + payload: CreateOneResolverArgs, + ): Promise> { if (!isDefined(payload.data)) { throw new GraphqlQueryRunnerException( 'Payload data is required', @@ -35,11 +35,11 @@ export class CreatedByCreateOnePreQueryHook } const [recordToCreateData] = - await this.createdByFromAuthContextService.injectCreatedBy( - [payload.data], - objectName, + await this.actorFromAuthContextService.injectActorFieldsOnCreate({ + records: [payload.data], + objectMetadataNameSingular: objectName, authContext, - ); + }); return { ...payload, diff --git a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/updated-by.update-many.pre-query-hook.ts b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/updated-by.update-many.pre-query-hook.ts new file mode 100644 index 00000000000..bea13593c23 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/updated-by.update-many.pre-query-hook.ts @@ -0,0 +1,49 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; +import { type UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { + ActorFromAuthContextService, + type RecordInput, +} from 'src/engine/core-modules/actor/services/actor-from-auth-context.service'; +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; + +@WorkspaceQueryHook(`*.updateMany`) +export class UpdatedByUpdateManyPreQueryHook + implements WorkspacePreQueryHookInstance +{ + constructor( + private readonly actorFromAuthContextService: ActorFromAuthContextService, + ) {} + + async execute( + authContext: AuthContext, + objectName: string, + payload: UpdateManyResolverArgs, + ): Promise> { + if (!isDefined(payload.data)) { + throw new GraphqlQueryRunnerException( + 'Payload data is required', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + + const [recordToUpdateData] = + await this.actorFromAuthContextService.injectUpdatedBy({ + records: [payload.data], + objectMetadataNameSingular: objectName, + authContext, + }); + + return { + ...payload, + data: recordToUpdateData, + }; + } +} diff --git a/packages/twenty-server/src/engine/core-modules/actor/query-hooks/updated-by.update-one.pre-query-hook.ts b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/updated-by.update-one.pre-query-hook.ts new file mode 100644 index 00000000000..08fcbc4465c --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/actor/query-hooks/updated-by.update-one.pre-query-hook.ts @@ -0,0 +1,49 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; +import { type UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { + GraphqlQueryRunnerException, + GraphqlQueryRunnerExceptionCode, +} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { + ActorFromAuthContextService, + type RecordInput, +} from 'src/engine/core-modules/actor/services/actor-from-auth-context.service'; +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; + +@WorkspaceQueryHook(`*.updateOne`) +export class UpdatedByUpdateOnePreQueryHook + implements WorkspacePreQueryHookInstance +{ + constructor( + private readonly actorFromAuthContextService: ActorFromAuthContextService, + ) {} + + async execute( + authContext: AuthContext, + objectName: string, + payload: UpdateOneResolverArgs, + ): Promise> { + if (!isDefined(payload.data)) { + throw new GraphqlQueryRunnerException( + 'Payload data is required', + GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT, + ); + } + + const [recordToUpdateData] = + await this.actorFromAuthContextService.injectUpdatedBy({ + records: [payload.data], + objectMetadataNameSingular: objectName, + authContext, + }); + + return { + ...payload, + data: recordToUpdateData, + }; + } +} diff --git a/packages/twenty-server/src/engine/core-modules/actor/services/__tests__/created-by-from-auth-context.service.spec.ts b/packages/twenty-server/src/engine/core-modules/actor/services/__tests__/actor-from-auth-context.service.spec.ts similarity index 84% rename from packages/twenty-server/src/engine/core-modules/actor/services/__tests__/created-by-from-auth-context.service.spec.ts rename to packages/twenty-server/src/engine/core-modules/actor/services/__tests__/actor-from-auth-context.service.spec.ts index 9d8be929279..ea7ef01af35 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/services/__tests__/created-by-from-auth-context.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/services/__tests__/actor-from-auth-context.service.spec.ts @@ -7,7 +7,7 @@ import { type FullNameMetadata, } from 'twenty-shared/types'; -import { CreatedByFromAuthContextService } from 'src/engine/core-modules/actor/services/created-by-from-auth-context.service'; +import { ActorFromAuthContextService } from 'src/engine/core-modules/actor/services/actor-from-auth-context.service'; import { type ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { type UserEntity } from 'src/engine/core-modules/user/user.entity'; @@ -31,8 +31,8 @@ const fromFullNameMetadataToName = ({ lastName, }: FullNameMetadata) => `${firstName} ${lastName}`; -describe('CreatedByFromAuthContextService', () => { - let service: CreatedByFromAuthContextService; +describe('ActorFromAuthContextService', () => { + let service: ActorFromAuthContextService; const mockWorkspaceMemberRepository = { findOneOrFail: jest.fn(), }; @@ -51,7 +51,7 @@ describe('CreatedByFromAuthContextService', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ - CreatedByFromAuthContextService, + ActorFromAuthContextService, { provide: GlobalWorkspaceOrmManager, useValue: globalWorkspaceOrmManager, @@ -93,8 +93,8 @@ describe('CreatedByFromAuthContextService', () => { ], }).compile(); - service = module.get( - CreatedByFromAuthContextService, + service = module.get( + ActorFromAuthContextService, ); }); @@ -129,11 +129,11 @@ describe('CreatedByFromAuthContextService', () => { mockedWorkspaceMember, ); - const result = await service.injectCreatedBy( - [{}], - 'person', - authContext as AuthContext, - ); + const result = await service.injectCreatedBy({ + records: [{}], + objectMetadataNameSingular: 'person', + authContext: authContext as AuthContext, + }); expect(result).toEqual([ { @@ -169,11 +169,11 @@ describe('CreatedByFromAuthContextService', () => { mockedWorkspaceMember, ); - const result = await service.injectCreatedBy( - [{}], - 'person', - authContext as AuthContext, - ); + const result = await service.injectCreatedBy({ + records: [{}], + objectMetadataNameSingular: 'person', + authContext: authContext as AuthContext, + }); expect(result).toEqual([ { @@ -196,11 +196,11 @@ describe('CreatedByFromAuthContextService', () => { workspace: { id: '20202020-bdec-497f-847a-1bb334fefe58' }, } as const satisfies TestingAuthContext; - const result = await service.injectCreatedBy( - [{}], - 'person', - authContext as AuthContext, - ); + const result = await service.injectCreatedBy({ + records: [{}], + objectMetadataNameSingular: 'person', + authContext: authContext as AuthContext, + }); expect(result).toEqual([ { @@ -220,9 +220,13 @@ describe('CreatedByFromAuthContextService', () => { } as const satisfies TestingAuthContext; await expect( - service.injectCreatedBy([{}], 'person', authContext as AuthContext), + service.injectCreatedBy({ + records: [{}], + objectMetadataNameSingular: 'person', + authContext: authContext as AuthContext, + }), ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unable to build createdBy metadata - no valid actor information found in auth context"`, + `"Unable to build actor metadata - no valid actor information found in auth context"`, ); }); }); diff --git a/packages/twenty-server/src/engine/core-modules/actor/services/created-by-from-auth-context.service.ts b/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts similarity index 61% rename from packages/twenty-server/src/engine/core-modules/actor/services/created-by-from-auth-context.service.ts rename to packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts index 16adc4e8d85..46fdfdaf879 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/services/created-by-from-auth-context.service.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts @@ -6,6 +6,7 @@ import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { buildCreatedByFromApiKey } from 'src/engine/core-modules/actor/utils/build-created-by-from-api-key.util'; +import { buildCreatedByFromApplication } from 'src/engine/core-modules/actor/utils/build-created-by-from-application.util'; import { buildCreatedByFromFullNameMetadata } from 'src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; @@ -14,25 +15,78 @@ import { buildFieldMapsFromFlatObjectMetadata } from 'src/engine/metadata-module import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; -import { buildCreatedByFromApplication } from 'src/engine/core-modules/actor/utils/build-created-by-from-application.util'; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type CreateInput = Record; +export type RecordInput = Record; + +export type InjectActorParams = { + records: RecordInput[]; + objectMetadataNameSingular: string; + authContext: AuthContext; +}; @Injectable() -export class CreatedByFromAuthContextService { - private readonly logger = new Logger(CreatedByFromAuthContextService.name); +export class ActorFromAuthContextService { + private readonly logger = new Logger(ActorFromAuthContextService.name); constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} - async injectCreatedBy( - records: CreateInput[], - objectMetadataNameSingular: string, - authContext: AuthContext, - ): Promise { + async injectCreatedBy({ + records, + objectMetadataNameSingular, + authContext, + }: InjectActorParams): Promise { + return this.injectActorField({ + records, + objectMetadataNameSingular, + authContext, + fieldName: 'createdBy', + }); + } + + async injectActorFieldsOnCreate({ + records, + objectMetadataNameSingular, + authContext, + }: InjectActorParams): Promise { + const recordsWithCreatedBy = await this.injectActorField({ + records, + objectMetadataNameSingular, + authContext, + fieldName: 'createdBy', + }); + + return await this.injectActorField({ + records: recordsWithCreatedBy, + objectMetadataNameSingular, + authContext, + fieldName: 'updatedBy', + }); + } + + async injectUpdatedBy({ + records, + objectMetadataNameSingular, + authContext, + }: InjectActorParams): Promise { + return this.injectActorField({ + records, + objectMetadataNameSingular, + authContext, + fieldName: 'updatedBy', + }); + } + + private async injectActorField({ + records, + objectMetadataNameSingular, + authContext, + fieldName, + }: InjectActorParams & { fieldName: 'createdBy' | 'updatedBy' }): Promise< + RecordInput[] + > { const workspace = authContext.workspace; assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); @@ -46,7 +100,7 @@ export class CreatedByFromAuthContextService { ); this.logger.log( - `Injecting createdBy from auth context for object ${objectMetadataNameSingular} and workspace ${workspace.id}`, + `Injecting ${fieldName} from auth context for object ${objectMetadataNameSingular} and workspace ${workspace.id}`, ); const { idByNameSingular } = buildObjectIdByNameMaps( @@ -68,9 +122,9 @@ export class CreatedByFromAuthContextService { `Object metadata found with fields: ${Object.keys(fieldIdByName)}`, ); - if (!isDefined(fieldIdByName['createdBy'])) { + if (!isDefined(fieldIdByName[fieldName])) { this.logger.log( - `CreatedBy field not found in object metadata, skipping injection`, + `${fieldName} field not found in object metadata, skipping injection`, ); return records; @@ -78,33 +132,35 @@ export class CreatedByFromAuthContextService { const clonedRecords = structuredClone(records); - const createdBy = await this.buildCreatedBy(authContext); + const actorMetadata = await this.buildActorMetadata(authContext); - if (Array.isArray(clonedRecords)) { - for (const datum of clonedRecords) { - this.injectCreatedByToRecord(createdBy, datum); - } - } else { - this.injectCreatedByToRecord(createdBy, clonedRecords); + for (const record of clonedRecords) { + this.injectActorToRecord(actorMetadata, record, fieldName); } return clonedRecords; } - private injectCreatedByToRecord( - createdBy: ActorMetadata, - record: CreateInput, + private injectActorToRecord( + actorMetadata: ActorMetadata, + record: RecordInput, + fieldName: 'createdBy' | 'updatedBy', ) { - // Front-end can fill the source field - if (createdBy && (!record.createdBy || !record.createdBy?.name)) { - record.createdBy = { - ...createdBy, - source: record.createdBy?.source ?? createdBy.source, - }; + const existingValue = record[fieldName] as ActorMetadata | undefined; + + if (fieldName === 'createdBy') { + if (actorMetadata && (!existingValue || !existingValue.name)) { + record[fieldName] = { + ...actorMetadata, + source: existingValue?.source ?? actorMetadata.source, + }; + } + } else { + record[fieldName] = actorMetadata; } } - private async buildCreatedBy( + private async buildActorMetadata( authContext: AuthContext, ): Promise { const { workspace, user, apiKey, application } = authContext; @@ -151,7 +207,7 @@ export class CreatedByFromAuthContextService { } throw new Error( - 'Unable to build createdBy metadata - no valid actor information found in auth context', + 'Unable to build actor metadata - no valid actor information found in auth context', ); } } diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts index 3081de8d9d5..8da0e9058e1 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts @@ -124,14 +124,17 @@ export class CreateRecordService { flatFieldMetadataMaps, ); + const actorMetadata = params.createdBy ?? { + source: FieldActorSource.WORKFLOW, + name: 'Workflow', + }; + const insertResult = await repository.insert( { ...transformedObjectRecord, position, - createdBy: params.createdBy ?? { - source: FieldActorSource.WORKFLOW, - name: 'Workflow', - }, + createdBy: actorMetadata, + updatedBy: actorMetadata, }, undefined, selectedColumns, diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/update-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/update-record.service.ts index dd9506ad8f3..a751082fa1b 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/update-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/update-record.service.ts @@ -1,6 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import deepEqual from 'deep-equal'; +import { FieldActorSource } from 'twenty-shared/types'; import { isDefined, isValidUuid } from 'twenty-shared/utils'; import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; @@ -34,6 +35,7 @@ export class UpdateRecordService { fieldsToUpdate, workspaceId, rolePermissionConfig, + updatedBy, } = params; if (!workspaceId) { @@ -161,6 +163,12 @@ export class UpdateRecordService { objectRecordId, { ...transformedObjectRecord, + updatedBy: updatedBy ?? { + source: FieldActorSource.WORKFLOW, + name: 'Workflow', + workspaceMemberId: null, + context: {}, + }, }, undefined, selectedColumns, diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-execution-context.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-execution-context.type.ts new file mode 100644 index 00000000000..9dc2b6a216f --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-execution-context.type.ts @@ -0,0 +1,7 @@ +import { type ActorMetadata } from 'twenty-shared/types'; + +import { type RecordCrudExecutionContext } from './record-crud-execution-context.type'; + +export type CreateRecordExecutionContext = RecordCrudExecutionContext & { + createdBy?: ActorMetadata; +}; diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-params.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-params.type.ts index 65569d735f6..61284a3aba1 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-params.type.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-params.type.ts @@ -1,7 +1,5 @@ -import { - type CreateRecordExecutionContext, - type RecordCrudExecutionContext, -} from './execution-context.type'; +import { type CreateRecordExecutionContext } from './create-record-execution-context.type'; +import { type RecordCrudExecutionContext } from './record-crud-execution-context.type'; import { type CreateRecordInput } from './record-crud-input.type'; export type CreateRecordParams = CreateRecordInput & diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/delete-record-params.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/delete-record-params.type.ts index 9c5fc793546..3843c41f41d 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/types/delete-record-params.type.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/delete-record-params.type.ts @@ -1,4 +1,4 @@ -import { type RecordCrudExecutionContext } from './execution-context.type'; +import { type RecordCrudExecutionContext } from './record-crud-execution-context.type'; import { type DeleteRecordInput } from './record-crud-input.type'; export type DeleteRecordParams = DeleteRecordInput & diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/find-records-params.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/find-records-params.type.ts index a314dee03e5..ff27d71ac43 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/types/find-records-params.type.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/find-records-params.type.ts @@ -3,7 +3,7 @@ import { type ObjectRecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; -import { type RecordCrudExecutionContext } from './execution-context.type'; +import { type RecordCrudExecutionContext } from './record-crud-execution-context.type'; import { type FindRecordsInput } from './record-crud-input.type'; export type FindRecordsParams = FindRecordsInput & diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/execution-context.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/record-crud-execution-context.type.ts similarity index 56% rename from packages/twenty-server/src/engine/core-modules/record-crud/types/execution-context.type.ts rename to packages/twenty-server/src/engine/core-modules/record-crud/types/record-crud-execution-context.type.ts index 90207c5d5ae..ace31e4a860 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/types/execution-context.type.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/record-crud-execution-context.type.ts @@ -1,12 +1,6 @@ -import { type ActorMetadata } from 'twenty-shared/types'; - import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; export type RecordCrudExecutionContext = { workspaceId: string; rolePermissionConfig?: RolePermissionConfig; }; - -export type CreateRecordExecutionContext = RecordCrudExecutionContext & { - createdBy?: ActorMetadata; -}; diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/update-record-execution-context.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/update-record-execution-context.type.ts new file mode 100644 index 00000000000..fb2bed98828 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/update-record-execution-context.type.ts @@ -0,0 +1,7 @@ +import { type ActorMetadata } from 'twenty-shared/types'; + +import { type RecordCrudExecutionContext } from './record-crud-execution-context.type'; + +export type UpdateRecordExecutionContext = RecordCrudExecutionContext & { + updatedBy?: ActorMetadata; +}; diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/update-record-params.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/update-record-params.type.ts index 19e3f723c86..e55c87fe0ca 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/types/update-record-params.type.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/update-record-params.type.ts @@ -1,4 +1,5 @@ -import { type RecordCrudExecutionContext } from './execution-context.type'; import { type UpdateRecordInput } from './record-crud-input.type'; +import { type UpdateRecordExecutionContext } from './update-record-execution-context.type'; -export type UpdateRecordParams = UpdateRecordInput & RecordCrudExecutionContext; +export type UpdateRecordParams = UpdateRecordInput & + UpdateRecordExecutionContext; diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/upsert-record-params.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/upsert-record-params.type.ts index e4a676e480d..ae338408e73 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/types/upsert-record-params.type.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/upsert-record-params.type.ts @@ -1,4 +1,4 @@ -import { type RecordCrudExecutionContext } from './execution-context.type'; +import { type RecordCrudExecutionContext } from './record-crud-execution-context.type'; import { type UpsertRecordInput } from './record-crud-input.type'; export type UpsertRecordParams = UpsertRecordInput & RecordCrudExecutionContext; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts index ea933ff82b2..f41a0576f18 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-flat-field-metadatas-for-custom-object.util.ts @@ -244,6 +244,42 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ applicationId: applicationId ?? null, }; + const updatedByFieldId = v4(); + const updatedByField: FlatFieldMetadata = { + type: FieldMetadataType.ACTOR, + id: updatedByFieldId, + viewFieldIds: [], + mainGroupByFieldMetadataViewIds: [], + kanbanAggregateOperationViewIds: [], + calendarViewIds: [], + isLabelSyncedWithName: false, + isUnique: false, + objectMetadataId, + universalIdentifier: updatedByFieldId, + workspaceId, + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.updatedBy, + name: 'updatedBy', + label: 'Updated by', + icon: 'IconUserCircle', + description: 'The workspace member who last updated the record', + isNullable: false, + isActive: true, + isCustom: false, + isSystem: false, + isUIReadOnly: true, + defaultValue: { name: "''", source: "'MANUAL'" }, + viewFilterIds: [], + createdAt, + updatedAt: createdAt, + options: null, + standardOverrides: null, + relationTargetFieldMetadataId: null, + relationTargetObjectMetadataId: null, + settings: null, + morphId: null, + applicationId: applicationId ?? null, + }; + const positionFieldId = v4(); const positionField: FlatFieldMetadata = { type: FieldMetadataType.POSITION, @@ -327,6 +363,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({ nameField, createdAtField, updatedAtField, + updatedByField, deletedAtField, createdByField, positionField, diff --git a/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts b/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts index 1a6737506ea..1754383d9e8 100644 --- a/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts +++ b/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts @@ -69,6 +69,16 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + @WorkspaceRelation({ standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.noteTargets, label: msg`Notes`, diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts index 210050b24c8..390118e9960 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/format-result.util.ts @@ -2,6 +2,7 @@ import { isPlainObject } from '@nestjs/common/utils/shared.utils'; import { isNull } from '@sniptt/guards'; import { + FieldActorSource, FieldMetadataType, compositeTypeDefinitions, } from 'twenty-shared/types'; @@ -157,6 +158,13 @@ export function formatResult( : value; } + // After assembling composite fields, handle those with missing required subfields + handleEmptyCompositeFields( + newData, + flatObjectMetadata, + flatFieldMetadataMaps, + ); + const fieldMetadataItemsOfTypeDateOnly = getFlatFieldsFromFlatObjectMetadata( flatObjectMetadata, flatFieldMetadataMaps, @@ -250,3 +258,85 @@ function transformCompositeFieldNullValue( ] ?? value ); } + +/** + * Handles composite fields with missing required subfields. + * - For nullable fields: sets to null if all required subfields are null + * - For non-nullable fields: provides a default value to prevent GraphQL errors + * + * This handles existing records that were created before the field was added + * or records with incomplete data. + */ +function handleEmptyCompositeFields( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: Record, + flatObjectMetadata: FlatObjectMetadata, + flatFieldMetadataMaps: FlatEntityMaps, +) { + const compositeFieldMetadataCollection = getCompositeFieldMetadataCollection( + flatObjectMetadata, + flatFieldMetadataMaps, + ); + + for (const fieldMetadata of compositeFieldMetadataCollection) { + const fieldValue = data[fieldMetadata.name]; + + if (!isDefined(fieldValue) || !isPlainObject(fieldValue)) { + continue; + } + + const compositeType = compositeTypeDefinitions.get(fieldMetadata.type); + + if (!compositeType) { + continue; + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const typedFieldValue = fieldValue as Record; + + // Check if all required properties are null/undefined + const requiredProperties = compositeType.properties.filter( + (prop) => prop.isRequired, + ); + + const allRequiredPropertiesAreNull = requiredProperties.every( + (prop) => + !isDefined(typedFieldValue[prop.name]) || + isNull(typedFieldValue[prop.name]), + ); + + if (allRequiredPropertiesAreNull && requiredProperties.length > 0) { + if (fieldMetadata.isNullable) { + // Field is nullable, set to null + data[fieldMetadata.name] = null; + } else { + // Field is non-nullable, provide a default value + data[fieldMetadata.name] = getDefaultCompositeFieldValue( + fieldMetadata.type, + ); + } + } + } +} + +/** + * Returns a default value for non-nullable composite fields. + */ +function getDefaultCompositeFieldValue( + fieldType: FieldMetadataType, + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): Record | null { + switch (fieldType) { + case FieldMetadataType.ACTOR: + return { + source: FieldActorSource.MANUAL, + name: '', + workspaceMemberId: null, + context: {}, + }; + default: + // For other composite types, return null and let GraphQL handle the error + // This should be extended as needed for other non-nullable composite fields + return null; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant.ts index f64625a37d8..7cad4d59456 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant.ts @@ -15,6 +15,9 @@ type AttachmentDataSeed = { createdBySource: string; createdByWorkspaceMemberId: string; createdByName: string; + updatedBySource: string; + updatedByWorkspaceMemberId: string; + updatedByName: string; personId: string | null; companyId: string | null; noteId: string | null; @@ -30,6 +33,9 @@ export const ATTACHMENT_DATA_SEED_COLUMNS: (keyof AttachmentDataSeed)[] = [ 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', 'personId', 'companyId', 'noteId', @@ -251,6 +257,9 @@ const GENERATE_ATTACHMENT_SEEDS = (): AttachmentDataSeed[] => { createdBySource: FieldActorSource.MANUAL, createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdByName: 'Tim A', + updatedBySource: FieldActorSource.MANUAL, + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, + updatedByName: 'Tim A', personId, companyId, noteId, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant.ts index d83a3bb355a..e5de29fb947 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant.ts @@ -11,6 +11,9 @@ type CompanyDataSeed = { createdByWorkspaceMemberId: string; createdByName: string; accountOwnerId: string; + updatedBySource: string; + updatedByWorkspaceMemberId: string; + updatedByName: string; position: number; }; @@ -25,6 +28,9 @@ export const COMPANY_DATA_SEED_COLUMNS: (keyof CompanyDataSeed)[] = [ 'createdByWorkspaceMemberId', 'createdByName', 'accountOwnerId', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', 'position', ]; @@ -8524,6 +8530,9 @@ const COMPANY_DATA_SEEDS_RAW = [ export const COMPANY_DATA_SEEDS: CompanyDataSeed[] = COMPANY_DATA_SEEDS_RAW.map( (company, index) => ({ ...company, + updatedBySource: company.createdBySource, + updatedByWorkspaceMemberId: company.createdByWorkspaceMemberId, + updatedByName: company.createdByName, position: index + 1, }), ); diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/dashboard-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/dashboard-data-seeds.constant.ts index ca945f44963..1dec7c283a1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/dashboard-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/dashboard-data-seeds.constant.ts @@ -9,6 +9,9 @@ type DashboardDataSeed = { createdBySource: string; createdByWorkspaceMemberId: string; createdByName: string; + updatedBySource: string; + updatedByWorkspaceMemberId: string; + updatedByName: string; position: number; }; @@ -19,6 +22,9 @@ export const DASHBOARD_DATA_SEED_COLUMNS: (keyof DashboardDataSeed)[] = [ 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', 'position', ]; @@ -41,6 +47,9 @@ export const getDashboardDataSeeds = ( createdBySource: 'MANUAL', createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdByName: 'Tim Apple', + updatedBySource: 'MANUAL', + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, + updatedByName: 'Tim Apple', position: 0, }, { @@ -53,6 +62,9 @@ export const getDashboardDataSeeds = ( createdBySource: 'MANUAL', createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.JONY, createdByName: 'Jony Ive', + updatedBySource: 'MANUAL', + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.JONY, + updatedByName: 'Jony Ive', position: 1, }, { @@ -62,6 +74,9 @@ export const getDashboardDataSeeds = ( createdBySource: 'MANUAL', createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.PHIL, createdByName: 'Phil Schiller', + updatedBySource: 'MANUAL', + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.PHIL, + updatedByName: 'Phil Schiller', position: 2, }, ]; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/note-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/note-data-seeds.constant.ts index 7cb37918917..b8cf04df402 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/note-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/note-data-seeds.constant.ts @@ -10,6 +10,9 @@ type NoteDataSeed = { createdByWorkspaceMemberId: string; createdByName: string; createdByContext: string | null; + updatedBySource: string; + updatedByWorkspaceMemberId: string; + updatedByName: string; }; export const NOTE_DATA_SEED_COLUMNS: (keyof NoteDataSeed)[] = [ @@ -22,6 +25,9 @@ export const NOTE_DATA_SEED_COLUMNS: (keyof NoteDataSeed)[] = [ 'createdByWorkspaceMemberId', 'createdByName', 'createdByContext', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', ]; const GENERATE_NOTE_IDS = (): Record => { @@ -161,6 +167,9 @@ const GENERATE_NOTE_SEEDS = (): NoteDataSeed[] => { createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdByName: 'Tim A', createdByContext: null, + updatedBySource: 'MANUAL', + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, + updatedByName: 'Tim A', }); } @@ -191,6 +200,9 @@ const GENERATE_NOTE_SEEDS = (): NoteDataSeed[] => { createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdByName: 'Tim A', createdByContext: null, + updatedBySource: 'MANUAL', + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, + updatedByName: 'Tim A', }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant.ts index 70138ccb0a7..85aa8fe64d3 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/opportunity-data-seeds.constant.ts @@ -15,6 +15,9 @@ type OpportunityDataSeed = { createdBySource: string; createdByWorkspaceMemberId: string; createdByName: string; + updatedBySource: string; + updatedByWorkspaceMemberId: string; + updatedByName: string; }; export const OPPORTUNITY_DATA_SEED_COLUMNS: (keyof OpportunityDataSeed)[] = [ @@ -30,6 +33,9 @@ export const OPPORTUNITY_DATA_SEED_COLUMNS: (keyof OpportunityDataSeed)[] = [ 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', ]; const GENERATE_OPPORTUNITY_IDS = (): Record => { @@ -188,6 +194,9 @@ const GENERATE_OPPORTUNITY_SEEDS = (): OpportunityDataSeed[] => { createdBySource: 'MANUAL', createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdByName: 'Tim Cook', + updatedBySource: 'MANUAL', + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, + updatedByName: 'Tim Cook', }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/person-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/person-data-seeds.constant.ts index a2109736bb4..4aa10a95df8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/person-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/person-data-seeds.constant.ts @@ -17,6 +17,9 @@ type PersonDataSeed = { phonesPrimaryPhoneNumber: string; phonesPrimaryPhoneCountryCode: string; phonesPrimaryPhoneCallingCode: string; + updatedBySource: string; + updatedByWorkspaceMemberId: string; + updatedByName: string; position: number; }; @@ -36,6 +39,9 @@ export const PERSON_DATA_SEED_COLUMNS: (keyof PersonDataSeed)[] = [ 'phonesPrimaryPhoneNumber', 'phonesPrimaryPhoneCountryCode', 'phonesPrimaryPhoneCallingCode', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', 'position', ]; @@ -21650,6 +21656,9 @@ const PERSON_DATA_SEEDS_RAW = [ export const PERSON_DATA_SEEDS: PersonDataSeed[] = PERSON_DATA_SEEDS_RAW.map( (person, index) => ({ ...person, + updatedBySource: person.createdBySource, + updatedByWorkspaceMemberId: person.createdByWorkspaceMemberId, + updatedByName: person.createdByName, position: index + 1, }), ); diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/task-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/task-data-seeds.constant.ts index 38310973c88..c5fc31f3298 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/task-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/task-data-seeds.constant.ts @@ -12,6 +12,9 @@ type TaskDataSeed = { createdBySource: string; createdByWorkspaceMemberId: string; createdByName: string; + updatedBySource: string; + updatedByWorkspaceMemberId: string; + updatedByName: string; }; export const TASK_DATA_SEED_COLUMNS: (keyof TaskDataSeed)[] = [ @@ -26,6 +29,9 @@ export const TASK_DATA_SEED_COLUMNS: (keyof TaskDataSeed)[] = [ 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', ]; // Generate all task IDs @@ -210,6 +216,9 @@ const GENERATE_TASK_SEEDS = (): TaskDataSeed[] => { createdBySource: 'MANUAL', createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdByName: 'Tim A', + updatedBySource: 'MANUAL', + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, + updatedByName: 'Tim A', }); } @@ -242,6 +251,9 @@ const GENERATE_TASK_SEEDS = (): TaskDataSeed[] => { createdBySource: 'MANUAL', createdByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, createdByName: 'Tim A', + updatedBySource: 'MANUAL', + updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, + updatedByName: 'Tim A', }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies.ts index a7999fd49d9..f60be43e8a6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-companies.ts @@ -29,6 +29,9 @@ export const prefillCompanies = async ( 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', ]) .orIgnore() .values([ @@ -47,6 +50,9 @@ export const prefillCompanies = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', }, { id: ANTHROPIC_ID, @@ -63,6 +69,9 @@ export const prefillCompanies = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', }, { id: STRIPE_ID, @@ -79,6 +88,9 @@ export const prefillCompanies = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', }, { id: FIGMA_ID, @@ -95,6 +107,9 @@ export const prefillCompanies = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', }, { id: NOTION_ID, @@ -111,6 +126,9 @@ export const prefillCompanies = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', }, ]) .returning('*') diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts index 720ee835863..55ed1b52f15 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-people.ts @@ -26,6 +26,9 @@ export const prefillPeople = async ( 'createdBySource', 'createdByWorkspaceMemberId', 'createdByName', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', 'phonesPrimaryPhoneNumber', 'phonesPrimaryPhoneCallingCode', 'companyId', @@ -43,6 +46,9 @@ export const prefillPeople = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', phonesPrimaryPhoneNumber: '123456789', phonesPrimaryPhoneCallingCode: '+1', companyId: AIRBNB_ID, @@ -58,6 +64,9 @@ export const prefillPeople = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', phonesPrimaryPhoneNumber: '555123456', phonesPrimaryPhoneCallingCode: '+1', companyId: ANTHROPIC_ID, @@ -73,6 +82,9 @@ export const prefillPeople = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', phonesPrimaryPhoneNumber: '987625341', phonesPrimaryPhoneCallingCode: '+1', companyId: STRIPE_ID, @@ -88,6 +100,9 @@ export const prefillPeople = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', phonesPrimaryPhoneNumber: '098822619', phonesPrimaryPhoneCallingCode: '+1', companyId: FIGMA_ID, @@ -103,6 +118,9 @@ export const prefillPeople = async ( createdBySource: FieldActorSource.SYSTEM, createdByWorkspaceMemberId: null, createdByName: 'System', + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', phonesPrimaryPhoneNumber: '882261739', phonesPrimaryPhoneCallingCode: '+1', companyId: NOTION_ID, diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows.ts index e480182826a..4645da933f5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/prefill-workflows.ts @@ -54,6 +54,9 @@ export const prefillWorkflows = async ( 'createdByWorkspaceMemberId', 'createdByName', 'createdByContext', + 'updatedBySource', + 'updatedByWorkspaceMemberId', + 'updatedByName', ]) .orIgnore() .values([ @@ -67,6 +70,9 @@ export const prefillWorkflows = async ( createdByWorkspaceMemberId: null, createdByName: 'System', createdByContext: {}, + updatedBySource: FieldActorSource.SYSTEM, + updatedByWorkspaceMemberId: null, + updatedByName: 'System', }, ]) .returning('*') diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts index f6d879830ef..65d165c075b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts @@ -54,6 +54,9 @@ export const STANDARD_OBJECTS = { createdBy: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.createdBy, }, + updatedBy: { + universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.updatedBy, + }, task: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.task }, note: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.note }, person: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.person }, @@ -397,6 +400,7 @@ export const STANDARD_OBJECTS = { }, position: { universalIdentifier: COMPANY_STANDARD_FIELD_IDS.position }, createdBy: { universalIdentifier: COMPANY_STANDARD_FIELD_IDS.createdBy }, + updatedBy: { universalIdentifier: COMPANY_STANDARD_FIELD_IDS.updatedBy }, people: { universalIdentifier: COMPANY_STANDARD_FIELD_IDS.people }, accountOwner: { universalIdentifier: COMPANY_STANDARD_FIELD_IDS.accountOwner, @@ -549,6 +553,9 @@ export const STANDARD_OBJECTS = { createdBy: { universalIdentifier: DASHBOARD_STANDARD_FIELD_IDS.createdBy, }, + updatedBy: { + universalIdentifier: DASHBOARD_STANDARD_FIELD_IDS.updatedBy, + }, timelineActivities: { universalIdentifier: DASHBOARD_STANDARD_FIELD_IDS.timelineActivities, }, @@ -1029,6 +1036,7 @@ export const STANDARD_OBJECTS = { title: { universalIdentifier: NOTE_STANDARD_FIELD_IDS.title }, bodyV2: { universalIdentifier: NOTE_STANDARD_FIELD_IDS.bodyV2 }, createdBy: { universalIdentifier: NOTE_STANDARD_FIELD_IDS.createdBy }, + updatedBy: { universalIdentifier: NOTE_STANDARD_FIELD_IDS.updatedBy }, noteTargets: { universalIdentifier: NOTE_STANDARD_FIELD_IDS.noteTargets }, attachments: { universalIdentifier: NOTE_STANDARD_FIELD_IDS.attachments }, timelineActivities: { @@ -1127,6 +1135,9 @@ export const STANDARD_OBJECTS = { createdBy: { universalIdentifier: OPPORTUNITY_STANDARD_FIELD_IDS.createdBy, }, + updatedBy: { + universalIdentifier: OPPORTUNITY_STANDARD_FIELD_IDS.updatedBy, + }, pointOfContact: { universalIdentifier: OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact, }, @@ -1255,6 +1266,7 @@ export const STANDARD_OBJECTS = { avatarUrl: { universalIdentifier: PERSON_STANDARD_FIELD_IDS.avatarUrl }, position: { universalIdentifier: PERSON_STANDARD_FIELD_IDS.position }, createdBy: { universalIdentifier: PERSON_STANDARD_FIELD_IDS.createdBy }, + updatedBy: { universalIdentifier: PERSON_STANDARD_FIELD_IDS.updatedBy }, company: { universalIdentifier: PERSON_STANDARD_FIELD_IDS.company }, pointOfContactForOpportunities: { universalIdentifier: @@ -1352,6 +1364,7 @@ export const STANDARD_OBJECTS = { dueAt: { universalIdentifier: TASK_STANDARD_FIELD_IDS.dueAt }, status: { universalIdentifier: TASK_STANDARD_FIELD_IDS.status }, createdBy: { universalIdentifier: TASK_STANDARD_FIELD_IDS.createdBy }, + updatedBy: { universalIdentifier: TASK_STANDARD_FIELD_IDS.updatedBy }, taskTargets: { universalIdentifier: TASK_STANDARD_FIELD_IDS.taskTargets }, attachments: { universalIdentifier: TASK_STANDARD_FIELD_IDS.attachments }, assignee: { universalIdentifier: TASK_STANDARD_FIELD_IDS.assignee }, @@ -1670,6 +1683,7 @@ export const STANDARD_OBJECTS = { universalIdentifier: WORKFLOW_STANDARD_FIELD_IDS.attachments, }, createdBy: { universalIdentifier: WORKFLOW_STANDARD_FIELD_IDS.createdBy }, + updatedBy: { universalIdentifier: WORKFLOW_STANDARD_FIELD_IDS.updatedBy }, searchVector: { universalIdentifier: WORKFLOW_STANDARD_FIELD_IDS.searchVector, }, @@ -1772,6 +1786,9 @@ export const STANDARD_OBJECTS = { createdBy: { universalIdentifier: WORKFLOW_RUN_STANDARD_FIELD_IDS.createdBy, }, + updatedBy: { + universalIdentifier: WORKFLOW_RUN_STANDARD_FIELD_IDS.updatedBy, + }, output: { universalIdentifier: WORKFLOW_RUN_STANDARD_FIELD_IDS.output }, context: { universalIdentifier: WORKFLOW_RUN_STANDARD_FIELD_IDS.context }, state: { universalIdentifier: WORKFLOW_RUN_STANDARD_FIELD_IDS.state }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts index 8ae5c968e3d..7175aec8d5c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts @@ -207,6 +207,28 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), // Relation fields task: createStandardRelationFieldFlatMetadata({ diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-company-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-company-standard-flat-field-metadata.util.ts index 139f6182cd6..058f295001d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-company-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-company-standard-flat-field-metadata.util.ts @@ -286,6 +286,28 @@ export const buildCompanyStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), searchVector: createStandardFieldFlatMetadata({ objectName, workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-dashboard-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-dashboard-standard-flat-field-metadata.util.ts index 4dc7a2bebfb..549684464e6 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-dashboard-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-dashboard-standard-flat-field-metadata.util.ts @@ -176,6 +176,28 @@ export const buildDashboardStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), searchVector: createStandardFieldFlatMetadata({ objectName, workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-note-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-note-standard-flat-field-metadata.util.ts index de35a36163f..6dab1d66b5e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-note-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-note-standard-flat-field-metadata.util.ts @@ -181,6 +181,28 @@ export const buildNoteStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), searchVector: createStandardFieldFlatMetadata({ objectName, workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts index 67bf781789a..d5adb4643af 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts @@ -232,6 +232,28 @@ export const buildOpportunityStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), searchVector: createStandardFieldFlatMetadata({ objectName, workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts index d583566d83e..a78ecc0763e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts @@ -286,6 +286,28 @@ export const buildPersonStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), // Relation fields company: createStandardRelationFieldFlatMetadata({ diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-task-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-task-standard-flat-field-metadata.util.ts index 6e9f27d8bb8..964fcaf5350 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-task-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-task-standard-flat-field-metadata.util.ts @@ -225,6 +225,28 @@ export const buildTaskStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), searchVector: createStandardFieldFlatMetadata({ objectName, workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts index ae624e2e8b6..8478fecf376 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-run-standard-flat-field-metadata.util.ts @@ -223,6 +223,28 @@ export const buildWorkflowRunStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), state: createStandardFieldFlatMetadata({ objectName, workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-standard-flat-field-metadata.util.ts index 591203b1413..3968a0e8df2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-standard-flat-field-metadata.util.ts @@ -200,6 +200,28 @@ export const buildWorkflowStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), + updatedBy: createStandardFieldFlatMetadata({ + objectName, + workspaceId, + context: { + fieldName: 'updatedBy', + type: FieldMetadataType.ACTOR, + label: 'Updated by', + description: 'The workspace member who last updated the record', + icon: 'IconUserCircle', + isUIReadOnly: true, + isNullable: false, + defaultValue: { + source: "'MANUAL'", + name: "'System'", + workspaceMemberId: null, + }, + }, + standardObjectMetadataRelatedEntityIds, + dependencyFlatEntityMaps, + twentyStandardApplicationId, + now, + }), searchVector: createStandardFieldFlatMetadata({ objectName, workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index f8984753c83..3c4c2fc41ad 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -35,6 +35,7 @@ export const ATTACHMENT_STANDARD_FIELD_IDS = { type: '20202020-a417-49b8-a40b-f6a7874caa0d', fileCategory: '20202020-8c3f-4d9e-9a1b-2e5f7a8c9d0e', createdBy: '395be3bd-a5c9-463d-aafe-9bc3bbec3f15', + updatedBy: '376239d1-3e65-4cb6-b5d8-e0917d43cc93', author: '20202020-6501-4ac5-a4ef-b2f8522ef6cd', activity: '20202020-b569-481b-a13f-9b94e47e54fe', task: '20202020-51e5-4621-9cf8-215487951c4b', @@ -128,6 +129,7 @@ export const COMPANY_STANDARD_FIELD_IDS = { idealCustomerProfile: '20202020-ba6b-438a-8213-2c5ba28d76a2', position: '20202020-9b4e-462b-991d-a0ee33326454', createdBy: '20202020-fabc-451d-ab7d-412170916baa', + updatedBy: '7444022e-b38f-4d4f-801b-cd664abc4834', people: '20202020-3213-4ddf-9494-6422bcff8d7c', accountOwner: '20202020-95b8-4e10-9881-edb5d4765f9d', // TODO: check if activityTargets field can be deleted @@ -280,6 +282,7 @@ export const NOTE_STANDARD_FIELD_IDS = { body: '20202020-e63d-4e70-95be-a78cd9abe7ef', bodyV2: '20202020-a7bb-4d94-be51-8f25181502c8', createdBy: '20202020-0d79-4e21-ab77-5a394eff97be', + updatedBy: '9b446e89-2484-4044-a3b5-420f6b578c0c', noteTargets: '20202020-1f25-43fe-8b00-af212fdde823', attachments: '20202020-4986-4c92-bf19-39934b149b16', timelineActivities: '20202020-7030-42f8-929c-1a57b25d6bce', @@ -303,6 +306,7 @@ export const OPPORTUNITY_STANDARD_FIELD_IDS = { stage: '20202020-6f76-477d-8551-28cd65b2b4b9', position: '20202020-806d-493a-bbc6-6313e62958e2', createdBy: '20202020-a63e-4a62-8e63-42a51828f831', + updatedBy: '3c8a6095-3f64-4e81-a59e-66c2bd181e11', pointOfContact: '20202020-8dfb-42fc-92b6-01afb759ed16', company: '20202020-cbac-457e-b565-adece5fc815f', favorites: '20202020-a1c2-4500-aaae-83ba8a0e827a', @@ -328,6 +332,7 @@ export const PERSON_STANDARD_FIELD_IDS = { avatarUrl: '20202020-b8a6-40df-961c-373dc5d2ec21', position: '20202020-fcd5-4231-aff5-fff583eaa0b1', createdBy: '20202020-f6ab-4d98-af24-a3d5b664148a', + updatedBy: 'e9e0dd35-184c-4742-84da-afadf45ce59a', company: '20202020-e2f3-448e-b34c-2d625f0025fd', pointOfContactForOpportunities: '20202020-911b-4a7d-b67b-918aa9a5b33a', // TODO: check if activityTargets field can be deleted @@ -350,6 +355,7 @@ export const TASK_STANDARD_FIELD_IDS = { dueAt: '20202020-fd99-40da-951b-4cb9a352fce3', status: '20202020-70bc-48f9-89c5-6aa730b151e0', createdBy: '20202020-1a04-48ab-a567-576965ae5387', + updatedBy: '9e8bf518-f4ab-433e-9674-efb75fba2802', taskTargets: '20202020-de9c-4d0e-a452-713d4a3e5fc7', attachments: '20202020-794d-4783-a8ff-cecdb15be139', assignee: '20202020-065a-4f42-a906-e20422c1753f', @@ -449,6 +455,7 @@ export const WORKFLOW_STANDARD_FIELD_IDS = { timelineActivities: '20202020-906e-486a-a798-131a5f081faf', attachments: '20202020-4a8c-4e2d-9b1c-7e5f3a2b4c6d', createdBy: '20202020-6007-401a-8aa5-e6f48581a6f3', + updatedBy: '3559831e-caf2-4eb5-9db1-b47bf968c774', searchVector: '20202020-535d-4ffa-b7f3-4fa0d5da1b7a', } as const; @@ -462,6 +469,7 @@ export const WORKFLOW_RUN_STANDARD_FIELD_IDS = { status: '20202020-6b3e-4f9c-8c2b-2e5b8e6d6f3b', position: '20202020-7802-4c40-ae89-1f506fe3365c', createdBy: '20202020-6007-401a-8aa5-e6f38581a6f3', + updatedBy: '730dc1c9-34f5-4c22-84a6-bcb55b7604e2', output: '20202020-7be4-4db2-8ac6-3ff0d740843d', context: '20202020-189c-478a-b867-d72feaf5926a', state: '20202020-611f-45f3-9cde-d64927e8ec57', @@ -516,6 +524,7 @@ export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = { name: '20202020-ba07-4ffd-ba63-009491f5749c', position: '20202020-c2bd-4e16-bb9a-c8b0411bf49d', createdBy: '20202020-be0e-4971-865b-32ca87cbb315', + updatedBy: 'd047ccb4-9469-4514-a982-29c4ae49317d', // TODO: check if activityTargets field can be deleted activityTargets: '20202020-7f42-40ae-b96c-c8a61acc83bf', noteTargets: '20202020-01fd-4f37-99dc-9427a444018a', @@ -531,6 +540,7 @@ export const DASHBOARD_STANDARD_FIELD_IDS = { position: '20202020-38af-409b-95f0-7f08aa5f420f', pageLayoutId: '20202020-bb53-4648-aa36-1d9d54e6f7f2', createdBy: '20202020-ff32-4fa1-b7ad-407cc6aa0734', + updatedBy: '53ee42e7-f157-42b5-b278-a5fa9b378307', timelineActivities: '20202020-9b0c-5d6e-7f8a-9b0c1d2e3f4a', favorites: '20202020-f032-478f-88fa-6426ff6f1e4c', attachments: '20202020-bf6f-4220-8c55-2764f1175870', diff --git a/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts b/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts index cfb6a4384f1..719894e6d66 100644 --- a/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts +++ b/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts @@ -148,6 +148,16 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: ATTACHMENT_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + @WorkspaceRelation({ standardId: ATTACHMENT_STANDARD_FIELD_IDS.author, type: RelationType.MANY_TO_ONE, diff --git a/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts b/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts index e90e4882c8e..8f6b8ba6ef9 100644 --- a/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts +++ b/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts @@ -168,6 +168,16 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: COMPANY_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + // Relations @WorkspaceRelation({ standardId: COMPANY_STANDARD_FIELD_IDS.people, diff --git a/packages/twenty-server/src/modules/dashboard/standard-objects/dashboard.workspace-entity.ts b/packages/twenty-server/src/modules/dashboard/standard-objects/dashboard.workspace-entity.ts index ce125e6e1b8..28941a6d1ed 100644 --- a/packages/twenty-server/src/modules/dashboard/standard-objects/dashboard.workspace-entity.ts +++ b/packages/twenty-server/src/modules/dashboard/standard-objects/dashboard.workspace-entity.ts @@ -91,6 +91,16 @@ export class DashboardWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: DASHBOARD_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + @WorkspaceRelation({ standardId: DASHBOARD_STANDARD_FIELD_IDS.timelineActivities, type: RelationType.ONE_TO_MANY, diff --git a/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts b/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts index f7ce539ce65..f60b242438e 100644 --- a/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts +++ b/packages/twenty-server/src/modules/note/standard-objects/note.workspace-entity.ts @@ -94,6 +94,16 @@ export class NoteWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: NOTE_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + @WorkspaceRelation({ standardId: NOTE_STANDARD_FIELD_IDS.noteTargets, label: msg`Relations`, diff --git a/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts b/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts index ed8a3741e2a..c9669045b82 100644 --- a/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts +++ b/packages/twenty-server/src/modules/opportunity/standard-objects/opportunity.workspace-entity.ts @@ -130,6 +130,16 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: OPPORTUNITY_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + @WorkspaceRelation({ standardId: OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact, type: RelationType.MANY_TO_ONE, diff --git a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts index 582aa946d32..78830b46b03 100644 --- a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts +++ b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts @@ -195,6 +195,16 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: PERSON_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + // Relations @WorkspaceRelation({ standardId: PERSON_STANDARD_FIELD_IDS.company, diff --git a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts index f94e638fa8a..f6788fdc428 100644 --- a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts +++ b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts @@ -133,6 +133,16 @@ export class TaskWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: TASK_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + @WorkspaceRelation({ standardId: TASK_STANDARD_FIELD_IDS.taskTargets, label: msg`Relations`, diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts index 2e659b88b43..56f5773d92a 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts @@ -204,6 +204,16 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + @WorkspaceField({ + standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; + @WorkspaceField({ standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.state, type: FieldMetadataType.RAW_JSON, diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts index b53df36e34e..c40767e05dc 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow.workspace-entity.ts @@ -220,4 +220,14 @@ export class WorkflowWorkspaceEntity extends BaseWorkspaceEntity { }) @WorkspaceIsFieldUIReadOnly() createdBy: ActorMetadata; + + @WorkspaceField({ + standardId: WORKFLOW_STANDARD_FIELD_IDS.updatedBy, + type: FieldMetadataType.ACTOR, + label: msg`Updated by`, + icon: 'IconUserCircle', + description: msg`The workspace member who last updated the record`, + }) + @WorkspaceIsFieldUIReadOnly() + updatedBy: ActorMetadata; } diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/utils/build-workflow-actor-metadata.util.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/utils/build-workflow-actor-metadata.util.ts new file mode 100644 index 00000000000..d6b394b8743 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/utils/build-workflow-actor-metadata.util.ts @@ -0,0 +1,18 @@ +import { type ActorMetadata, FieldActorSource } from 'twenty-shared/types'; + +import { type WorkflowExecutionContext } from 'src/modules/workflow/workflow-executor/types/workflow-execution-context.type'; + +export const buildWorkflowActorMetadata = ( + executionContext: WorkflowExecutionContext, +): ActorMetadata => { + if (executionContext.isActingOnBehalfOfUser) { + return executionContext.initiator; + } + + return { + source: FieldActorSource.WORKFLOW, + name: 'Workflow', + workspaceMemberId: null, + context: {}, + }; +}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts index 703ef7c072a..c895a6738e0 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { type ActorMetadata, FieldActorSource } from 'twenty-shared/types'; import { resolveInput } from 'twenty-shared/utils'; import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface'; @@ -14,7 +13,7 @@ import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/work import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; -import { type WorkflowExecutionContext } from 'src/modules/workflow/workflow-executor/types/workflow-execution-context.type'; +import { buildWorkflowActorMetadata } from 'src/modules/workflow/workflow-executor/utils/build-workflow-actor-metadata.util'; import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util'; import { resolveRichTextFieldsInRecord } from 'src/modules/workflow/workflow-executor/utils/resolve-rich-text-fields-in-record.util'; import { type WorkflowCreateRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type'; @@ -65,7 +64,7 @@ export class CreateRecordWorkflowAction implements WorkflowAction { const executionContext = await this.workflowExecutionContextService.getExecutionContext(runInfo); - const createdBy = this.buildCreatedByActor(executionContext); + const createdBy = buildWorkflowActorMetadata(executionContext); const toolOutput = await this.createRecordService.execute({ objectName: workflowActionInput.objectName, @@ -86,19 +85,4 @@ export class CreateRecordWorkflowAction implements WorkflowAction { result: toolOutput.result, }; } - - private buildCreatedByActor( - executionContext: WorkflowExecutionContext, - ): ActorMetadata { - if (executionContext.isActingOnBehalfOfUser) { - return executionContext.initiator; - } - - return { - source: FieldActorSource.WORKFLOW, - name: 'Workflow', - workspaceMemberId: null, - context: {}, - }; - } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts index 6f9297d93d4..ee5b31cbd7c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts @@ -17,6 +17,7 @@ import { import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; +import { buildWorkflowActorMetadata } from 'src/modules/workflow/workflow-executor/utils/build-workflow-actor-metadata.util'; import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util'; import { resolveRichTextFieldsInRecord } from 'src/modules/workflow/workflow-executor/utils/resolve-rich-text-fields-in-record.util'; import { isWorkflowUpdateRecordAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/guards/is-workflow-update-record-action.guard'; @@ -86,12 +87,15 @@ export class UpdateRecordWorkflowAction implements WorkflowAction { const executionContext = await this.workflowExecutionContextService.getExecutionContext(runInfo); + const updatedBy = buildWorkflowActorMetadata(executionContext); + const toolOutput = await this.updateRecordService.execute({ objectName: workflowActionInput.objectName, objectRecordId: workflowActionInput.objectRecordId, objectRecord: workflowActionInput.objectRecord, fieldsToUpdate: workflowActionInput.fieldsToUpdate, workspaceId, + updatedBy, rolePermissionConfig: executionContext.rolePermissionConfig, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap index 657b329f324..9df2855a9ff 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap @@ -73,6 +73,22 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -594,6 +610,27 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -699,6 +736,22 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -1220,6 +1273,27 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -1336,6 +1410,22 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -1857,6 +1947,27 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -1962,6 +2073,22 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -2483,6 +2610,27 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -2599,6 +2747,22 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] = "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -3120,6 +3284,27 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] = "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -3225,6 +3410,22 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -3746,6 +3947,27 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -3851,6 +4073,22 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -4402,6 +4640,27 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -4507,6 +4766,22 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -5028,6 +5303,27 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -5133,6 +5429,22 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -5654,6 +5966,27 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -5770,6 +6103,22 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -6297,6 +6646,27 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -6402,6 +6772,22 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -6989,6 +7375,27 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -7094,6 +7501,22 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -7669,6 +8092,27 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -7774,6 +8218,22 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -8331,6 +8791,27 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -8436,6 +8917,22 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -8981,6 +9478,27 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -9097,6 +9615,22 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -9684,6 +10218,27 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -9789,6 +10344,22 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] = "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -10310,6 +10881,27 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] = "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], @@ -10415,6 +11007,22 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res "status": "fail", "type": "create_field", }, + { + "errors": [ + { + "code": "OBJECT_METADATA_NOT_FOUND", + "message": "Object metadata not found", + "userFriendlyMessage": "Field to create related object not found", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "updatedBy", + "objectMetadataId": Any, + }, + "status": "fail", + "type": "create_field", + }, { "errors": [ { @@ -10936,6 +11544,27 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res "status": "fail", "type": "create_view_field", }, + { + "errors": [ + { + "code": "INVALID_VIEW_DATA", + "message": "Field metadata not found", + "userFriendlyMessage": "Field metadata not found", + }, + { + "code": "INVALID_VIEW_DATA", + "message": "View not found", + "userFriendlyMessage": "View not found", + }, + ], + "flatEntityMinimalInformation": { + "fieldMetadataId": Any, + "id": Any, + "viewId": Any, + }, + "status": "fail", + "type": "create_view_field", + }, ], "viewFilter": [], "viewGroup": [], diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/view-side-effect-on-object-metadata-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/view-side-effect-on-object-metadata-creation.integration-spec.ts index e43a4d5da61..102922a6007 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/view-side-effect-on-object-metadata-creation.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/view-side-effect-on-object-metadata-creation.integration-spec.ts @@ -93,6 +93,6 @@ describe('View side effect on object creation', () => { expectToFail: false, }); - expect(createdViewFields.length).toBe(11); + expect(createdViewFields.length).toBe(12); }); });