mirror of
https://github.com/twentyhq/twenty.git
synced 2026-06-12 01:46:39 -04:00
fix: exclude system objects and workflow/dashboard from AI/MCP write tool descriptors (#20973)
## Summary fix: exclude system join objects from AI/MCP create/update/delete tool descriptors Closes #20403 --- AI was used for assistance. --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -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<SelectOption<string>> =
|
||||
activeNonSystemObjectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
canObjectBeManagedByWorkflow({
|
||||
canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadataItem.nameSingular,
|
||||
isSystem: objectMetadataItem.isSystem,
|
||||
}),
|
||||
)
|
||||
.map((item) => ({
|
||||
|
||||
@@ -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<SelectOption<string>> =
|
||||
activeNonSystemObjectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
canObjectBeManagedByWorkflow({
|
||||
canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadataItem.nameSingular,
|
||||
isSystem: objectMetadataItem.isSystem,
|
||||
}),
|
||||
)
|
||||
.map((item) => ({
|
||||
|
||||
@@ -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<SelectOption<string>> =
|
||||
activeNonSystemObjectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
canObjectBeManagedByWorkflow({
|
||||
canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadataItem.nameSingular,
|
||||
isSystem: objectMetadataItem.isSystem,
|
||||
}),
|
||||
)
|
||||
.map((item) => ({
|
||||
|
||||
@@ -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<SelectOption<string>> =
|
||||
activeNonSystemObjectMetadataItems
|
||||
.filter((objectMetadataItem) =>
|
||||
canObjectBeManagedByWorkflow({
|
||||
canObjectBeManagedByAutomation({
|
||||
nameSingular: objectMetadataItem.nameSingular,
|
||||
isSystem: objectMetadataItem.isSystem,
|
||||
}),
|
||||
)
|
||||
.map((item) => ({
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<FlatObjectMetadata> &
|
||||
Pick<FlatObjectMetadata, 'nameSingular' | 'namePlural'>,
|
||||
) =>
|
||||
getFlatObjectMetadataMock({
|
||||
universalIdentifier: overrides.nameSingular,
|
||||
labelSingular: overrides.nameSingular,
|
||||
labelPlural: overrides.namePlural,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('DatabaseToolProvider', () => {
|
||||
const generateDescriptorNames = async (objects: FlatObjectMetadata[]) => {
|
||||
const flatObjectMetadataMaps =
|
||||
createEmptyFlatEntityMaps() as FlatEntityMaps<FlatObjectMetadata>;
|
||||
|
||||
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',
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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.`,
|
||||
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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],
|
||||
);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
export const canObjectBeManagedByWorkflow = ({
|
||||
nameSingular,
|
||||
isSystem,
|
||||
}: {
|
||||
nameSingular: string;
|
||||
isSystem: boolean;
|
||||
}) => {
|
||||
const excludedNonSystemObjectMetadataItemNames = [
|
||||
'workflow',
|
||||
'workflowVersion',
|
||||
'workflowRun',
|
||||
'dashboard',
|
||||
];
|
||||
|
||||
return (
|
||||
!excludedNonSystemObjectMetadataItemNames.includes(nameSingular) &&
|
||||
!isSystem
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user