[permissions] permissions and workflows (#12436)
In this PR - Determine object record permissions on workflows objects (workflow, workflowVersion, workflowRun) base on settings permissions @Weiko - Add Workflow permission guards on workflow resolvers @thomtrp . **Any method within a resolver that has the SettingsPermission Guard is only callable by a apiKey or a user that has the permission** (so not by external parties). - Add checks bypass in workflow services since 1) for actions gated by settings permissions, the gate should be done at resolver level, so it will have been done before the call to the service 2) some service methods may be called by workflowTriggerController which is callable by external parties without permissions (ex: workflowCommonWorkspaceService.getWorkflowVersionOrFail). This is something we may want to change in the future (still to discuss), by removing the guard at resolver-level and relying on shouldBypassPermissionChecks at getRepository and made in a way that we only bypass for external parties. - Add checks bypass for actions performed by workflows since they should not be restricted in our current vision - Add tests
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
|
|
||||||
export const SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS = {
|
export const OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS = {
|
||||||
apiKey: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
apiKey: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||||
webhook: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
webhook: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||||
} as const;
|
} as const;
|
||||||
@ -15,7 +15,7 @@ import {
|
|||||||
WorkspaceResolverBuilderMethodNames,
|
WorkspaceResolverBuilderMethodNames,
|
||||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import { SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/graphql/graphql-query-runner/constants/system-objects-permissions-requirements.constant';
|
import { OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements';
|
||||||
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||||
@ -97,7 +97,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
featureFlagsMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED];
|
featureFlagsMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED];
|
||||||
|
|
||||||
if (objectMetadataItemWithFieldMaps.isSystem === true) {
|
if (objectMetadataItemWithFieldMaps.isSystem === true) {
|
||||||
await this.validateSystemObjectPermissionsOrThrow(options);
|
await this.validateSettingsPermissionsOnObjectOrThrow(options);
|
||||||
} else {
|
} else {
|
||||||
if (!isPermissionsV2Enabled)
|
if (!isPermissionsV2Enabled)
|
||||||
await this.validateObjectRecordPermissionsOrThrow({
|
await this.validateObjectRecordPermissionsOrThrow({
|
||||||
@ -186,19 +186,19 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validateSystemObjectPermissionsOrThrow(
|
private async validateSettingsPermissionsOnObjectOrThrow(
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
) {
|
) {
|
||||||
const { authContext, objectMetadataItemWithFieldMaps } = options;
|
const { authContext, objectMetadataItemWithFieldMaps } = options;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Object.keys(SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS).includes(
|
Object.keys(OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS).includes(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
const permissionRequired: SettingPermissionType =
|
const permissionRequired: SettingPermissionType =
|
||||||
// @ts-expect-error legacy noImplicitAny
|
// @ts-expect-error legacy noImplicitAny
|
||||||
SYSTEM_OBJECTS_PERMISSIONS_REQUIREMENTS[
|
OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS[
|
||||||
objectMetadataItemWithFieldMaps.nameSingular
|
objectMetadataItemWithFieldMaps.nameSingular
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -162,6 +162,10 @@ export class WorkspaceQueryHookExplorer implements OnModuleInit {
|
|||||||
{
|
{
|
||||||
req: {
|
req: {
|
||||||
workspaceId: executeParams?.[0].workspace.id,
|
workspaceId: executeParams?.[0].workspace.id,
|
||||||
|
userWorkspaceId: executeParams?.[0].userWorkspaceId,
|
||||||
|
apiKey: executeParams?.[0].apiKey,
|
||||||
|
workspaceMemberId: executeParams?.[0].workspaceMemberId,
|
||||||
|
user: executeParams?.[0].user,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
contextId,
|
contextId,
|
||||||
|
|||||||
@ -14,7 +14,8 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { WorkflowTriggerRestApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-rest-api-exception.filter';
|
import { WorkflowTriggerRestApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-rest-api-exception.filter';
|
||||||
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard';
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStatus,
|
WorkflowVersionStatus,
|
||||||
WorkflowVersionWorkspaceEntity,
|
WorkflowVersionWorkspaceEntity,
|
||||||
@ -28,10 +29,13 @@ import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types
|
|||||||
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
||||||
|
|
||||||
@Controller('webhooks')
|
@Controller('webhooks')
|
||||||
@UseFilters(WorkflowTriggerRestApiExceptionFilter)
|
@UseFilters(
|
||||||
|
WorkflowTriggerRestApiExceptionFilter,
|
||||||
|
PermissionsGraphqlApiExceptionFilter,
|
||||||
|
)
|
||||||
export class WorkflowTriggerController {
|
export class WorkflowTriggerController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workflowTriggerWorkspaceService: WorkflowTriggerWorkspaceService,
|
private readonly workflowTriggerWorkspaceService: WorkflowTriggerWorkspaceService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -68,8 +72,10 @@ export class WorkflowTriggerController {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}) {
|
}) {
|
||||||
const workflowRepository =
|
const workflowRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflow',
|
'workflow',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflow = await workflowRepository.findOne({
|
const workflow = await workflowRepository.findOne({
|
||||||
@ -94,8 +100,10 @@ export class WorkflowTriggerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
const workflowVersion = await workflowVersionRepository.findOne({
|
const workflowVersion = await workflowVersionRepository.findOne({
|
||||||
where: { id: workflow.lastPublishedVersionId },
|
where: { id: workflow.lastPublishedVersionId },
|
||||||
|
|||||||
@ -7,14 +7,24 @@ import { ComputeStepOutputSchemaInput } from 'src/engine/core-modules/workflow/d
|
|||||||
import { WorkflowTriggerGraphqlApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter';
|
import { WorkflowTriggerGraphqlApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||||
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
@UseGuards(
|
||||||
@UseFilters(WorkflowTriggerGraphqlApiExceptionFilter)
|
WorkspaceAuthGuard,
|
||||||
|
UserAuthGuard,
|
||||||
|
SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS),
|
||||||
|
)
|
||||||
|
@UseFilters(
|
||||||
|
WorkflowTriggerGraphqlApiExceptionFilter,
|
||||||
|
PermissionsGraphqlApiExceptionFilter,
|
||||||
|
)
|
||||||
export class WorkflowBuilderResolver {
|
export class WorkflowBuilderResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
|
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { UseGuards } from '@nestjs/common';
|
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
|
import { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
|
||||||
@ -9,13 +9,21 @@ import { UpdateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow
|
|||||||
import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-step.dto';
|
import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-step.dto';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-step/workflow-version-step.workspace-service';
|
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-step/workflow-version-step.workspace-service';
|
||||||
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
|
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
@UseGuards(
|
||||||
|
WorkspaceAuthGuard,
|
||||||
|
UserAuthGuard,
|
||||||
|
SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS),
|
||||||
|
)
|
||||||
|
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||||
export class WorkflowStepResolver {
|
export class WorkflowStepResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workflowVersionStepWorkspaceService: WorkflowVersionStepWorkspaceService,
|
private readonly workflowVersionStepWorkspaceService: WorkflowVersionStepWorkspaceService,
|
||||||
@ -78,10 +86,12 @@ export class WorkflowStepResolver {
|
|||||||
|
|
||||||
@Mutation(() => WorkflowActionDTO)
|
@Mutation(() => WorkflowActionDTO)
|
||||||
async updateWorkflowRunStep(
|
async updateWorkflowRunStep(
|
||||||
|
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||||
@Args('input')
|
@Args('input')
|
||||||
{ workflowRunId, step }: UpdateWorkflowRunStepInput,
|
{ workflowRunId, step }: UpdateWorkflowRunStepInput,
|
||||||
): Promise<WorkflowActionDTO> {
|
): Promise<WorkflowActionDTO> {
|
||||||
await this.workflowRunWorkspaceService.updateWorkflowRunStep({
|
await this.workflowRunWorkspaceService.updateWorkflowRunStep({
|
||||||
|
workspaceId,
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
step,
|
step,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,20 +1,30 @@
|
|||||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { buildCreatedByFromFullNameMetadata } from 'src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { RunWorkflowVersionInput } from 'src/engine/core-modules/workflow/dtos/run-workflow-version-input.dto';
|
import { RunWorkflowVersionInput } from 'src/engine/core-modules/workflow/dtos/run-workflow-version-input.dto';
|
||||||
import { WorkflowRunDTO } from 'src/engine/core-modules/workflow/dtos/workflow-run.dto';
|
import { WorkflowRunDTO } from 'src/engine/core-modules/workflow/dtos/workflow-run.dto';
|
||||||
import { WorkflowTriggerGraphqlApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter';
|
import { WorkflowTriggerGraphqlApiExceptionFilter } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter';
|
||||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||||
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
||||||
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
|
||||||
import { buildCreatedByFromFullNameMetadata } from 'src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util';
|
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
@UseGuards(
|
||||||
@UseFilters(WorkflowTriggerGraphqlApiExceptionFilter)
|
WorkspaceAuthGuard,
|
||||||
|
UserAuthGuard,
|
||||||
|
SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS),
|
||||||
|
)
|
||||||
|
@UseFilters(
|
||||||
|
WorkflowTriggerGraphqlApiExceptionFilter,
|
||||||
|
PermissionsGraphqlApiExceptionFilter,
|
||||||
|
)
|
||||||
export class WorkflowTriggerResolver {
|
export class WorkflowTriggerResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workflowTriggerWorkspaceService: WorkflowTriggerWorkspaceService,
|
private readonly workflowTriggerWorkspaceService: WorkflowTriggerWorkspaceService,
|
||||||
|
|||||||
@ -1,16 +1,24 @@
|
|||||||
import { UseGuards } from '@nestjs/common';
|
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { CreateDraftFromWorkflowVersionInput } from 'src/engine/core-modules/workflow/dtos/create-draft-from-workflow-version-input';
|
import { CreateDraftFromWorkflowVersionInput } from 'src/engine/core-modules/workflow/dtos/create-draft-from-workflow-version-input';
|
||||||
import { WorkflowVersionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version.dto';
|
import { WorkflowVersionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version.dto';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service';
|
import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard)
|
@UseGuards(
|
||||||
|
WorkspaceAuthGuard,
|
||||||
|
UserAuthGuard,
|
||||||
|
SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS),
|
||||||
|
)
|
||||||
|
@UseFilters(PermissionsGraphqlApiExceptionFilter)
|
||||||
export class WorkflowVersionResolver {
|
export class WorkflowVersionResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workflowVersionWorkspaceService: WorkflowVersionWorkspaceService,
|
private readonly workflowVersionWorkspaceService: WorkflowVersionWorkspaceService,
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { WorkflowBuilderResolver } from 'src/engine/core-modules/workflow/resolv
|
|||||||
import { WorkflowStepResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-step.resolver';
|
import { WorkflowStepResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-step.resolver';
|
||||||
import { WorkflowTriggerResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver';
|
import { WorkflowTriggerResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver';
|
||||||
import { WorkflowVersionResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-version.resolver';
|
import { WorkflowVersionResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-version.resolver';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
||||||
import { WorkflowBuilderModule } from 'src/modules/workflow/workflow-builder/workflow-builder.module';
|
import { WorkflowBuilderModule } from 'src/modules/workflow/workflow-builder/workflow-builder.module';
|
||||||
import { WorkflowVersionModule } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.module';
|
import { WorkflowVersionModule } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.module';
|
||||||
@ -18,6 +19,7 @@ import { WorkflowTriggerModule } from 'src/modules/workflow/workflow-trigger/wor
|
|||||||
WorkflowCommonModule,
|
WorkflowCommonModule,
|
||||||
WorkflowVersionModule,
|
WorkflowVersionModule,
|
||||||
WorkflowRunModule,
|
WorkflowRunModule,
|
||||||
|
PermissionsModule,
|
||||||
],
|
],
|
||||||
controllers: [WorkflowTriggerController],
|
controllers: [WorkflowTriggerController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@ -6,4 +6,5 @@ export enum SettingPermissionType {
|
|||||||
DATA_MODEL = 'DATA_MODEL',
|
DATA_MODEL = 'DATA_MODEL',
|
||||||
ADMIN_PANEL = 'ADMIN_PANEL',
|
ADMIN_PANEL = 'ADMIN_PANEL',
|
||||||
SECURITY = 'SECURITY',
|
SECURITY = 'SECURITY',
|
||||||
|
WORKFLOWS = 'WORKFLOWS',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,7 @@ export class RemoteServerService<T extends RemoteServerType> {
|
|||||||
const createdRemoteServer = entityManager.create(
|
const createdRemoteServer = entityManager.create(
|
||||||
RemoteServerEntity,
|
RemoteServerEntity,
|
||||||
remoteServerToCreate,
|
remoteServerToCreate,
|
||||||
);
|
) as RemoteServerEntity<RemoteServerType>;
|
||||||
|
|
||||||
const foreignDataWrapperQuery =
|
const foreignDataWrapperQuery =
|
||||||
this.foreignDataWrapperServerQueryFactory.createForeignDataWrapperServer(
|
this.foreignDataWrapperServerQueryFactory.createForeignDataWrapperServer(
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { In, Repository } from 'typeorm';
|
|||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { UserWorkspaceRoleMap } from 'src/engine/metadata-modules/workspace-permissions-cache/types/user-workspace-role-map.type';
|
import { UserWorkspaceRoleMap } from 'src/engine/metadata-modules/workspace-permissions-cache/types/user-workspace-role-map.type';
|
||||||
@ -18,6 +19,7 @@ import { WorkspacePermissionsCacheStorageService } from 'src/engine/metadata-mod
|
|||||||
import { TwentyORMExceptionCode } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
import { TwentyORMExceptionCode } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
|
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||||
|
|
||||||
type CacheResult<T, U> = {
|
type CacheResult<T, U> = {
|
||||||
version: T;
|
version: T;
|
||||||
@ -244,7 +246,7 @@ export class WorkspacePermissionsCacheService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
...(roleIds ? { id: In(roleIds) } : {}),
|
...(roleIds ? { id: In(roleIds) } : {}),
|
||||||
},
|
},
|
||||||
relations: ['objectPermissions'],
|
relations: ['objectPermissions', 'settingPermissions'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspaceObjectMetadataCollection =
|
const workspaceObjectMetadataCollection =
|
||||||
@ -256,7 +258,7 @@ export class WorkspacePermissionsCacheService {
|
|||||||
const objectRecordsPermissions: ObjectRecordsPermissions = {};
|
const objectRecordsPermissions: ObjectRecordsPermissions = {};
|
||||||
|
|
||||||
for (const objectMetadata of workspaceObjectMetadataCollection) {
|
for (const objectMetadata of workspaceObjectMetadataCollection) {
|
||||||
const { id: objectMetadataId, isSystem } = objectMetadata;
|
const { id: objectMetadataId, isSystem, standardId } = objectMetadata;
|
||||||
|
|
||||||
let canRead = role.canReadAllObjectRecords;
|
let canRead = role.canReadAllObjectRecords;
|
||||||
let canUpdate = role.canUpdateAllObjectRecords;
|
let canUpdate = role.canUpdateAllObjectRecords;
|
||||||
@ -264,32 +266,48 @@ export class WorkspacePermissionsCacheService {
|
|||||||
let canDestroy = role.canDestroyAllObjectRecords;
|
let canDestroy = role.canDestroyAllObjectRecords;
|
||||||
|
|
||||||
if (isPermissionsV2Enabled) {
|
if (isPermissionsV2Enabled) {
|
||||||
const objectRecordPermissionsOverride = role.objectPermissions.find(
|
if (
|
||||||
(objectPermission) =>
|
standardId &&
|
||||||
objectPermission.objectMetadataId === objectMetadataId,
|
[
|
||||||
);
|
STANDARD_OBJECT_IDS.workflow,
|
||||||
|
STANDARD_OBJECT_IDS.workflowRun,
|
||||||
|
STANDARD_OBJECT_IDS.workflowVersion,
|
||||||
|
].includes(standardId)
|
||||||
|
) {
|
||||||
|
const hasWorkflowsPermissions = this.hasWorkflowsPermissions(role);
|
||||||
|
|
||||||
const getPermissionValue = (
|
canRead = hasWorkflowsPermissions;
|
||||||
overrideValue: boolean | undefined,
|
canUpdate = hasWorkflowsPermissions;
|
||||||
defaultValue: boolean,
|
canSoftDelete = hasWorkflowsPermissions;
|
||||||
) => (isSystem ? true : (overrideValue ?? defaultValue));
|
canDestroy = hasWorkflowsPermissions;
|
||||||
|
} else {
|
||||||
|
const objectRecordPermissionsOverride = role.objectPermissions.find(
|
||||||
|
(objectPermission) =>
|
||||||
|
objectPermission.objectMetadataId === objectMetadataId,
|
||||||
|
);
|
||||||
|
|
||||||
canRead = getPermissionValue(
|
const getPermissionValue = (
|
||||||
objectRecordPermissionsOverride?.canReadObjectRecords,
|
overrideValue: boolean | undefined,
|
||||||
canRead,
|
defaultValue: boolean,
|
||||||
);
|
) => (isSystem ? true : (overrideValue ?? defaultValue));
|
||||||
canUpdate = getPermissionValue(
|
|
||||||
objectRecordPermissionsOverride?.canUpdateObjectRecords,
|
canRead = getPermissionValue(
|
||||||
canUpdate,
|
objectRecordPermissionsOverride?.canReadObjectRecords,
|
||||||
);
|
canRead,
|
||||||
canSoftDelete = getPermissionValue(
|
);
|
||||||
objectRecordPermissionsOverride?.canSoftDeleteObjectRecords,
|
canUpdate = getPermissionValue(
|
||||||
canSoftDelete,
|
objectRecordPermissionsOverride?.canUpdateObjectRecords,
|
||||||
);
|
canUpdate,
|
||||||
canDestroy = getPermissionValue(
|
);
|
||||||
objectRecordPermissionsOverride?.canDestroyObjectRecords,
|
canSoftDelete = getPermissionValue(
|
||||||
canDestroy,
|
objectRecordPermissionsOverride?.canSoftDeleteObjectRecords,
|
||||||
);
|
canSoftDelete,
|
||||||
|
);
|
||||||
|
canDestroy = getPermissionValue(
|
||||||
|
objectRecordPermissionsOverride?.canDestroyObjectRecords,
|
||||||
|
canDestroy,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
objectRecordsPermissions[objectMetadataId] = {
|
objectRecordsPermissions[objectMetadataId] = {
|
||||||
@ -298,9 +316,9 @@ export class WorkspacePermissionsCacheService {
|
|||||||
canSoftDelete,
|
canSoftDelete,
|
||||||
canDestroy,
|
canDestroy,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
permissionsByRoleId[role.id] = objectRecordsPermissions;
|
permissionsByRoleId[role.id] = objectRecordsPermissions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return permissionsByRoleId;
|
return permissionsByRoleId;
|
||||||
@ -313,7 +331,7 @@ export class WorkspacePermissionsCacheService {
|
|||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
},
|
},
|
||||||
select: ['id', 'isSystem'],
|
select: ['id', 'isSystem', 'standardId'],
|
||||||
});
|
});
|
||||||
|
|
||||||
return workspaceObjectMetadata;
|
return workspaceObjectMetadata;
|
||||||
@ -336,4 +354,19 @@ export class WorkspacePermissionsCacheService {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as UserWorkspaceRoleMap);
|
}, {} as UserWorkspaceRoleMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private hasWorkflowsPermissions(role: RoleEntity): boolean {
|
||||||
|
const hasWorkflowsPermissionFromRole = role.canUpdateAllSettings;
|
||||||
|
const hasWorkflowsPermissionsFromSettingPermissions = isDefined(
|
||||||
|
role.settingPermissions.find(
|
||||||
|
(settingPermission) =>
|
||||||
|
settingPermission.setting === SettingPermissionType.WORKFLOWS,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
hasWorkflowsPermissionFromRole ||
|
||||||
|
hasWorkflowsPermissionsFromSettingPermissions
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,11 +14,14 @@ export class WorkflowVersionDeleteOnePreQueryHook
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
_authContext: AuthContext,
|
authContext: AuthContext,
|
||||||
_objectName: string,
|
_objectName: string,
|
||||||
payload: DeleteOneResolverArgs,
|
payload: DeleteOneResolverArgs,
|
||||||
): Promise<DeleteOneResolverArgs> {
|
): Promise<DeleteOneResolverArgs> {
|
||||||
|
const { workspace } = authContext;
|
||||||
|
|
||||||
await this.workflowVersionValidationWorkspaceService.validateWorkflowVersionForDeleteOne(
|
await this.workflowVersionValidationWorkspaceService.validateWorkflowVersionForDeleteOne(
|
||||||
|
workspace.id,
|
||||||
payload,
|
payload,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -15,12 +15,17 @@ export class WorkflowVersionUpdateOnePreQueryHook
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
_authContext: AuthContext,
|
authContext: AuthContext,
|
||||||
_objectName: string,
|
_objectName: string,
|
||||||
payload: UpdateOneResolverArgs<WorkflowVersionWorkspaceEntity>,
|
payload: UpdateOneResolverArgs<WorkflowVersionWorkspaceEntity>,
|
||||||
): Promise<UpdateOneResolverArgs<WorkflowVersionWorkspaceEntity>> {
|
): Promise<UpdateOneResolverArgs<WorkflowVersionWorkspaceEntity>> {
|
||||||
|
const { workspace } = authContext;
|
||||||
|
|
||||||
await this.workflowVersionValidationWorkspaceService.validateWorkflowVersionForUpdateOne(
|
await this.workflowVersionValidationWorkspaceService.validateWorkflowVersionForUpdateOne(
|
||||||
payload,
|
{
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
payload,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
import {
|
import {
|
||||||
WorkflowCommonException,
|
WorkflowCommonException,
|
||||||
@ -18,7 +19,6 @@ import {
|
|||||||
WorkflowTriggerException,
|
WorkflowTriggerException,
|
||||||
WorkflowTriggerExceptionCode,
|
WorkflowTriggerExceptionCode,
|
||||||
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
|
||||||
|
|
||||||
export type ObjectMetadataInfo = {
|
export type ObjectMetadataInfo = {
|
||||||
objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps;
|
objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps;
|
||||||
@ -28,14 +28,18 @@ export type ObjectMetadataInfo = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowCommonWorkspaceService {
|
export class WorkflowCommonWorkspaceService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getWorkflowVersionOrFail(
|
async getWorkflowVersionOrFail({
|
||||||
workflowVersionId: string,
|
workspaceId,
|
||||||
): Promise<WorkflowVersionWorkspaceEntity> {
|
workflowVersionId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
workflowVersionId: string;
|
||||||
|
}): Promise<WorkflowVersionWorkspaceEntity> {
|
||||||
if (!workflowVersionId) {
|
if (!workflowVersionId) {
|
||||||
throw new WorkflowTriggerException(
|
throw new WorkflowTriggerException(
|
||||||
'Workflow version ID is required',
|
'Workflow version ID is required',
|
||||||
@ -44,8 +48,10 @@ export class WorkflowCommonWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersion = await workflowVersionRepository.findOne({
|
const workflowVersion = await workflowVersionRepository.findOne({
|
||||||
@ -124,18 +130,24 @@ export class WorkflowCommonWorkspaceService {
|
|||||||
operation: 'restore' | 'delete' | 'destroy';
|
operation: 'restore' | 'delete' | 'destroy';
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowRunRepository =
|
const workflowRunRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowRunWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowRun',
|
'workflowRun',
|
||||||
|
{ shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowAutomatedTriggerRepository =
|
const workflowAutomatedTriggerRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowAutomatedTriggerWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowAutomatedTrigger',
|
'workflowAutomatedTrigger',
|
||||||
|
{ shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const workflowId of workflowIds) {
|
for (const workflowId of workflowIds) {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
UpdateOneResolverArgs,
|
UpdateOneResolverArgs,
|
||||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import {
|
import {
|
||||||
WorkflowQueryValidationException,
|
WorkflowQueryValidationException,
|
||||||
WorkflowQueryValidationExceptionCode,
|
WorkflowQueryValidationExceptionCode,
|
||||||
@ -24,10 +24,11 @@ import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/work
|
|||||||
export class WorkflowVersionValidationWorkspaceService {
|
export class WorkflowVersionValidationWorkspaceService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async validateWorkflowVersionForCreateOne(
|
async validateWorkflowVersionForCreateOne(
|
||||||
|
workspaceId: string,
|
||||||
payload: CreateOneResolverArgs<WorkflowVersionWorkspaceEntity>,
|
payload: CreateOneResolverArgs<WorkflowVersionWorkspaceEntity>,
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
@ -41,8 +42,10 @@ export class WorkflowVersionValidationWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowAlreadyHasDraftVersion =
|
const workflowAlreadyHasDraftVersion =
|
||||||
@ -63,13 +66,18 @@ export class WorkflowVersionValidationWorkspaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateWorkflowVersionForUpdateOne(
|
async validateWorkflowVersionForUpdateOne({
|
||||||
payload: UpdateOneResolverArgs<WorkflowVersionWorkspaceEntity>,
|
workspaceId,
|
||||||
) {
|
payload,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
payload: UpdateOneResolverArgs<WorkflowVersionWorkspaceEntity>;
|
||||||
|
}) {
|
||||||
const workflowVersion =
|
const workflowVersion =
|
||||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
|
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||||
payload.id,
|
workspaceId,
|
||||||
);
|
workflowVersionId: payload.id,
|
||||||
|
});
|
||||||
|
|
||||||
// If the only field updated is the name, we can update the workflow version
|
// If the only field updated is the name, we can update the workflow version
|
||||||
// Otherwise, we need to assert that the workflow version is a draft
|
// Otherwise, we need to assert that the workflow version is a draft
|
||||||
@ -93,17 +101,23 @@ export class WorkflowVersionValidationWorkspaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateWorkflowVersionForDeleteOne(payload: DeleteOneResolverArgs) {
|
async validateWorkflowVersionForDeleteOne(
|
||||||
|
workspaceId: string,
|
||||||
|
payload: DeleteOneResolverArgs,
|
||||||
|
) {
|
||||||
const workflowVersion =
|
const workflowVersion =
|
||||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
|
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||||
payload.id,
|
workspaceId,
|
||||||
);
|
workflowVersionId: payload.id,
|
||||||
|
});
|
||||||
|
|
||||||
assertWorkflowVersionIsDraft(workflowVersion);
|
assertWorkflowVersionIsDraft(workflowVersion);
|
||||||
|
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level
|
||||||
);
|
);
|
||||||
|
|
||||||
const otherWorkflowVersionsExist = await workflowVersionRepository.exists({
|
const otherWorkflowVersionsExist = await workflowVersionRepository.exists({
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow
|
|||||||
import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-step.dto';
|
import { WorkflowActionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-step.dto';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStepException,
|
WorkflowVersionStepException,
|
||||||
WorkflowVersionStepExceptionCode,
|
WorkflowVersionStepExceptionCode,
|
||||||
@ -47,7 +47,7 @@ const BASE_STEP_DEFINITION: BaseWorkflowActionSettings = {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowVersionStepWorkspaceService {
|
export class WorkflowVersionStepWorkspaceService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
|
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
|
||||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||||
@ -74,7 +74,8 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -118,8 +119,10 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
step: WorkflowAction;
|
step: WorkflowAction;
|
||||||
}): Promise<WorkflowAction> {
|
}): Promise<WorkflowAction> {
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersion = await workflowVersionRepository.findOne({
|
const workflowVersion = await workflowVersionRepository.findOne({
|
||||||
@ -174,8 +177,10 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
stepIdToDelete: string;
|
stepIdToDelete: string;
|
||||||
}): Promise<WorkflowActionDTO> {
|
}): Promise<WorkflowActionDTO> {
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersion = await workflowVersionRepository.findOne({
|
const workflowVersion = await workflowVersionRepository.findOne({
|
||||||
@ -279,9 +284,10 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
response: object;
|
response: object;
|
||||||
}) {
|
}) {
|
||||||
const workflowRun =
|
const workflowRun =
|
||||||
await this.workflowRunWorkspaceService.getWorkflowRunOrFail(
|
await this.workflowRunWorkspaceService.getWorkflowRunOrFail({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
);
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
const step = workflowRun.output?.flow?.steps?.find(
|
const step = workflowRun.output?.flow?.steps?.find(
|
||||||
(step) => step.id === stepId,
|
(step) => step.id === stepId,
|
||||||
@ -302,6 +308,7 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const enrichedResponse = await this.enrichFormStepResponse({
|
const enrichedResponse = await this.enrichFormStepResponse({
|
||||||
|
workspaceId,
|
||||||
step,
|
step,
|
||||||
response,
|
response,
|
||||||
});
|
});
|
||||||
@ -319,6 +326,7 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
||||||
|
workspaceId,
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
stepOutput: newStepOutput,
|
stepOutput: newStepOutput,
|
||||||
context: updatedContext,
|
context: updatedContext,
|
||||||
@ -554,9 +562,11 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async enrichFormStepResponse({
|
private async enrichFormStepResponse({
|
||||||
|
workspaceId,
|
||||||
step,
|
step,
|
||||||
response,
|
response,
|
||||||
}: {
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
step: WorkflowFormAction;
|
step: WorkflowFormAction;
|
||||||
response: object;
|
response: object;
|
||||||
}) {
|
}) {
|
||||||
@ -580,9 +590,11 @@ export class WorkflowVersionStepWorkspaceService {
|
|||||||
// @ts-expect-error legacy noImplicitAny
|
// @ts-expect-error legacy noImplicitAny
|
||||||
isValidUuid(response[key].id)
|
isValidUuid(response[key].id)
|
||||||
) {
|
) {
|
||||||
const repository = await this.twentyORMManager.getRepository(
|
const repository =
|
||||||
field.settings.objectName,
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
);
|
workspaceId,
|
||||||
|
field.settings.objectName,
|
||||||
|
);
|
||||||
|
|
||||||
const record = await repository.findOne({
|
const record = await repository.findOne({
|
||||||
// @ts-expect-error legacy noImplicitAny
|
// @ts-expect-error legacy noImplicitAny
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { Repository } from 'typeorm';
|
|||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStepException,
|
WorkflowVersionStepException,
|
||||||
@ -26,7 +26,7 @@ import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowVersionWorkspaceService {
|
export class WorkflowVersionWorkspaceService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workflowVersionStepWorkspaceService: WorkflowVersionStepWorkspaceService,
|
private readonly workflowVersionStepWorkspaceService: WorkflowVersionStepWorkspaceService,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
@ -44,8 +44,10 @@ export class WorkflowVersionWorkspaceService {
|
|||||||
workflowVersionIdToCopy: string;
|
workflowVersionIdToCopy: string;
|
||||||
}) {
|
}) {
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersionToCopy = await workflowVersionRepository.findOne({
|
const workflowVersionToCopy = await workflowVersionRepository.findOne({
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { RecordInputTransformerService } from 'src/engine/core-modules/record-tr
|
|||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import {
|
import {
|
||||||
@ -31,7 +31,7 @@ import { WorkflowCreateRecordActionInput } from 'src/modules/workflow/workflow-e
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateRecordWorkflowAction implements WorkflowExecutor {
|
export class CreateRecordWorkflowAction implements WorkflowExecutor {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
@ -76,9 +76,12 @@ export class CreateRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
context,
|
context,
|
||||||
) as WorkflowCreateRecordActionInput;
|
) as WorkflowCreateRecordActionInput;
|
||||||
|
|
||||||
const repository = await this.twentyORMManager.getRepository(
|
const repository =
|
||||||
workflowActionInput.objectName,
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
);
|
workspaceId,
|
||||||
|
workflowActionInput.objectName,
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
|
);
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfa
|
|||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import {
|
import {
|
||||||
WorkflowStepExecutorException,
|
WorkflowStepExecutorException,
|
||||||
@ -29,7 +29,7 @@ import { WorkflowDeleteRecordActionInput } from 'src/modules/workflow/workflow-e
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteRecordWorkflowAction implements WorkflowExecutor {
|
export class DeleteRecordWorkflowAction implements WorkflowExecutor {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
@ -72,10 +72,6 @@ export class DeleteRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const repository = await this.twentyORMManager.getRepository(
|
|
||||||
workflowActionInput.objectName,
|
|
||||||
);
|
|
||||||
|
|
||||||
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
|
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
|
||||||
|
|
||||||
if (!workspaceId) {
|
if (!workspaceId) {
|
||||||
@ -85,6 +81,13 @@ export class DeleteRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const repository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
workflowActionInput.objectName,
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
|
);
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
nameSingular: workflowActionInput.objectName,
|
nameSingular: workflowActionInput.objectName,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
|
|||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import {
|
import {
|
||||||
@ -36,7 +36,7 @@ import { WorkflowFindRecordsActionInput } from 'src/modules/workflow/workflow-ex
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class FindRecordsWorkflowAction implements WorkflowExecutor {
|
export class FindRecordsWorkflowAction implements WorkflowExecutor {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||||
) {}
|
) {}
|
||||||
@ -67,10 +67,6 @@ export class FindRecordsWorkflowAction implements WorkflowExecutor {
|
|||||||
context,
|
context,
|
||||||
) as WorkflowFindRecordsActionInput;
|
) as WorkflowFindRecordsActionInput;
|
||||||
|
|
||||||
const repository = await this.twentyORMManager.getRepository(
|
|
||||||
workflowActionInput.objectName,
|
|
||||||
);
|
|
||||||
|
|
||||||
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
|
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
|
||||||
|
|
||||||
if (!workspaceId) {
|
if (!workspaceId) {
|
||||||
@ -80,6 +76,13 @@ export class FindRecordsWorkflowAction implements WorkflowExecutor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const repository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
workflowActionInput.objectName,
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
|
);
|
||||||
|
|
||||||
const { objectMetadataItemWithFieldsMaps, objectMetadataMaps } =
|
const { objectMetadataItemWithFieldsMaps, objectMetadataMaps } =
|
||||||
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
|
||||||
workflowActionInput.objectName,
|
workflowActionInput.objectName,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter
|
|||||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
@ -33,7 +33,7 @@ import { WorkflowUpdateRecordActionInput } from 'src/modules/workflow/workflow-e
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class UpdateRecordWorkflowAction implements WorkflowExecutor {
|
export class UpdateRecordWorkflowAction implements WorkflowExecutor {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
@ -79,10 +79,6 @@ export class UpdateRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const repository = await this.twentyORMManager.getRepository(
|
|
||||||
workflowActionInput.objectName,
|
|
||||||
);
|
|
||||||
|
|
||||||
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
|
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
|
||||||
|
|
||||||
if (!workspaceId) {
|
if (!workspaceId) {
|
||||||
@ -92,6 +88,13 @@ export class UpdateRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const repository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
workflowActionInput.objectName,
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
|
);
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
nameSingular: workflowActionInput.objectName,
|
nameSingular: workflowActionInput.objectName,
|
||||||
|
|||||||
@ -18,7 +18,6 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
let service: WorkflowExecutorWorkspaceService;
|
let service: WorkflowExecutorWorkspaceService;
|
||||||
let workflowExecutorFactory: WorkflowExecutorFactory;
|
let workflowExecutorFactory: WorkflowExecutorFactory;
|
||||||
let workspaceEventEmitter: WorkspaceEventEmitter;
|
let workspaceEventEmitter: WorkspaceEventEmitter;
|
||||||
let scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory;
|
|
||||||
let workflowRunWorkspaceService: WorkflowRunWorkspaceService;
|
let workflowRunWorkspaceService: WorkflowRunWorkspaceService;
|
||||||
|
|
||||||
const mockWorkflowExecutor = {
|
const mockWorkflowExecutor = {
|
||||||
@ -86,9 +85,6 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
workspaceEventEmitter = module.get<WorkspaceEventEmitter>(
|
workspaceEventEmitter = module.get<WorkspaceEventEmitter>(
|
||||||
WorkspaceEventEmitter,
|
WorkspaceEventEmitter,
|
||||||
);
|
);
|
||||||
scopedWorkspaceContextFactory = module.get<ScopedWorkspaceContextFactory>(
|
|
||||||
ScopedWorkspaceContextFactory,
|
|
||||||
);
|
|
||||||
workflowRunWorkspaceService = module.get<WorkflowRunWorkspaceService>(
|
workflowRunWorkspaceService = module.get<WorkflowRunWorkspaceService>(
|
||||||
WorkflowRunWorkspaceService,
|
WorkflowRunWorkspaceService,
|
||||||
);
|
);
|
||||||
@ -185,6 +181,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
data: 'some-data',
|
data: 'some-data',
|
||||||
'step-1': { stepOutput: 'success' },
|
'step-1': { stepOutput: 'success' },
|
||||||
},
|
},
|
||||||
|
workspaceId: 'workspace-id',
|
||||||
});
|
});
|
||||||
expect(result).toEqual({ result: { success: true } });
|
expect(result).toEqual({ result: { success: true } });
|
||||||
|
|
||||||
@ -221,6 +218,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
|
workspaceId: 'workspace-id',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -248,6 +246,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
output: mockPendingEvent,
|
output: mockPendingEvent,
|
||||||
},
|
},
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
|
workspaceId: 'workspace-id',
|
||||||
});
|
});
|
||||||
|
|
||||||
// No recursive call to execute should happen
|
// No recursive call to execute should happen
|
||||||
@ -304,6 +303,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
|
workspaceId: 'workspace-id',
|
||||||
});
|
});
|
||||||
expect(result).toEqual({ result: { success: true } });
|
expect(result).toEqual({ result: { success: true } });
|
||||||
|
|
||||||
@ -387,6 +387,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
output: errorOutput,
|
output: errorOutput,
|
||||||
},
|
},
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
|
workspaceId: 'workspace-id',
|
||||||
});
|
});
|
||||||
expect(result).toEqual(errorOutput);
|
expect(result).toEqual(errorOutput);
|
||||||
});
|
});
|
||||||
@ -414,6 +415,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
context: mockContext,
|
context: mockContext,
|
||||||
|
workspaceId: 'workspace-id',
|
||||||
});
|
});
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
error: BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE,
|
error: BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE,
|
||||||
@ -423,9 +425,8 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
|
|
||||||
describe('sendWorkflowNodeRunEvent', () => {
|
describe('sendWorkflowNodeRunEvent', () => {
|
||||||
it('should emit a billing event', () => {
|
it('should emit a billing event', () => {
|
||||||
service['sendWorkflowNodeRunEvent']();
|
service['sendWorkflowNodeRunEvent']('workspace-id');
|
||||||
|
|
||||||
expect(scopedWorkspaceContextFactory.create).toHaveBeenCalled();
|
|
||||||
expect(workspaceEventEmitter.emitCustomBatchEvent).toHaveBeenCalledWith(
|
expect(workspaceEventEmitter.emitCustomBatchEvent).toHaveBeenCalledWith(
|
||||||
BILLING_FEATURE_USED,
|
BILLING_FEATURE_USED,
|
||||||
[
|
[
|
||||||
@ -437,24 +438,5 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
|||||||
'workspace-id',
|
'workspace-id',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle missing workspace ID', () => {
|
|
||||||
mockScopedWorkspaceContextFactory.create.mockReturnValueOnce({
|
|
||||||
workspaceId: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
service['sendWorkflowNodeRunEvent']();
|
|
||||||
|
|
||||||
expect(workspaceEventEmitter.emitCustomBatchEvent).toHaveBeenCalledWith(
|
|
||||||
BILLING_FEATURE_USED,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
eventName: BillingMeterEventName.WORKFLOW_NODE_RUN,
|
|
||||||
value: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -21,6 +21,10 @@ import { WorkflowExecutorFactory } from 'src/modules/workflow/workflow-executor/
|
|||||||
import { WorkflowExecutorInput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-input';
|
import { WorkflowExecutorInput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-input';
|
||||||
import { WorkflowExecutorOutput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-output.type';
|
import { WorkflowExecutorOutput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-output.type';
|
||||||
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
|
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
|
||||||
|
import {
|
||||||
|
WorkflowTriggerException,
|
||||||
|
WorkflowTriggerExceptionCode,
|
||||||
|
} from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception';
|
||||||
|
|
||||||
const MAX_RETRIES_ON_FAILURE = 3;
|
const MAX_RETRIES_ON_FAILURE = 3;
|
||||||
|
|
||||||
@ -58,15 +62,25 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
|
|
||||||
let actionOutput: WorkflowExecutorOutput;
|
let actionOutput: WorkflowExecutorOutput;
|
||||||
|
|
||||||
|
const { workspaceId } = this.scopedWorkspaceContextFactory.create();
|
||||||
|
|
||||||
|
if (!workspaceId) {
|
||||||
|
throw new WorkflowTriggerException(
|
||||||
|
'No workspace id found',
|
||||||
|
WorkflowTriggerExceptionCode.INTERNAL_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.billingService.isBillingEnabled() &&
|
this.billingService.isBillingEnabled() &&
|
||||||
!(await this.canBillWorkflowNodeExecution())
|
!(await this.canBillWorkflowNodeExecution(workspaceId))
|
||||||
) {
|
) {
|
||||||
const billingOutput = {
|
const billingOutput = {
|
||||||
error: BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE,
|
error: BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
||||||
|
workspaceId,
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
stepOutput: {
|
stepOutput: {
|
||||||
id: step.id,
|
id: step.id,
|
||||||
@ -93,7 +107,7 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!actionOutput.error) {
|
if (!actionOutput.error) {
|
||||||
this.sendWorkflowNodeRunEvent();
|
this.sendWorkflowNodeRunEvent(workspaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stepOutput: StepOutput = {
|
const stepOutput: StepOutput = {
|
||||||
@ -106,6 +120,7 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
workflowRunId,
|
workflowRunId,
|
||||||
stepOutput,
|
stepOutput,
|
||||||
context,
|
context,
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return actionOutput;
|
return actionOutput;
|
||||||
@ -127,6 +142,7 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
workflowRunId,
|
workflowRunId,
|
||||||
stepOutput,
|
stepOutput,
|
||||||
context: updatedContext,
|
context: updatedContext,
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isDefined(step.nextStepIds?.[0])) {
|
if (!isDefined(step.nextStepIds?.[0])) {
|
||||||
@ -159,15 +175,13 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
workflowRunId,
|
workflowRunId,
|
||||||
stepOutput,
|
stepOutput,
|
||||||
context,
|
context,
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return actionOutput;
|
return actionOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendWorkflowNodeRunEvent() {
|
private sendWorkflowNodeRunEvent(workspaceId: string) {
|
||||||
const workspaceId =
|
|
||||||
this.scopedWorkspaceContextFactory.create().workspaceId ?? '';
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitCustomBatchEvent<BillingUsageEvent>(
|
this.workspaceEventEmitter.emitCustomBatchEvent<BillingUsageEvent>(
|
||||||
BILLING_FEATURE_USED,
|
BILLING_FEATURE_USED,
|
||||||
[
|
[
|
||||||
@ -180,10 +194,7 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async canBillWorkflowNodeExecution() {
|
private async canBillWorkflowNodeExecution(workspaceId: string) {
|
||||||
const workspaceId =
|
|
||||||
this.scopedWorkspaceContextFactory.create().workspaceId ?? '';
|
|
||||||
|
|
||||||
return this.billingService.canBillMeteredProduct(
|
return this.billingService.canBillMeteredProduct(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
BillingProductKey.WORKFLOW_NODE_EXECUTION,
|
BillingProductKey.WORKFLOW_NODE_EXECUTION,
|
||||||
|
|||||||
@ -37,21 +37,25 @@ export class RunWorkflowJob {
|
|||||||
workflowRunId,
|
workflowRunId,
|
||||||
payload,
|
payload,
|
||||||
lastExecutedStepId,
|
lastExecutedStepId,
|
||||||
|
workspaceId,
|
||||||
}: RunWorkflowJobData): Promise<void> {
|
}: RunWorkflowJobData): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (lastExecutedStepId) {
|
if (lastExecutedStepId) {
|
||||||
await this.resumeWorkflowExecution({
|
await this.resumeWorkflowExecution({
|
||||||
|
workspaceId,
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
lastExecutedStepId,
|
lastExecutedStepId,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.startWorkflowExecution({
|
await this.startWorkflowExecution({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
|
workspaceId,
|
||||||
payload: payload ?? {},
|
payload: payload ?? {},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.workflowRunWorkspaceService.endWorkflowRun({
|
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||||
|
workspaceId,
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
status: WorkflowRunStatus.FAILED,
|
status: WorkflowRunStatus.FAILED,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
@ -61,9 +65,11 @@ export class RunWorkflowJob {
|
|||||||
|
|
||||||
private async startWorkflowExecution({
|
private async startWorkflowExecution({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
|
workspaceId,
|
||||||
payload,
|
payload,
|
||||||
}: {
|
}: {
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
|
workspaceId: string;
|
||||||
payload: object;
|
payload: object;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const context = {
|
const context = {
|
||||||
@ -71,14 +77,16 @@ export class RunWorkflowJob {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const workflowRun =
|
const workflowRun =
|
||||||
await this.workflowRunWorkspaceService.getWorkflowRunOrFail(
|
await this.workflowRunWorkspaceService.getWorkflowRunOrFail({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
);
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
const workflowVersion =
|
const workflowVersion =
|
||||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
|
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||||
workflowRun.workflowVersionId,
|
workspaceId,
|
||||||
);
|
workflowVersionId: workflowRun.workflowVersionId,
|
||||||
|
});
|
||||||
|
|
||||||
if (!workflowVersion.trigger || !workflowVersion.steps) {
|
if (!workflowVersion.trigger || !workflowVersion.steps) {
|
||||||
throw new WorkflowRunException(
|
throw new WorkflowRunException(
|
||||||
@ -89,6 +97,7 @@ export class RunWorkflowJob {
|
|||||||
|
|
||||||
await this.workflowRunWorkspaceService.startWorkflowRun({
|
await this.workflowRunWorkspaceService.startWorkflowRun({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
|
workspaceId,
|
||||||
context,
|
context,
|
||||||
output: {
|
output: {
|
||||||
flow: {
|
flow: {
|
||||||
@ -110,20 +119,24 @@ export class RunWorkflowJob {
|
|||||||
currentStepId: workflowVersion.steps[0].id,
|
currentStepId: workflowVersion.steps[0].id,
|
||||||
steps: workflowVersion.steps,
|
steps: workflowVersion.steps,
|
||||||
context,
|
context,
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resumeWorkflowExecution({
|
private async resumeWorkflowExecution({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
lastExecutedStepId,
|
lastExecutedStepId,
|
||||||
|
workspaceId,
|
||||||
}: {
|
}: {
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
lastExecutedStepId: string;
|
lastExecutedStepId: string;
|
||||||
|
workspaceId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const workflowRun =
|
const workflowRun =
|
||||||
await this.workflowRunWorkspaceService.getWorkflowRunOrFail(
|
await this.workflowRunWorkspaceService.getWorkflowRunOrFail({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
);
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
if (workflowRun.status !== WorkflowRunStatus.RUNNING) {
|
if (workflowRun.status !== WorkflowRunStatus.RUNNING) {
|
||||||
throw new WorkflowRunException(
|
throw new WorkflowRunException(
|
||||||
@ -148,6 +161,7 @@ export class RunWorkflowJob {
|
|||||||
if (!nextStepId) {
|
if (!nextStepId) {
|
||||||
await this.workflowRunWorkspaceService.endWorkflowRun({
|
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
|
workspaceId,
|
||||||
status: WorkflowRunStatus.COMPLETED,
|
status: WorkflowRunStatus.COMPLETED,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -159,6 +173,7 @@ export class RunWorkflowJob {
|
|||||||
currentStepId: nextStepId,
|
currentStepId: nextStepId,
|
||||||
steps: workflowRun.output?.flow?.steps ?? [],
|
steps: workflowRun.output?.flow?.steps ?? [],
|
||||||
context: workflowRun.context ?? {},
|
context: workflowRun.context ?? {},
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,12 +182,14 @@ export class RunWorkflowJob {
|
|||||||
currentStepId,
|
currentStepId,
|
||||||
steps,
|
steps,
|
||||||
context,
|
context,
|
||||||
|
workspaceId,
|
||||||
}: {
|
}: {
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
currentStepId: string;
|
currentStepId: string;
|
||||||
steps: WorkflowAction[];
|
steps: WorkflowAction[];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
context: Record<string, any>;
|
context: Record<string, any>;
|
||||||
|
workspaceId: string;
|
||||||
}) {
|
}) {
|
||||||
const { error, pendingEvent } =
|
const { error, pendingEvent } =
|
||||||
await this.workflowExecutorWorkspaceService.execute({
|
await this.workflowExecutorWorkspaceService.execute({
|
||||||
@ -188,6 +205,7 @@ export class RunWorkflowJob {
|
|||||||
|
|
||||||
await this.workflowRunWorkspaceService.endWorkflowRun({
|
await this.workflowRunWorkspaceService.endWorkflowRun({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
|
workspaceId,
|
||||||
status: error ? WorkflowRunStatus.FAILED : WorkflowRunStatus.COMPLETED,
|
status: error ? WorkflowRunStatus.FAILED : WorkflowRunStatus.COMPLETED,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { RecordPositionService } from 'src/engine/core-modules/record-position/s
|
|||||||
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import {
|
import {
|
||||||
StepOutput,
|
StepOutput,
|
||||||
@ -17,7 +17,6 @@ import {
|
|||||||
WorkflowRunStatus,
|
WorkflowRunStatus,
|
||||||
WorkflowRunWorkspaceEntity,
|
WorkflowRunWorkspaceEntity,
|
||||||
} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
||||||
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||||
import {
|
import {
|
||||||
@ -28,7 +27,7 @@ import {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowRunWorkspaceService {
|
export class WorkflowRunWorkspaceService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
@ -44,19 +43,34 @@ export class WorkflowRunWorkspaceService {
|
|||||||
workflowVersionId: string;
|
workflowVersionId: string;
|
||||||
createdBy: ActorMetadata;
|
createdBy: ActorMetadata;
|
||||||
}) {
|
}) {
|
||||||
|
const workspaceId =
|
||||||
|
this.scopedWorkspaceContextFactory.create()?.workspaceId;
|
||||||
|
|
||||||
|
if (!workspaceId) {
|
||||||
|
throw new WorkflowRunException(
|
||||||
|
'Workspace id is invalid',
|
||||||
|
WorkflowRunExceptionCode.WORKFLOW_RUN_INVALID,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const workflowRunRepository =
|
const workflowRunRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
'workflowRun',
|
'workflowRun',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersion =
|
const workflowVersion =
|
||||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
|
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||||
|
workspaceId,
|
||||||
workflowVersionId,
|
workflowVersionId,
|
||||||
);
|
});
|
||||||
|
|
||||||
const workflowRepository =
|
const workflowRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
'workflow',
|
'workflow',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflow = await workflowRepository.findOne({
|
const workflow = await workflowRepository.findOne({
|
||||||
@ -78,16 +92,6 @@ export class WorkflowRunWorkspaceService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspaceId =
|
|
||||||
this.scopedWorkspaceContextFactory.create()?.workspaceId;
|
|
||||||
|
|
||||||
if (!workspaceId) {
|
|
||||||
throw new WorkflowRunException(
|
|
||||||
'Workspace id is invalid',
|
|
||||||
WorkflowRunExceptionCode.WORKFLOW_RUN_INVALID,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const position = await this.recordPositionService.buildRecordPosition({
|
const position = await this.recordPositionService.buildRecordPosition({
|
||||||
value: 'first',
|
value: 'first',
|
||||||
objectMetadata: {
|
objectMetadata: {
|
||||||
@ -111,17 +115,21 @@ export class WorkflowRunWorkspaceService {
|
|||||||
|
|
||||||
async startWorkflowRun({
|
async startWorkflowRun({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
|
workspaceId,
|
||||||
context,
|
context,
|
||||||
output,
|
output,
|
||||||
}: {
|
}: {
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
|
workspaceId: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
context: Record<string, any>;
|
context: Record<string, any>;
|
||||||
output: WorkflowRunOutput;
|
output: WorkflowRunOutput;
|
||||||
}) {
|
}) {
|
||||||
const workflowRunRepository =
|
const workflowRunRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowRunWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowRun',
|
'workflowRun',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
||||||
@ -159,16 +167,20 @@ export class WorkflowRunWorkspaceService {
|
|||||||
|
|
||||||
async endWorkflowRun({
|
async endWorkflowRun({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
|
workspaceId,
|
||||||
status,
|
status,
|
||||||
error,
|
error,
|
||||||
}: {
|
}: {
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
|
workspaceId: string;
|
||||||
status: WorkflowRunStatus;
|
status: WorkflowRunStatus;
|
||||||
error?: string;
|
error?: string;
|
||||||
}) {
|
}) {
|
||||||
const workflowRunRepository =
|
const workflowRunRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowRunWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowRun',
|
'workflowRun',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
||||||
@ -202,16 +214,20 @@ export class WorkflowRunWorkspaceService {
|
|||||||
async saveWorkflowRunState({
|
async saveWorkflowRunState({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
stepOutput,
|
stepOutput,
|
||||||
|
workspaceId,
|
||||||
context,
|
context,
|
||||||
}: {
|
}: {
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
stepOutput: StepOutput;
|
stepOutput: StepOutput;
|
||||||
|
workspaceId: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
context: Record<string, any>;
|
context: Record<string, any>;
|
||||||
}) {
|
}) {
|
||||||
const workflowRunRepository =
|
const workflowRunRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowRunWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowRun',
|
'workflowRun',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
||||||
@ -250,13 +266,17 @@ export class WorkflowRunWorkspaceService {
|
|||||||
async updateWorkflowRunStep({
|
async updateWorkflowRunStep({
|
||||||
workflowRunId,
|
workflowRunId,
|
||||||
step,
|
step,
|
||||||
|
workspaceId,
|
||||||
}: {
|
}: {
|
||||||
workflowRunId: string;
|
workflowRunId: string;
|
||||||
step: WorkflowAction;
|
step: WorkflowAction;
|
||||||
|
workspaceId: string;
|
||||||
}) {
|
}) {
|
||||||
const workflowRunRepository =
|
const workflowRunRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowRunWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowRun',
|
'workflowRun',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
const workflowRunToUpdate = await workflowRunRepository.findOneBy({
|
||||||
@ -302,12 +322,18 @@ export class WorkflowRunWorkspaceService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkflowRunOrFail(
|
async getWorkflowRunOrFail({
|
||||||
workflowRunId: string,
|
workflowRunId,
|
||||||
): Promise<WorkflowRunWorkspaceEntity> {
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
workflowRunId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<WorkflowRunWorkspaceEntity> {
|
||||||
const workflowRunRepository =
|
const workflowRunRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowRunWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowRun',
|
'workflowRun',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowRun = await workflowRunRepository.findOne({
|
const workflowRun = await workflowRunRepository.findOne({
|
||||||
@ -353,8 +379,10 @@ export class WorkflowRunWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workflowRunRepository =
|
const workflowRunRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowRunWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
'workflowRun',
|
'workflowRun',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowRunAfter = await workflowRunRepository.findOneBy({
|
const workflowRunAfter = await workflowRunRepository.findOneBy({
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
|||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||||
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||||
@ -28,17 +28,25 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
update: jest.fn(),
|
update: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockTwentyORMManager = {
|
const mockTwentyORMGlobalManager = {
|
||||||
getRepository: jest.fn().mockImplementation((entity) => {
|
getRepositoryForWorkspace: jest
|
||||||
if (entity === 'workflow') {
|
.fn()
|
||||||
return Promise.resolve(mockWorkflowRepository);
|
.mockImplementation((_workspaceId, entity, options) => {
|
||||||
}
|
if (!options?.shouldBypassPermissionChecks) {
|
||||||
if (entity === 'workflowVersion') {
|
throw new Error(
|
||||||
return Promise.resolve(mockWorkflowVersionRepository);
|
'Permission check will fail because job runners dont have permissions',
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve(null);
|
if (entity === 'workflow') {
|
||||||
}),
|
return Promise.resolve(mockWorkflowRepository);
|
||||||
|
}
|
||||||
|
if (entity === 'workflowVersion') {
|
||||||
|
return Promise.resolve(mockWorkflowVersionRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockServerlessFunctionService = {
|
const mockServerlessFunctionService = {
|
||||||
@ -55,8 +63,8 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
WorkflowStatusesUpdateJob,
|
WorkflowStatusesUpdateJob,
|
||||||
{
|
{
|
||||||
provide: TwentyORMManager,
|
provide: TwentyORMGlobalManager,
|
||||||
useValue: mockTwentyORMManager,
|
useValue: mockTwentyORMGlobalManager,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: ServerlessFunctionService,
|
provide: ServerlessFunctionService,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless
|
|||||||
import { ServerlessFunctionExceptionCode } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
import { ServerlessFunctionExceptionCode } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStatus,
|
WorkflowVersionStatus,
|
||||||
@ -71,7 +71,7 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
protected readonly logger = new Logger(WorkflowStatusesUpdateJob.name);
|
protected readonly logger = new Logger(WorkflowStatusesUpdateJob.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||||
@ -128,13 +128,17 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const workflowRepository =
|
const workflowRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflow',
|
'workflow',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const newWorkflowStatuses = await this.getWorkflowStatuses({
|
const newWorkflowStatuses = await this.getWorkflowStatuses({
|
||||||
@ -248,13 +252,17 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const workflowRepository =
|
const workflowRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflow',
|
'workflow',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflow = await workflowRepository.findOneOrFail({
|
const workflow = await workflowRepository.findOneOrFail({
|
||||||
|
|||||||
@ -206,6 +206,7 @@ export class DatabaseEventTriggerListener {
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
relatedObjectMetadataNameSingular,
|
relatedObjectMetadataNameSingular,
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
record[joinField.name] = await relatedObjectRepository.findOne({
|
record[joinField.name] = await relatedObjectRepository.findOne({
|
||||||
@ -254,6 +255,7 @@ export class DatabaseEventTriggerListener {
|
|||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowAutomatedTriggerWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowAutomatedTriggerWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
automatedTriggerTableName,
|
automatedTriggerTableName,
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const eventListeners = await workflowAutomatedTriggerRepository.find({
|
const eventListeners = await workflowAutomatedTriggerRepository.find({
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu
|
|||||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||||
import { handleWorkflowTriggerException } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter';
|
import { handleWorkflowTriggerException } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter';
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStatus,
|
WorkflowVersionStatus,
|
||||||
WorkflowVersionWorkspaceEntity,
|
WorkflowVersionWorkspaceEntity,
|
||||||
@ -33,7 +33,7 @@ const DEFAULT_WORKFLOW_NAME = 'Workflow';
|
|||||||
@Processor({ queueName: MessageQueue.workflowQueue, scope: Scope.REQUEST })
|
@Processor({ queueName: MessageQueue.workflowQueue, scope: Scope.REQUEST })
|
||||||
export class WorkflowTriggerJob {
|
export class WorkflowTriggerJob {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
|
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
|
||||||
@InjectMessageQueue(MessageQueue.workflowQueue)
|
@InjectMessageQueue(MessageQueue.workflowQueue)
|
||||||
private readonly messageQueueService: MessageQueueService,
|
private readonly messageQueueService: MessageQueueService,
|
||||||
@ -43,8 +43,10 @@ export class WorkflowTriggerJob {
|
|||||||
async handle(data: WorkflowTriggerJobData): Promise<void> {
|
async handle(data: WorkflowTriggerJobData): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const workflowRepository =
|
const workflowRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowWorkspaceEntity>(
|
||||||
|
data.workspaceId,
|
||||||
'workflow',
|
'workflow',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflow = await workflowRepository.findOneBy({
|
const workflow = await workflowRepository.findOneBy({
|
||||||
@ -66,8 +68,10 @@ export class WorkflowTriggerJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
data.workspaceId,
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersion = await workflowVersionRepository.findOneBy({
|
const workflowVersion = await workflowVersionRepository.findOneBy({
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import { AutomatedTriggerType } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity';
|
import { AutomatedTriggerType } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity';
|
||||||
import {
|
import {
|
||||||
@ -36,7 +36,7 @@ import { assertNever } from 'src/utils/assert';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowTriggerWorkspaceService {
|
export class WorkflowTriggerWorkspaceService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
|
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
|
||||||
@ -68,9 +68,10 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
payload: object;
|
payload: object;
|
||||||
createdBy: ActorMetadata;
|
createdBy: ActorMetadata;
|
||||||
}) {
|
}) {
|
||||||
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail(
|
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
|
||||||
workflowVersionId,
|
workflowVersionId,
|
||||||
);
|
workspaceId: this.getWorkspaceId(),
|
||||||
|
});
|
||||||
|
|
||||||
return this.workflowRunnerWorkspaceService.run({
|
return this.workflowRunnerWorkspaceService.run({
|
||||||
workspaceId: this.getWorkspaceId(),
|
workspaceId: this.getWorkspaceId(),
|
||||||
@ -81,9 +82,12 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async activateWorkflowVersion(workflowVersionId: string) {
|
async activateWorkflowVersion(workflowVersionId: string) {
|
||||||
|
const workspaceId = this.getWorkspaceId();
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
this.getWorkspaceId(),
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflowVersionNullable = await workflowVersionRepository.findOne({
|
const workflowVersionNullable = await workflowVersionRepository.findOne({
|
||||||
@ -96,8 +100,10 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const workflowRepository =
|
const workflowRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowWorkspaceEntity>(
|
||||||
|
workspaceId,
|
||||||
'workflow',
|
'workflow',
|
||||||
|
{ shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level
|
||||||
);
|
);
|
||||||
|
|
||||||
const workflow = await workflowRepository.findOne({
|
const workflow = await workflowRepository.findOne({
|
||||||
@ -113,7 +119,10 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
|
|
||||||
assertVersionCanBeActivated(workflowVersion, workflow);
|
assertVersionCanBeActivated(workflowVersion, workflow);
|
||||||
|
|
||||||
const workspaceDataSource = await this.twentyORMManager.getDatasource();
|
const workspaceDataSource =
|
||||||
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||||
|
workspaceId: this.getWorkspaceId(),
|
||||||
|
});
|
||||||
const queryRunner = workspaceDataSource.createQueryRunner();
|
const queryRunner = workspaceDataSource.createQueryRunner();
|
||||||
|
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
@ -142,7 +151,10 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deactivateWorkflowVersion(workflowVersionId: string) {
|
async deactivateWorkflowVersion(workflowVersionId: string) {
|
||||||
const workspaceDataSource = await this.twentyORMManager.getDatasource();
|
const workspaceDataSource =
|
||||||
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||||
|
workspaceId: this.getWorkspaceId(),
|
||||||
|
});
|
||||||
const queryRunner = workspaceDataSource.createQueryRunner();
|
const queryRunner = workspaceDataSource.createQueryRunner();
|
||||||
|
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
@ -150,8 +162,10 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const workflowVersionRepository =
|
const workflowVersionRepository =
|
||||||
await this.twentyORMManager.getRepository<WorkflowVersionWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
|
||||||
|
this.getWorkspaceId(),
|
||||||
'workflowVersion',
|
'workflowVersion',
|
||||||
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.performDeactivationSteps(
|
await this.performDeactivationSteps(
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
export const WORKFLOW_GQL_FIELDS = `
|
||||||
|
id
|
||||||
|
name
|
||||||
|
lastPublishedVersionId
|
||||||
|
statuses
|
||||||
|
position
|
||||||
|
createdBy {
|
||||||
|
source
|
||||||
|
workspaceMemberId
|
||||||
|
name
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
deletedAt
|
||||||
|
`;
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { print } from 'graphql';
|
import { print } from 'graphql';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { deleteOneRoleOperationFactory } from 'test/integration/graphql/utils/delete-one-role-operation-factory.util';
|
import { deleteOneRoleOperationFactory } from 'test/integration/graphql/utils/delete-one-role-operation-factory.util';
|
||||||
|
import { destroyOneOperationFactory } from 'test/integration/graphql/utils/destroy-one-operation-factory.util';
|
||||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||||
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
|
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
|
||||||
import { updateWorkspaceMemberRole } from 'test/integration/graphql/utils/update-workspace-member-role.util';
|
import { updateWorkspaceMemberRole } from 'test/integration/graphql/utils/update-workspace-member-role.util';
|
||||||
@ -84,7 +85,7 @@ describe('Granular settings permissions', () => {
|
|||||||
mutation UpsertSettingPermissions {
|
mutation UpsertSettingPermissions {
|
||||||
upsertSettingPermissions(upsertSettingPermissionsInput: {
|
upsertSettingPermissions(upsertSettingPermissionsInput: {
|
||||||
roleId: "${customRoleId}"
|
roleId: "${customRoleId}"
|
||||||
settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}, ${SettingPermissionType.WORKSPACE}]
|
settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}, ${SettingPermissionType.WORKSPACE}, ${SettingPermissionType.WORKFLOWS}]
|
||||||
}) {
|
}) {
|
||||||
id
|
id
|
||||||
setting
|
setting
|
||||||
@ -243,6 +244,48 @@ describe('Granular settings permissions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Workflows Permissions', () => {
|
||||||
|
it('should allow access to workflows operations when user has WORKFLOWS setting permission', async () => {
|
||||||
|
// Test creating a workflow (requires WORKFLOWS permission)
|
||||||
|
const createWorkflowQuery = {
|
||||||
|
query: `
|
||||||
|
mutation CreateWorkflow {
|
||||||
|
createWorkflow(data: {
|
||||||
|
name: "Test Workflow"
|
||||||
|
}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send(createWorkflowQuery);
|
||||||
|
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body.errors).toBeUndefined();
|
||||||
|
expect(response.body.data.createWorkflow).toBeDefined();
|
||||||
|
expect(response.body.data.createWorkflow.name).toBe('Test Workflow');
|
||||||
|
|
||||||
|
// Clean up - delete the created workflow
|
||||||
|
const graphqlOperation = destroyOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: `
|
||||||
|
id
|
||||||
|
`,
|
||||||
|
recordId: response.body.data.createWorkflow.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
|
||||||
|
.send(graphqlOperation);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Denied Permissions', () => {
|
describe('Denied Permissions', () => {
|
||||||
it('should deny access to roles operations when user does not have ROLES setting permission', async () => {
|
it('should deny access to roles operations when user does not have ROLES setting permission', async () => {
|
||||||
// Test creating a role (requires ROLES permission, which our custom role doesn't have)
|
// Test creating a role (requires ROLES permission, which our custom role doesn't have)
|
||||||
@ -354,7 +397,7 @@ describe('Granular settings permissions', () => {
|
|||||||
|
|
||||||
expect(customRole).toBeDefined();
|
expect(customRole).toBeDefined();
|
||||||
expect(customRole.canUpdateAllSettings).toBe(false);
|
expect(customRole.canUpdateAllSettings).toBe(false);
|
||||||
expect(customRole.settingPermissions).toHaveLength(2);
|
expect(customRole.settingPermissions).toHaveLength(3);
|
||||||
expect(
|
expect(
|
||||||
customRole.settingPermissions.map((p: any) => p.setting),
|
customRole.settingPermissions.map((p: any) => p.setting),
|
||||||
).toContain(SettingPermissionType.DATA_MODEL);
|
).toContain(SettingPermissionType.DATA_MODEL);
|
||||||
@ -0,0 +1,370 @@
|
|||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
|
||||||
|
import { WORKFLOW_GQL_FIELDS } from 'test/integration/constants/workflow-gql-fields.constants';
|
||||||
|
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
|
||||||
|
import { destroyOneOperationFactory } from 'test/integration/graphql/utils/destroy-one-operation-factory.util';
|
||||||
|
import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util';
|
||||||
|
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
|
||||||
|
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||||
|
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
|
||||||
|
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
|
||||||
|
|
||||||
|
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||||
|
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
|
||||||
|
|
||||||
|
describe('workflowsPermissions', () => {
|
||||||
|
describe('createOne workflow', () => {
|
||||||
|
describe('permissions V2 disabled', () => {
|
||||||
|
it('should throw a permission error when user does not have permission (guest role)', async () => {
|
||||||
|
const workflowId = randomUUID();
|
||||||
|
const graphqlOperation = createOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
id: workflowId,
|
||||||
|
name: 'Test Workflow',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({ createWorkflow: null });
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(response.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a workflow when user has permission (admin role)', async () => {
|
||||||
|
const workflowId = randomUUID();
|
||||||
|
const graphqlOperation = createOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
id: workflowId,
|
||||||
|
name: 'Test Workflow Admin',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.createWorkflow).toBeDefined();
|
||||||
|
expect(response.body.data.createWorkflow.id).toBe(workflowId);
|
||||||
|
expect(response.body.data.createWorkflow.name).toBe(
|
||||||
|
'Test Workflow Admin',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up - delete the created workflow
|
||||||
|
const destroyWorkflowOperation = destroyOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: `
|
||||||
|
id
|
||||||
|
`,
|
||||||
|
recordId: response.body.data.createWorkflow.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(destroyWorkflowOperation);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('permissions V2 enabled', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
const enablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IS_PERMISSIONS_V2_ENABLED',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(enablePermissionsQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const disablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IS_PERMISSIONS_V2_ENABLED',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(disablePermissionsQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (guest role)', async () => {
|
||||||
|
const workflowId = randomUUID();
|
||||||
|
const graphqlOperation = createOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
id: workflowId,
|
||||||
|
name: 'Test Workflow V2',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({ createWorkflow: null });
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(response.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a workflow when user has permission (admin role)', async () => {
|
||||||
|
const workflowId = randomUUID();
|
||||||
|
const graphqlOperation = createOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
id: workflowId,
|
||||||
|
name: 'Test Workflow Admin',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.createWorkflow).toBeDefined();
|
||||||
|
expect(response.body.data.createWorkflow.id).toBe(workflowId);
|
||||||
|
expect(response.body.data.createWorkflow.name).toBe(
|
||||||
|
'Test Workflow Admin',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up - delete the created workflow
|
||||||
|
const destroyWorkflowOperation = destroyOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: `
|
||||||
|
id
|
||||||
|
`,
|
||||||
|
recordId: response.body.data.createWorkflow.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(destroyWorkflowOperation);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a workflow when executed by api key', async () => {
|
||||||
|
const workflowId = randomUUID();
|
||||||
|
const graphqlOperation = createOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
id: workflowId,
|
||||||
|
name: 'Test Workflow API Key',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithApiKey(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.createWorkflow).toBeDefined();
|
||||||
|
expect(response.body.data.createWorkflow.id).toBe(workflowId);
|
||||||
|
expect(response.body.data.createWorkflow.name).toBe(
|
||||||
|
'Test Workflow API Key',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up - delete the created workflow
|
||||||
|
const destroyWorkflowOperation = destroyOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: `
|
||||||
|
id
|
||||||
|
`,
|
||||||
|
recordId: response.body.data.createWorkflow.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(destroyWorkflowOperation);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateOne workflow', () => {
|
||||||
|
describe('permissions V2 disabled', () => {
|
||||||
|
const workflowId = randomUUID();
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const createWorkflowOperation = createOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
id: workflowId,
|
||||||
|
name: 'Original Workflow Name',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(createWorkflowOperation);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const destroyWorkflowOperation = destroyOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: `
|
||||||
|
id
|
||||||
|
`,
|
||||||
|
recordId: workflowId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(destroyWorkflowOperation);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (guest role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
recordId: workflowId,
|
||||||
|
data: {
|
||||||
|
name: 'Updated Workflow Name Guest',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({ updateWorkflow: null });
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(response.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a workflow when user has permission (admin role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
recordId: workflowId,
|
||||||
|
data: {
|
||||||
|
name: 'Updated Workflow Name Admin',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.updateWorkflow).toBeDefined();
|
||||||
|
expect(response.body.data.updateWorkflow.id).toBe(workflowId);
|
||||||
|
expect(response.body.data.updateWorkflow.name).toBe(
|
||||||
|
'Updated Workflow Name Admin',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('permissions V2 enabled', () => {
|
||||||
|
const workflowId = randomUUID();
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const createWorkflowOperation = createOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
id: workflowId,
|
||||||
|
name: 'Original Workflow V2',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(createWorkflowOperation);
|
||||||
|
|
||||||
|
const enablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IS_PERMISSIONS_V2_ENABLED',
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(enablePermissionsQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const destroyWorkflowOperation = destroyOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: `
|
||||||
|
id
|
||||||
|
`,
|
||||||
|
recordId: workflowId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(destroyWorkflowOperation);
|
||||||
|
|
||||||
|
const disablePermissionsQuery = updateFeatureFlagFactory(
|
||||||
|
SEED_APPLE_WORKSPACE_ID,
|
||||||
|
'IS_PERMISSIONS_V2_ENABLED',
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(disablePermissionsQuery);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a permission error when user does not have permission (guest role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
recordId: workflowId,
|
||||||
|
data: {
|
||||||
|
name: 'Updated Workflow V2 Guest',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({ updateWorkflow: null });
|
||||||
|
expect(response.body.errors).toBeDefined();
|
||||||
|
expect(response.body.errors[0].message).toBe(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
expect(response.body.errors[0].extensions.code).toBe(
|
||||||
|
ErrorCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a workflow when user has permission (admin role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
recordId: workflowId,
|
||||||
|
data: {
|
||||||
|
name: 'Updated Workflow V2 Admin',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await makeGraphqlAPIRequest(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.updateWorkflow).toBeDefined();
|
||||||
|
expect(response.body.data.updateWorkflow.id).toBe(workflowId);
|
||||||
|
expect(response.body.data.updateWorkflow.name).toBe(
|
||||||
|
'Updated Workflow V2 Admin',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a workflow when executed by api key', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workflow',
|
||||||
|
gqlFields: WORKFLOW_GQL_FIELDS,
|
||||||
|
recordId: workflowId,
|
||||||
|
data: {
|
||||||
|
name: 'Updated Workflow API Key',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithApiKey(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toBeDefined();
|
||||||
|
expect(response.body.data.updateWorkflow).toBeDefined();
|
||||||
|
expect(response.body.data.updateWorkflow.id).toBe(workflowId);
|
||||||
|
expect(response.body.data.updateWorkflow.name).toBe(
|
||||||
|
'Updated Workflow API Key',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user