refactor: Consolidate WorkflowService.getMany() (no-changelog) (#6892)
In scope: - Consolidate `WorkflowService.getMany()`. - Support non-entity field `ownedBy` for `select`. - Support `tags` for `filter`. - Move `addOwnerId` to `OwnershipService`. - Remove unneeded check for `filter.id`. - Simplify DTO validation for `filter` and `select`. - Expand tests for `GET /workflows`. Workflow list query DTOs: ``` filter → name, active, tags select → id, name, active, tags, createdAt, updatedAt, versionId, ownedBy ``` Out of scope: - Migrate `shared_workflow.roleId` and `shared_credential.roleId` to string IDs. - Refactor `WorkflowHelpers.getSharedWorkflowIds()`.
This commit is contained in:
@@ -11,23 +11,23 @@ import * as ResponseHelper from '@/ResponseHelper';
|
||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||
import config from '@/config';
|
||||
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||
import type { RoleNames } from '@db/entities/Role';
|
||||
import type { User } from '@db/entities/User';
|
||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||
import { validateEntity } from '@/GenericHelpers';
|
||||
import { ExternalHooks } from '@/ExternalHooks';
|
||||
import { type WorkflowRequest, type ListQuery, hasSharing } from '@/requests';
|
||||
import { TagService } from '@/services/tag.service';
|
||||
import type { ListQueryOptions, WorkflowRequest } from '@/requests';
|
||||
import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces';
|
||||
import { NodeTypes } from '@/NodeTypes';
|
||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||
import { TestWebhooks } from '@/TestWebhooks';
|
||||
import { getSharedWorkflowIds } from '@/WorkflowHelpers';
|
||||
import { isSharingEnabled, whereClause } from '@/UserManagement/UserManagementHelper';
|
||||
import { whereClause } from '@/UserManagement/UserManagementHelper';
|
||||
import { InternalHooks } from '@/InternalHooks';
|
||||
import type { WorkflowForList } from './workflows.types';
|
||||
import { WorkflowRepository } from '@/databases/repositories';
|
||||
import { RoleService } from '@/services/role.service';
|
||||
import { OwnershipService } from '@/services/ownership.service';
|
||||
import { isStringArray } from '@/utils';
|
||||
|
||||
export class WorkflowsService {
|
||||
static async getSharing(
|
||||
@@ -94,75 +94,85 @@ export class WorkflowsService {
|
||||
return Db.collections.Workflow.findOne({ where: workflow, relations: options?.relations });
|
||||
}
|
||||
|
||||
// Warning: this function is overridden by EE to disregard role list.
|
||||
static async getWorkflowIdsForUser(user: User, roles?: RoleNames[]): Promise<string[]> {
|
||||
return getSharedWorkflowIds(user, roles);
|
||||
}
|
||||
static async getMany(sharedWorkflowIds: string[], options?: ListQuery.Options) {
|
||||
if (sharedWorkflowIds.length === 0) return { workflows: [], count: 0 };
|
||||
|
||||
static async getMany(
|
||||
user: User,
|
||||
options?: ListQueryOptions,
|
||||
): Promise<[WorkflowForList[], number]> {
|
||||
const sharedWorkflowIds = await this.getWorkflowIdsForUser(user, ['owner']);
|
||||
if (sharedWorkflowIds.length === 0) {
|
||||
// return early since without shared workflows there can be no hits
|
||||
// (note: getSharedWorkflowIds() returns _all_ workflow ids for global owners)
|
||||
return [[], 0];
|
||||
}
|
||||
|
||||
const filter = options?.filter ?? {};
|
||||
|
||||
// safeguard against querying ids not shared with the user
|
||||
const workflowId = filter?.id?.toString();
|
||||
if (workflowId !== undefined && !sharedWorkflowIds.includes(workflowId)) {
|
||||
LoggerProxy.verbose(`User ${user.id} attempted to query non-shared workflow ${workflowId}`);
|
||||
return [[], 0];
|
||||
}
|
||||
|
||||
const DEFAULT_SELECT: FindOptionsSelect<WorkflowEntity> = {
|
||||
id: true,
|
||||
name: true,
|
||||
active: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
const where: FindOptionsWhere<WorkflowEntity> = {
|
||||
...options?.filter,
|
||||
id: In(sharedWorkflowIds),
|
||||
};
|
||||
|
||||
const select: FindOptionsSelect<WorkflowEntity> = options?.select ?? DEFAULT_SELECT;
|
||||
const reqTags = options?.filter?.tags;
|
||||
|
||||
if (isStringArray(reqTags)) {
|
||||
where.tags = reqTags.map((tag) => ({ name: tag }));
|
||||
}
|
||||
|
||||
type Select = FindOptionsSelect<WorkflowEntity> & { ownedBy?: true };
|
||||
|
||||
const select: Select = options?.select
|
||||
? { ...options.select } // copy to enable field removal without affecting original
|
||||
: {
|
||||
name: true,
|
||||
active: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
versionId: true,
|
||||
shared: { userId: true, roleId: true },
|
||||
};
|
||||
|
||||
delete select?.ownedBy; // remove non-entity field, handled after query
|
||||
|
||||
const relations: string[] = [];
|
||||
|
||||
const areTagsEnabled = !config.getEnv('workflowTagsDisabled');
|
||||
const isDefaultSelect = options?.select === undefined;
|
||||
const areTagsRequested = isDefaultSelect || options?.select?.tags === true;
|
||||
const isOwnedByIncluded = isDefaultSelect || options?.select?.ownedBy === true;
|
||||
|
||||
if (isDefaultSelect && !config.getEnv('workflowTagsDisabled')) {
|
||||
if (areTagsEnabled && areTagsRequested) {
|
||||
relations.push('tags');
|
||||
select.tags = { id: true, name: true };
|
||||
}
|
||||
|
||||
if (isDefaultSelect && isSharingEnabled()) {
|
||||
relations.push('shared');
|
||||
select.shared = { userId: true, roleId: true };
|
||||
select.versionId = true;
|
||||
}
|
||||
if (isOwnedByIncluded) relations.push('shared');
|
||||
|
||||
filter.id = In(sharedWorkflowIds);
|
||||
|
||||
if (typeof filter.name === 'string' && filter.name !== '') {
|
||||
filter.name = Like(`%${filter.name}%`);
|
||||
if (typeof where.name === 'string' && where.name !== '') {
|
||||
where.name = Like(`%${where.name}%`);
|
||||
}
|
||||
|
||||
const findManyOptions: FindManyOptions<WorkflowEntity> = {
|
||||
select,
|
||||
relations,
|
||||
where: filter,
|
||||
order: { updatedAt: 'ASC' },
|
||||
select: { ...select, id: true },
|
||||
where,
|
||||
};
|
||||
|
||||
if (isDefaultSelect || options?.select?.updatedAt === true) {
|
||||
findManyOptions.order = { updatedAt: 'ASC' };
|
||||
}
|
||||
|
||||
if (relations.length > 0) {
|
||||
findManyOptions.relations = relations;
|
||||
}
|
||||
|
||||
if (options?.take) {
|
||||
findManyOptions.skip = options.skip;
|
||||
findManyOptions.take = options.take;
|
||||
}
|
||||
|
||||
return Container.get(WorkflowRepository).findAndCount(findManyOptions);
|
||||
const [workflows, count] = (await Container.get(WorkflowRepository).findAndCount(
|
||||
findManyOptions,
|
||||
)) as [ListQuery.Workflow.Plain[] | ListQuery.Workflow.WithSharing[], number];
|
||||
|
||||
if (!hasSharing(workflows)) return { workflows, count };
|
||||
|
||||
const workflowOwnerRole = await Container.get(RoleService).findWorkflowOwnerRole();
|
||||
|
||||
return {
|
||||
workflows: workflows.map((w) =>
|
||||
Container.get(OwnershipService).addOwnedBy(w, workflowOwnerRole),
|
||||
),
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
static async update(
|
||||
|
||||
Reference in New Issue
Block a user