From 01e0502fcfd1f0ecfc2c87d82c5ffc3a5eca250f Mon Sep 17 00:00:00 2001
From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com>
Date: Mon, 29 Dec 2025 14:35:17 +0530
Subject: [PATCH] feat: add updatedBy field to track last record modifier
(#16807)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
> [!NOTE]
> Implements last-modifier tracking and unified actor injection.
>
> - New `ActorFromAuthContextService` replaces createdBy-only logic;
pre-query hooks now inject both `createdBy` and `updatedBy` on create
and `updatedBy` on update
> - Add `updatedBy` standard field to core/custom objects (e.g.,
`person`, `company`, `task`, `note`, `attachment`, `dashboard`,
`workflow`, `workflowRun`) with IDs, metadata builders, and ORM entity
fields
> - Record CRUD: `create` now sets both `createdBy` and `updatedBy`;
`update` sets `updatedBy`; workflow actions use a shared workflow actor
builder; REST base handler uses the new actor service
> - Seeder data and snapshots updated to include `updatedBy`; GraphQL
result formatting adds defaults/handling for empty composite fields
(incl. actor)
> - Frontend `useUpdateOneRecord` upserts returned record into the local
store after mutation
>
> Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1f283778e9cb28a35bf84632a1a2997250bb3131. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).
---------
Co-authored-by: Félix Malfait
Co-authored-by: Félix Malfait
---
.../object-record/hooks/useUpdateOneRecord.ts | 6 +
...tivity-query-result-getter.handler.spec.ts | 6 +
.../core/handlers/rest-api-base.handler.ts | 4 +-
.../engine/core-modules/actor/actor.module.ts | 12 +-
.../created-by.create-many.pre-query-hook.ts | 20 +-
.../created-by.create-one.pre-query-hook.ts | 22 +-
.../updated-by.update-many.pre-query-hook.ts | 49 ++
.../updated-by.update-one.pre-query-hook.ts | 49 ++
...> actor-from-auth-context.service.spec.ts} | 50 +-
....ts => actor-from-auth-context.service.ts} | 118 +++-
.../services/create-record.service.ts | 11 +-
.../services/update-record.service.ts | 8 +
.../create-record-execution-context.type.ts | 7 +
.../types/create-record-params.type.ts | 6 +-
.../types/delete-record-params.type.ts | 2 +-
.../types/find-records-params.type.ts | 2 +-
... => record-crud-execution-context.type.ts} | 6 -
.../update-record-execution-context.type.ts | 7 +
.../types/update-record-params.type.ts | 5 +-
.../types/upsert-record-params.type.ts | 2 +-
...-field-metadatas-for-custom-object.util.ts | 37 ++
.../twenty-orm/custom.workspace-entity.ts | 10 +
.../twenty-orm/utils/format-result.util.ts | 90 +++
.../attachment-data-seeds.constant.ts | 9 +
.../constants/company-data-seeds.constant.ts | 9 +
.../dashboard-data-seeds.constant.ts | 15 +
.../constants/note-data-seeds.constant.ts | 12 +
.../opportunity-data-seeds.constant.ts | 9 +
.../constants/person-data-seeds.constant.ts | 9 +
.../constants/task-data-seeds.constant.ts | 12 +
.../prefill-companies.ts | 18 +
.../prefill-people.ts | 18 +
.../prefill-workflows.ts | 6 +
.../constants/standard-object.constant.ts | 17 +
...hment-standard-flat-field-metadata.util.ts | 22 +
...mpany-standard-flat-field-metadata.util.ts | 22 +
...board-standard-flat-field-metadata.util.ts | 22 +
...-note-standard-flat-field-metadata.util.ts | 22 +
...unity-standard-flat-field-metadata.util.ts | 22 +
...erson-standard-flat-field-metadata.util.ts | 22 +
...-task-standard-flat-field-metadata.util.ts | 22 +
...w-run-standard-flat-field-metadata.util.ts | 22 +
...kflow-standard-flat-field-metadata.util.ts | 22 +
.../constants/standard-field-ids.ts | 10 +
.../attachment.workspace-entity.ts | 10 +
.../company.workspace-entity.ts | 10 +
.../dashboard.workspace-entity.ts | 10 +
.../standard-objects/note.workspace-entity.ts | 10 +
.../opportunity.workspace-entity.ts | 10 +
.../person.workspace-entity.ts | 10 +
.../standard-objects/task.workspace-entity.ts | 10 +
.../workflow-run.workspace-entity.ts | 10 +
.../workflow.workspace-entity.ts | 10 +
.../build-workflow-actor-metadata.util.ts | 18 +
.../create-record.workflow-action.ts | 20 +-
.../update-record.workflow-action.ts | 4 +
...bject-metadata-v2.integration-spec.ts.snap | 629 ++++++++++++++++++
...ject-metadata-creation.integration-spec.ts | 2 +-
58 files changed, 1516 insertions(+), 118 deletions(-)
create mode 100644 packages/twenty-server/src/engine/core-modules/actor/query-hooks/updated-by.update-many.pre-query-hook.ts
create mode 100644 packages/twenty-server/src/engine/core-modules/actor/query-hooks/updated-by.update-one.pre-query-hook.ts
rename packages/twenty-server/src/engine/core-modules/actor/services/__tests__/{created-by-from-auth-context.service.spec.ts => actor-from-auth-context.service.spec.ts} (84%)
rename packages/twenty-server/src/engine/core-modules/actor/services/{created-by-from-auth-context.service.ts => actor-from-auth-context.service.ts} (61%)
create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-execution-context.type.ts
rename packages/twenty-server/src/engine/core-modules/record-crud/types/{execution-context.type.ts => record-crud-execution-context.type.ts} (56%)
create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/types/update-record-execution-context.type.ts
create mode 100644 packages/twenty-server/src/modules/workflow/workflow-executor/utils/build-workflow-actor-metadata.util.ts
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);
});
});