diff --git a/packages/cli/src/workflows/workflows.controller.ee.ts b/packages/cli/src/workflows/workflows.controller.ee.ts index 90de9dc93f..410b699fce 100644 --- a/packages/cli/src/workflows/workflows.controller.ee.ts +++ b/packages/cli/src/workflows/workflows.controller.ee.ts @@ -203,12 +203,16 @@ EEWorkflowController.post( EEWorkflowController.get( '/', ResponseHelper.send(async (req: WorkflowRequest.GetAll) => { - const workflows = await EEWorkflows.getMany(req.user, req.query.filter); - await EEWorkflows.addCredentialsToWorkflows(workflows, req.user); + const [workflows, workflowOwnerRole] = await Promise.all([ + EEWorkflows.getMany(req.user, req.query.filter), + Db.collections.Role.findOneOrFail({ + select: ['id'], + where: { name: 'owner', scope: 'workflow' }, + }), + ]); return workflows.map((workflow) => { - EEWorkflows.addOwnerAndSharings(workflow); - workflow.nodes = []; + EEWorkflows.addOwnerId(workflow, workflowOwnerRole); return workflow; }); }), diff --git a/packages/cli/src/workflows/workflows.services.ee.ts b/packages/cli/src/workflows/workflows.services.ee.ts index aa47acd2e7..97b8df7e9c 100644 --- a/packages/cli/src/workflows/workflows.services.ee.ts +++ b/packages/cli/src/workflows/workflows.services.ee.ts @@ -5,6 +5,7 @@ import * as ResponseHelper from '@/ResponseHelper'; import * as WorkflowHelpers from '@/WorkflowHelpers'; import type { ICredentialsDb } from '@/Interfaces'; import { SharedWorkflow } from '@db/entities/SharedWorkflow'; +import type { Role } from '@db/entities/Role'; import type { User } from '@db/entities/User'; import { WorkflowEntity } from '@db/entities/WorkflowEntity'; import { RoleService } from '@/role/role.service'; @@ -13,6 +14,7 @@ import { WorkflowsService } from './workflows.services'; import type { CredentialUsedByWorkflow, WorkflowWithSharingsAndCredentials, + WorkflowForList, } from './workflows.types'; import { EECredentialsService as EECredentials } from '@/credentials/credentials.service.ee'; import { getSharedWorkflowIds } from '@/WorkflowHelpers'; @@ -87,6 +89,14 @@ export class EEWorkflowsService extends WorkflowsService { return transaction.save(newSharedWorkflows); } + static addOwnerId(workflow: WorkflowForList, workflowOwnerRole: Role): void { + const ownerId = workflow.shared?.find( + ({ roleId }) => String(roleId) === workflowOwnerRole.id, + )?.userId; + workflow.ownedBy = ownerId ? { id: ownerId } : null; + delete workflow.shared; + } + static addOwnerAndSharings(workflow: WorkflowWithSharingsAndCredentials): void { workflow.ownedBy = null; workflow.sharedWith = []; @@ -156,72 +166,6 @@ export class EEWorkflowsService extends WorkflowsService { }); } - static async addCredentialsToWorkflows( - workflows: WorkflowWithSharingsAndCredentials[], - currentUser: User, - ): Promise { - // Create 2 maps: one with all the credential ids used by all workflows - // And another to match back workflow <> credentials - const allUsedCredentialIds = new Set(); - const mapsWorkflowsToUsedCredentials: string[][] = []; - workflows.forEach((workflow, idx) => { - workflow.nodes.forEach((node) => { - if (!node.credentials) { - return; - } - Object.keys(node.credentials).forEach((credentialType) => { - const credential = node.credentials?.[credentialType]; - if (!credential?.id) { - return; - } - if (!mapsWorkflowsToUsedCredentials[idx]) { - mapsWorkflowsToUsedCredentials[idx] = []; - } - mapsWorkflowsToUsedCredentials[idx].push(credential.id); - allUsedCredentialIds.add(credential.id); - }); - }); - }); - - const usedWorkflowsCredentials = await EECredentials.getMany({ - where: { - id: In(Array.from(allUsedCredentialIds)), - }, - relations: ['shared', 'shared.user', 'shared.role'], - }); - const userCredentials = await EECredentials.getAll(currentUser, { disableGlobalRole: true }); - const userCredentialIds = userCredentials.map((credential) => credential.id); - const credentialsMap: Record = {}; - usedWorkflowsCredentials.forEach((credential) => { - const credentialId = credential.id; - credentialsMap[credentialId] = { - id: credentialId, - name: credential.name, - type: credential.type, - currentUserHasAccess: userCredentialIds.includes(credentialId), - sharedWith: [], - ownedBy: null, - }; - credential.shared?.forEach(({ user, role }) => { - const { id, email, firstName, lastName } = user; - if (role.name === 'owner') { - credentialsMap[credentialId].ownedBy = { id, email, firstName, lastName }; - } else { - credentialsMap[credentialId].sharedWith?.push({ - id, - email, - firstName, - lastName, - }); - } - }); - }); - - mapsWorkflowsToUsedCredentials.forEach((usedCredentialIds, idx) => { - workflows[idx].usedCredentials = usedCredentialIds.map((id) => credentialsMap[id]); - }); - } - static validateCredentialPermissionsToUser( workflow: WorkflowEntity, allowedCredentials: ICredentialsDb[], diff --git a/packages/cli/src/workflows/workflows.services.ts b/packages/cli/src/workflows/workflows.services.ts index 3b5fd2d928..601f34807d 100644 --- a/packages/cli/src/workflows/workflows.services.ts +++ b/packages/cli/src/workflows/workflows.services.ts @@ -1,7 +1,7 @@ import { validate as jsonSchemaValidate } from 'jsonschema'; import type { INode, IPinData, JsonObject } from 'n8n-workflow'; import { NodeApiError, jsonParse, LoggerProxy, Workflow } from 'n8n-workflow'; -import type { FindOptionsWhere, UpdateResult } from 'typeorm'; +import type { FindOptionsSelect, FindOptionsWhere, UpdateResult } from 'typeorm'; import { In } from 'typeorm'; import pick from 'lodash.pick'; import { v4 as uuid } from 'uuid'; @@ -25,12 +25,12 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData' import * as TestWebhooks from '@/TestWebhooks'; import { getSharedWorkflowIds } from '@/WorkflowHelpers'; import { isSharingEnabled, whereClause } from '@/UserManagement/UserManagementHelper'; +import type { WorkflowForList } from '@/workflows/workflows.types'; -export interface IGetWorkflowsQueryFilter { - id?: string; - name?: string; - active?: boolean; -} +export type IGetWorkflowsQueryFilter = Pick< + FindOptionsWhere, + 'id' | 'name' | 'active' +>; const schemaGetWorkflowsQueryFilter = { $id: '/IGetWorkflowsQueryFilter', @@ -114,7 +114,7 @@ export class WorkflowsService { return getSharedWorkflowIds(user, roles); } - static async getMany(user: User, rawFilter: string): Promise { + static async getMany(user: User, rawFilter: string): Promise { const sharedWorkflowIds = await this.getWorkflowIdsForUser(user, ['owner']); if (sharedWorkflowIds.length === 0) { // return early since without shared workflows there can be no hits @@ -122,7 +122,7 @@ export class WorkflowsService { return []; } - let filter: IGetWorkflowsQueryFilter | undefined = undefined; + let filter: IGetWorkflowsQueryFilter = {}; if (rawFilter) { try { const filterJson: JsonObject = jsonParse(rawFilter); @@ -152,31 +152,32 @@ export class WorkflowsService { return []; } - const fields: Array = [ - 'id', - 'name', - 'active', - 'createdAt', - 'updatedAt', - 'nodes', - ]; + const select: FindOptionsSelect = { + id: true, + name: true, + active: true, + createdAt: true, + updatedAt: true, + }; const relations: string[] = []; if (!config.getEnv('workflowTagsDisabled')) { relations.push('tags'); + select.tags = { name: true }; } if (isSharingEnabled()) { - relations.push('shared', 'shared.user', 'shared.role'); + relations.push('shared'); + select.shared = { userId: true, roleId: true }; + select.versionId = true; } + filter.id = In(sharedWorkflowIds); return Db.collections.Workflow.find({ - select: isSharingEnabled() ? [...fields, 'versionId'] : fields, + select, relations, - where: { - id: In(sharedWorkflowIds), - ...filter, - }, + where: filter, + order: { updatedAt: 'DESC' }, }); } diff --git a/packages/cli/src/workflows/workflows.types.ts b/packages/cli/src/workflows/workflows.types.ts index ef30bb18c7..99813736ab 100644 --- a/packages/cli/src/workflows/workflows.types.ts +++ b/packages/cli/src/workflows/workflows.types.ts @@ -9,6 +9,12 @@ export interface WorkflowWithSharingsAndCredentials extends Omit { + ownedBy?: Pick | null; + shared?: SharedWorkflow[]; +} + export interface CredentialUsedByWorkflow { id: string; name: string; diff --git a/packages/cli/test/integration/workflows.controller.ee.test.ts b/packages/cli/test/integration/workflows.controller.ee.test.ts index 8c5fda07bb..b03bbebc74 100644 --- a/packages/cli/test/integration/workflows.controller.ee.test.ts +++ b/packages/cli/test/integration/workflows.controller.ee.test.ts @@ -151,7 +151,7 @@ describe('PUT /workflows/:id', () => { }); describe('GET /workflows', () => { - test('should return workflows with ownership, sharing and credential usage details', async () => { + test('should return workflows without nodes, sharing and credential usage details', async () => { const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const member = await testDb.createUser({ globalRole: globalMemberRole }); @@ -188,32 +188,11 @@ describe('GET /workflows', () => { expect(response.statusCode).toBe(200); expect(fetchedWorkflow.ownedBy).toMatchObject({ id: owner.id, - email: owner.email, - firstName: owner.firstName, - lastName: owner.lastName, }); - expect(fetchedWorkflow.sharedWith).toHaveLength(1); - - const [sharee] = fetchedWorkflow.sharedWith; - - expect(sharee).toMatchObject({ - id: member.id, - email: member.email, - firstName: member.firstName, - lastName: member.lastName, - }); - - expect(fetchedWorkflow.usedCredentials).toHaveLength(1); - - const [usedCredential] = fetchedWorkflow.usedCredentials; - - expect(usedCredential).toMatchObject({ - id: savedCredential.id, - name: savedCredential.name, - type: savedCredential.type, - currentUserHasAccess: true, - }); + expect(fetchedWorkflow.sharedWith).not.toBeDefined() + expect(fetchedWorkflow.usedCredentials).not.toBeDefined() + expect(fetchedWorkflow.nodes).not.toBeDefined() }); });