diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord.tsx index d3236fee176..74f04f1d702 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord.tsx @@ -15,7 +15,7 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components import { t } from '@lingui/core/macro'; import { useEffect, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { HorizontalSeparator } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; import { type JsonValue } from 'type-fest'; @@ -74,9 +74,8 @@ export const WorkflowEditActionCreateRecord = ({ const availableMetadata: Array> = activeNonSystemObjectMetadataItems .filter((objectMetadataItem) => - canObjectBeManagedByWorkflow({ + canObjectBeManagedByAutomation({ nameSingular: objectMetadataItem.nameSingular, - isSystem: objectMetadataItem.isSystem, }), ) .map((item) => ({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord.tsx index 19badac48c3..cbccce90fa1 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord.tsx @@ -11,7 +11,7 @@ import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowS import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter'; import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker'; import { isDefined } from 'twenty-shared/utils'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { HorizontalSeparator } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; import { type JsonValue } from 'type-fest'; @@ -46,9 +46,8 @@ export const WorkflowEditActionDeleteRecord = ({ const availableMetadata: Array> = activeNonSystemObjectMetadataItems .filter((objectMetadataItem) => - canObjectBeManagedByWorkflow({ + canObjectBeManagedByAutomation({ nameSingular: objectMetadataItem.nameSingular, - isSystem: objectMetadataItem.isSystem, }), ) .map((item) => ({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx index 48eea59537b..03a63101178 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpdateRecord.tsx @@ -16,7 +16,7 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components import { t } from '@lingui/core/macro'; import { useEffect, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { HorizontalSeparator } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; import { type JsonValue } from 'type-fest'; @@ -47,9 +47,8 @@ export const WorkflowEditActionUpdateRecord = ({ const availableMetadata: Array> = activeNonSystemObjectMetadataItems .filter((objectMetadataItem) => - canObjectBeManagedByWorkflow({ + canObjectBeManagedByAutomation({ nameSingular: objectMetadataItem.nameSingular, - isSystem: objectMetadataItem.isSystem, }), ) .map((item) => ({ diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpsertRecord.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpsertRecord.tsx index b7f89fec19a..6926ec1af85 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpsertRecord.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionUpsertRecord.tsx @@ -17,7 +17,7 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components import { t } from '@lingui/core/macro'; import { useEffect, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { HorizontalSeparator } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; import { type JsonValue } from 'type-fest'; @@ -84,9 +84,8 @@ export const WorkflowEditActionUpsertRecord = ({ const availableMetadata: Array> = activeNonSystemObjectMetadataItems .filter((objectMetadataItem) => - canObjectBeManagedByWorkflow({ + canObjectBeManagedByAutomation({ nameSingular: objectMetadataItem.nameSingular, - isSystem: objectMetadataItem.isSystem, }), ) .map((item) => ({ diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-many-records.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-many-records.service.ts index 11f695183ab..0ec3ba5e185 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-many-records.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-many-records.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { FieldActorSource } from 'twenty-shared/types'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { CommonCreateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service'; import { @@ -38,13 +38,12 @@ export class CreateManyRecordsService { }); if ( - !canObjectBeManagedByWorkflow({ + !canObjectBeManagedByAutomation({ nameSingular: flatObjectMetadata.nameSingular, - isSystem: flatObjectMetadata.isSystem, }) ) { throw new RecordCrudException( - 'Failed to create: Object cannot be created by workflow', + 'Failed to create: Object cannot be created by automation', RecordCrudExceptionCode.INVALID_REQUEST, ); } 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 e1bbb8995e8..cd36ddb67e7 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 @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { FieldActorSource } from 'twenty-shared/types'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-one-query-runner.service'; import { @@ -38,13 +38,12 @@ export class CreateRecordService { }); if ( - !canObjectBeManagedByWorkflow({ + !canObjectBeManagedByAutomation({ nameSingular: flatObjectMetadata.nameSingular, - isSystem: flatObjectMetadata.isSystem, }) ) { throw new RecordCrudException( - 'Failed to create: Object cannot be created by workflow', + 'Failed to create: Object cannot be created by automation', RecordCrudExceptionCode.INVALID_REQUEST, ); } diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-record.service.ts index e991be8b359..b6eea807b6d 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/delete-record.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { isDefined, isValidUuid } from 'twenty-shared/utils'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { CommonDeleteOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-one-query-runner.service'; import { CommonDestroyOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service'; @@ -42,13 +42,12 @@ export class DeleteRecordService { }); if ( - !canObjectBeManagedByWorkflow({ + !canObjectBeManagedByAutomation({ nameSingular: flatObjectMetadata.nameSingular, - isSystem: flatObjectMetadata.isSystem, }) ) { throw new RecordCrudException( - 'Failed to delete: Object cannot be deleted by workflow', + 'Failed to delete: Object cannot be deleted by automation', RecordCrudExceptionCode.INVALID_REQUEST, ); } diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/update-many-records.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/update-many-records.service.ts index 572b24346e6..dbe83ad114b 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/update-many-records.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/update-many-records.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { CommonUpdateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-many-query-runner.service'; import { @@ -37,13 +37,12 @@ export class UpdateManyRecordsService { }); if ( - !canObjectBeManagedByWorkflow({ + !canObjectBeManagedByAutomation({ nameSingular: flatObjectMetadata.nameSingular, - isSystem: flatObjectMetadata.isSystem, }) ) { throw new RecordCrudException( - 'Failed to update: Object cannot be updated by workflow', + 'Failed to update: Object cannot be updated by automation', RecordCrudExceptionCode.INVALID_REQUEST, ); } 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 c0d056d9d30..ce07e5bd010 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,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { isDefined, isValidUuid } from 'twenty-shared/utils'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { CommonUpdateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-one-query-runner.service'; import { @@ -52,13 +52,12 @@ export class UpdateRecordService { }); if ( - !canObjectBeManagedByWorkflow({ + !canObjectBeManagedByAutomation({ nameSingular: flatObjectMetadata.nameSingular, - isSystem: flatObjectMetadata.isSystem, }) ) { throw new RecordCrudException( - 'Failed to update: Object cannot be updated by workflow', + 'Failed to update: Object cannot be updated by automation', RecordCrudExceptionCode.INVALID_REQUEST, ); } diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-record.service.ts index f4343a12fa7..58ee63f8598 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/upsert-record.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; -import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-one-query-runner.service'; import { @@ -32,13 +32,12 @@ export class UpsertRecordService { }); if ( - !canObjectBeManagedByWorkflow({ + !canObjectBeManagedByAutomation({ nameSingular: flatObjectMetadata.nameSingular, - isSystem: flatObjectMetadata.isSystem, }) ) { throw new RecordCrudException( - 'Failed to update: Object cannot be updated by workflow', + 'Failed to upsert: Object cannot be upserted by automation', RecordCrudExceptionCode.INVALID_REQUEST, ); } diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/__tests__/database-tool.provider.spec.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/__tests__/database-tool.provider.spec.ts new file mode 100644 index 00000000000..4d78421f527 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/__tests__/database-tool.provider.spec.ts @@ -0,0 +1,174 @@ +import { type ObjectPermissions } from 'twenty-shared/types'; + +import { DatabaseToolProvider } from 'src/engine/core-modules/tool-provider/providers/database-tool.provider'; +import { type ToolDescriptor } from 'src/engine/core-modules/tool-provider/types/tool-descriptor.type'; +import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { type WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { type WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; + +const roleId = 'role-id'; +const workspaceId = 'workspace-id'; + +const allObjectPermissions: ObjectPermissions = { + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: true, + canDestroyObjectRecords: true, + restrictedFields: {}, + rowLevelPermissionPredicates: [], + rowLevelPermissionPredicateGroups: [], +}; + +const createFlatObject = ( + overrides: Partial & + Pick, +) => + getFlatObjectMetadataMock({ + universalIdentifier: overrides.nameSingular, + labelSingular: overrides.nameSingular, + labelPlural: overrides.namePlural, + ...overrides, + }); + +describe('DatabaseToolProvider', () => { + const generateDescriptorNames = async (objects: FlatObjectMetadata[]) => { + const flatObjectMetadataMaps = + createEmptyFlatEntityMaps() as FlatEntityMaps; + + for (const object of objects) { + flatObjectMetadataMaps.byUniversalIdentifier[object.universalIdentifier] = + object; + flatObjectMetadataMaps.universalIdentifierById[object.id] = + object.universalIdentifier; + } + + const workspaceCacheService = { + getOrRecompute: jest.fn().mockResolvedValue({ + rolesPermissions: { + [roleId]: Object.fromEntries( + objects.map((object) => [object.id, allObjectPermissions]), + ), + }, + }), + } as unknown as WorkspaceCacheService; + + const flatEntityMapsCacheService = { + getOrRecomputeManyOrAllFlatEntityMaps: jest.fn().mockResolvedValue({ + flatObjectMetadataMaps, + flatFieldMetadataMaps: createEmptyFlatEntityMaps(), + }), + } as unknown as WorkspaceManyOrAllFlatEntityMapsCacheService; + + const provider = new DatabaseToolProvider( + workspaceCacheService, + flatEntityMapsCacheService, + ); + + const descriptors = (await provider.generateDescriptors( + { + workspaceId, + roleId, + rolePermissionConfig: { unionOf: [roleId] }, + }, + { includeSchemas: false }, + )) as ToolDescriptor[]; + + return descriptors.map((descriptor) => descriptor.name); + }; + + it('advertises write tools for join/system objects allowed by automation', async () => { + const descriptorNames = await generateDescriptorNames([ + createFlatObject({ + nameSingular: 'noteTarget', + namePlural: 'noteTargets', + isSystem: true, + }), + createFlatObject({ + nameSingular: 'taskTarget', + namePlural: 'taskTargets', + isSystem: true, + }), + createFlatObject({ + nameSingular: 'attachment', + namePlural: 'attachments', + isSystem: true, + }), + createFlatObject({ + nameSingular: 'timelineActivity', + namePlural: 'timelineActivities', + isSystem: true, + }), + createFlatObject({ + nameSingular: 'person', + namePlural: 'people', + }), + ]); + + expect(descriptorNames).toEqual( + expect.arrayContaining([ + 'create_note_target', + 'create_many_note_targets', + 'update_note_target', + 'update_many_note_targets', + 'delete_note_target', + 'create_task_target', + 'create_attachment', + 'create_timeline_activity', + 'create_person', + ]), + ); + }); + + it('does not advertise write tools for objects blocked from automation', async () => { + const descriptorNames = await generateDescriptorNames([ + createFlatObject({ + nameSingular: 'workspaceMember', + namePlural: 'workspaceMembers', + isSystem: true, + }), + createFlatObject({ + nameSingular: 'message', + namePlural: 'messages', + isSystem: true, + }), + createFlatObject({ + nameSingular: 'calendarEvent', + namePlural: 'calendarEvents', + isSystem: true, + }), + createFlatObject({ + nameSingular: 'dashboard', + namePlural: 'dashboards', + }), + ]); + + expect(descriptorNames).toEqual( + expect.arrayContaining([ + 'find_workspace_members', + 'find_messages', + 'find_calendar_events', + 'find_dashboards', + ]), + ); + + expect(descriptorNames).toEqual( + expect.not.arrayContaining([ + 'create_workspace_member', + 'update_workspace_member', + 'delete_workspace_member', + 'create_message', + 'update_message', + 'delete_message', + 'create_calendar_event', + 'update_calendar_event', + 'delete_calendar_event', + 'create_dashboard', + 'update_dashboard', + 'delete_dashboard', + ]), + ); + }); +}); diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts index 45afaa2bb2f..e3c17932f08 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts @@ -5,6 +5,7 @@ import { type ObjectsPermissionsByRoleId, } from 'twenty-shared/types'; import { camelToSnakeCase, isDefined } from 'twenty-shared/utils'; +import { canObjectBeManagedByAutomation } from 'twenty-shared/workflow'; import { z } from 'zod'; import { type GenerateDescriptorOptions } from 'src/engine/core-modules/tool-provider/interfaces/generate-descriptor-options.type'; @@ -116,6 +117,9 @@ export class DatabaseToolProvider implements ToolProvider { const restrictedFields = permission.restrictedFields; const snakePlural = camelToSnakeCase(objectMetadata.namePlural); const snakeSingular = camelToSnakeCase(objectMetadata.nameSingular); + const canBeManagedByAutomation = canObjectBeManagedByAutomation({ + nameSingular: objectMetadata.nameSingular, + }); if (permission.canReadObjectRecords) { descriptors.push({ @@ -182,7 +186,7 @@ export class DatabaseToolProvider implements ToolProvider { } } - if (permission.canUpdateObjectRecords) { + if (permission.canUpdateObjectRecords && canBeManagedByAutomation) { descriptors.push({ name: `create_${snakeSingular}`, description: `Create a new ${objectMetadata.labelSingular} record. Provide all required fields and any optional fields you want to set. The system will automatically handle timestamps and IDs. Returns the created record with all its data.`, @@ -266,7 +270,7 @@ export class DatabaseToolProvider implements ToolProvider { }); } - if (permission.canSoftDeleteObjectRecords) { + if (permission.canSoftDeleteObjectRecords && canBeManagedByAutomation) { descriptors.push({ name: `delete_${snakeSingular}`, description: `Delete a ${objectMetadata.labelSingular} record by marking it as deleted. The record is hidden from normal queries. This is reversible. Use this to remove records.`, diff --git a/packages/twenty-shared/src/workflow/constants/ObjectsBlockedFromAutomation.ts b/packages/twenty-shared/src/workflow/constants/ObjectsBlockedFromAutomation.ts new file mode 100644 index 00000000000..e515f07edce --- /dev/null +++ b/packages/twenty-shared/src/workflow/constants/ObjectsBlockedFromAutomation.ts @@ -0,0 +1,19 @@ +// Objects whose records must not be created, updated, or deleted by +// automation callers (workflows and AI tools). Either they back the +// automation runtime itself (recursion risk), gate access/permissions, +// or are owned by background sync (writing to them corrupts state). +export const OBJECTS_BLOCKED_FROM_AUTOMATION = [ + 'workflow', + 'workflowVersion', + 'workflowRun', + 'workflowAutomatedTrigger', + 'workspaceMember', + 'dashboard', + 'message', + 'messageThread', + 'messageChannelMessageAssociation', + 'messageParticipant', + 'calendarEvent', + 'calendarEventParticipant', + 'calendarChannelEventAssociation', +] as const; diff --git a/packages/twenty-shared/src/workflow/index.ts b/packages/twenty-shared/src/workflow/index.ts index 615c45e7f04..9f97657b888 100644 --- a/packages/twenty-shared/src/workflow/index.ts +++ b/packages/twenty-shared/src/workflow/index.ts @@ -10,6 +10,7 @@ export { CAPTURE_ALL_VARIABLE_TAG_INNER_REGEX } from './constants/CaptureAllVariableTagInnerRegex'; export { CONTENT_TYPE_VALUES_HTTP_REQUEST } from './constants/ContentTypeValuesHttpRequest'; export { IF_ELSE_BRANCH_POSITION_OFFSETS } from './constants/IfElseBranchPositionOffsets'; +export { OBJECTS_BLOCKED_FROM_AUTOMATION } from './constants/ObjectsBlockedFromAutomation'; export { TRIGGER_STEP_ID } from './constants/TriggerStepId'; export { workflowAiAgentActionSchema } from './schemas/ai-agent-action-schema'; export { workflowAiAgentActionSettingsSchema } from './schemas/ai-agent-action-settings-schema'; @@ -81,7 +82,7 @@ export type { WorkflowRunStepInfos, } from './types/WorkflowRunStateStepInfos'; export { StepStatus } from './types/WorkflowRunStateStepInfos'; -export { canObjectBeManagedByWorkflow } from './utils/canObjectBeManagedByWorkflow'; +export { canObjectBeManagedByAutomation } from './utils/canObjectBeManagedByAutomation'; export { extractRawVariableNamePart } from './utils/extractRawVariableNameParts'; export { getFunctionInputFromInputSchema } from './utils/getFunctionInputFromInputSchema'; export { getWorkflowRunContext } from './utils/getWorkflowRunContext'; diff --git a/packages/twenty-shared/src/workflow/utils/__tests__/canObjectBeManagedByAutomation.test.ts b/packages/twenty-shared/src/workflow/utils/__tests__/canObjectBeManagedByAutomation.test.ts new file mode 100644 index 00000000000..bce2fa6d8af --- /dev/null +++ b/packages/twenty-shared/src/workflow/utils/__tests__/canObjectBeManagedByAutomation.test.ts @@ -0,0 +1,45 @@ +import { canObjectBeManagedByAutomation } from '@/workflow/utils/canObjectBeManagedByAutomation'; + +describe('canObjectBeManagedByAutomation', () => { + it('should return true for a standard non-blocked object', () => { + expect(canObjectBeManagedByAutomation({ nameSingular: 'company' })).toBe( + true, + ); + }); + + it('should return true for noteTarget and taskTarget', () => { + expect(canObjectBeManagedByAutomation({ nameSingular: 'noteTarget' })).toBe( + true, + ); + expect(canObjectBeManagedByAutomation({ nameSingular: 'taskTarget' })).toBe( + true, + ); + }); + + it('should return true for attachment and timelineActivity', () => { + expect(canObjectBeManagedByAutomation({ nameSingular: 'attachment' })).toBe( + true, + ); + expect( + canObjectBeManagedByAutomation({ nameSingular: 'timelineActivity' }), + ).toBe(true); + }); + + it.each([ + 'workflow', + 'workflowVersion', + 'workflowRun', + 'workflowAutomatedTrigger', + 'workspaceMember', + 'dashboard', + 'message', + 'messageThread', + 'messageChannelMessageAssociation', + 'messageParticipant', + 'calendarEvent', + 'calendarEventParticipant', + 'calendarChannelEventAssociation', + ])('should return false for %s', (nameSingular) => { + expect(canObjectBeManagedByAutomation({ nameSingular })).toBe(false); + }); +}); diff --git a/packages/twenty-shared/src/workflow/utils/__tests__/canObjectBeManagedByWorkflow.test.ts b/packages/twenty-shared/src/workflow/utils/__tests__/canObjectBeManagedByWorkflow.test.ts deleted file mode 100644 index bfd6032c192..00000000000 --- a/packages/twenty-shared/src/workflow/utils/__tests__/canObjectBeManagedByWorkflow.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { canObjectBeManagedByWorkflow } from '@/workflow/utils/canObjectBeManagedByWorkflow'; - -describe('canObjectBeManagedByWorkflow', () => { - it('should return true for non-system, non-excluded objects', () => { - expect( - canObjectBeManagedByWorkflow({ - nameSingular: 'company', - isSystem: false, - }), - ).toBe(true); - }); - - it('should return false for system objects', () => { - expect( - canObjectBeManagedByWorkflow({ - nameSingular: 'company', - isSystem: true, - }), - ).toBe(false); - }); - - it('should return false for workflow object', () => { - expect( - canObjectBeManagedByWorkflow({ - nameSingular: 'workflow', - isSystem: false, - }), - ).toBe(false); - }); - - it('should return false for workflowVersion object', () => { - expect( - canObjectBeManagedByWorkflow({ - nameSingular: 'workflowVersion', - isSystem: false, - }), - ).toBe(false); - }); - - it('should return false for workflowRun object', () => { - expect( - canObjectBeManagedByWorkflow({ - nameSingular: 'workflowRun', - isSystem: false, - }), - ).toBe(false); - }); - - it('should return false for dashboard object', () => { - expect( - canObjectBeManagedByWorkflow({ - nameSingular: 'dashboard', - isSystem: false, - }), - ).toBe(false); - }); -}); diff --git a/packages/twenty-shared/src/workflow/utils/canObjectBeManagedByAutomation.ts b/packages/twenty-shared/src/workflow/utils/canObjectBeManagedByAutomation.ts new file mode 100644 index 00000000000..f298d571cc2 --- /dev/null +++ b/packages/twenty-shared/src/workflow/utils/canObjectBeManagedByAutomation.ts @@ -0,0 +1,11 @@ +import { OBJECTS_BLOCKED_FROM_AUTOMATION } from '../constants/ObjectsBlockedFromAutomation'; + +export const canObjectBeManagedByAutomation = ({ + nameSingular, +}: { + nameSingular: string; +}): boolean => { + return !OBJECTS_BLOCKED_FROM_AUTOMATION.includes( + nameSingular as (typeof OBJECTS_BLOCKED_FROM_AUTOMATION)[number], + ); +}; diff --git a/packages/twenty-shared/src/workflow/utils/canObjectBeManagedByWorkflow.ts b/packages/twenty-shared/src/workflow/utils/canObjectBeManagedByWorkflow.ts deleted file mode 100644 index 1506683a314..00000000000 --- a/packages/twenty-shared/src/workflow/utils/canObjectBeManagedByWorkflow.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const canObjectBeManagedByWorkflow = ({ - nameSingular, - isSystem, -}: { - nameSingular: string; - isSystem: boolean; -}) => { - const excludedNonSystemObjectMetadataItemNames = [ - 'workflow', - 'workflowVersion', - 'workflowRun', - 'dashboard', - ]; - - return ( - !excludedNonSystemObjectMetadataItemNames.includes(nameSingular) && - !isSystem - ); -};