diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 28b760fd2e618..2a6a4f38436ca 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -159,6 +159,7 @@ export async function init( collections.Workflow = linkRepository(entities.WorkflowEntity); collections.Webhook = linkRepository(entities.WebhookEntity); collections.Tag = linkRepository(entities.TagEntity); + collections.WorkflowTagMapping = linkRepository(entities.WorkflowTagMapping); collections.Role = linkRepository(entities.Role); collections.User = linkRepository(entities.User); collections.AuthIdentity = linkRepository(entities.AuthIdentity); diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index bf3f3e4d578a3..952d873174ded 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -48,8 +48,9 @@ import type { User } from '@db/entities/User'; import type { WebhookEntity } from '@db/entities/WebhookEntity'; import type { WorkflowEntity } from '@db/entities/WorkflowEntity'; import type { WorkflowStatistics } from '@db/entities/WorkflowStatistics'; +import type { WorkflowTagMapping } from '@db/entities/WorkflowTagMapping'; import type { EventDestinations } from '@db/entities/MessageEventBusDestinationEntity'; -import type { ExecutionMetadata } from './databases/entities/ExecutionMetadata'; +import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata'; export interface IActivationError { time: number; @@ -81,6 +82,7 @@ export interface IDatabaseCollections { Workflow: Repository; Webhook: Repository; Tag: Repository; + WorkflowTagMapping: Repository; Role: Repository; User: Repository; SharedCredentials: Repository; @@ -108,7 +110,8 @@ export type UsageCount = { usageCount: number; }; -export type ITagWithCountDb = TagEntity & UsageCount; +export type ITagWithCountDb = Pick & + UsageCount; // ---------------------------------- // workflows diff --git a/packages/cli/src/TagHelpers.ts b/packages/cli/src/TagHelpers.ts index 39313ea5aa356..b433149236d97 100644 --- a/packages/cli/src/TagHelpers.ts +++ b/packages/cli/src/TagHelpers.ts @@ -1,10 +1,6 @@ -/* eslint-disable no-param-reassign */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import type { EntityManager } from 'typeorm'; - -import { getConnection } from '@/Db'; import { TagEntity } from '@db/entities/TagEntity'; -import type { ITagToImport, ITagWithCountDb, IWorkflowToImport } from '@/Interfaces'; +import type { ITagToImport, IWorkflowToImport } from '@/Interfaces'; // ---------------------------------- // utils @@ -25,70 +21,10 @@ export function sortByRequestOrder( return requestOrder.map((tagId) => tagMap[tagId]); } -// ---------------------------------- -// queries -// ---------------------------------- - -/** - * Retrieve all tags and the number of workflows each tag is related to. - */ -export async function getTagsWithCountDb(tablePrefix: string): Promise { - return getConnection() - .createQueryBuilder() - .select(`${tablePrefix}tag_entity.id`, 'id') - .addSelect(`${tablePrefix}tag_entity.name`, 'name') - .addSelect(`${tablePrefix}tag_entity.createdAt`, 'createdAt') - .addSelect(`${tablePrefix}tag_entity.updatedAt`, 'updatedAt') - .addSelect(`COUNT(${tablePrefix}workflows_tags.workflowId)`, 'usageCount') - .from(`${tablePrefix}tag_entity`, 'tag_entity') - .leftJoin( - `${tablePrefix}workflows_tags`, - 'workflows_tags', - `${tablePrefix}workflows_tags.tagId = tag_entity.id`, - ) - .groupBy(`${tablePrefix}tag_entity.id`) - .getRawMany() - .then((tagsWithCount) => { - tagsWithCount.forEach((tag) => { - // NOTE: since this code doesn't use the DB entities, we need to stringify the IDs manually - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - tag.id = tag.id.toString(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - tag.usageCount = Number(tag.usageCount); - }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return tagsWithCount; - }); -} - // ---------------------------------- // mutations // ---------------------------------- -/** - * Relate a workflow to one or more tags. - */ -export async function createRelations(workflowId: string, tagIds: string[], tablePrefix: string) { - return getConnection() - .createQueryBuilder() - .insert() - .into(`${tablePrefix}workflows_tags`) - .values(tagIds.map((tagId) => ({ workflowId, tagId }))) - .execute(); -} - -/** - * Remove all tags for a workflow during a tag update operation. - */ -export async function removeRelations(workflowId: string, tablePrefix: string) { - return getConnection() - .createQueryBuilder() - .delete() - .from(`${tablePrefix}workflows_tags`) - .where('workflowId = :id', { id: workflowId }) - .execute(); -} - const createTag = async (transactionManager: EntityManager, name: string): Promise => { const tag = new TagEntity(); tag.name = name; diff --git a/packages/cli/src/controllers/tags.controller.ts b/packages/cli/src/controllers/tags.controller.ts index 3c73235c853b3..154f003e5f263 100644 --- a/packages/cli/src/controllers/tags.controller.ts +++ b/packages/cli/src/controllers/tags.controller.ts @@ -4,7 +4,6 @@ import type { Config } from '@/config'; import { Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators'; import type { IDatabaseCollections, IExternalHooksClass, ITagWithCountDb } from '@/Interfaces'; import { TagEntity } from '@db/entities/TagEntity'; -import { getTagsWithCountDb } from '@/TagHelpers'; import { validateEntity } from '@/GenericHelpers'; import { BadRequestError, UnauthorizedError } from '@/ResponseHelper'; import { TagsRequest } from '@/requests'; @@ -44,8 +43,17 @@ export class TagsController { async getAll(req: TagsRequest.GetAll): Promise { const { withUsageCount } = req.query; if (withUsageCount === 'true') { - const tablePrefix = this.config.getEnv('database.tablePrefix'); - return getTagsWithCountDb(tablePrefix); + return this.tagsRepository + .find({ + select: ['id', 'name', 'createdAt', 'updatedAt'], + relations: ['workflowMappings'], + }) + .then((tags) => + tags.map(({ workflowMappings, ...rest }) => ({ + ...rest, + usageCount: workflowMappings.length, + })), + ); } return this.tagsRepository.find({ select: ['id', 'name', 'createdAt', 'updatedAt'] }); diff --git a/packages/cli/src/databases/entities/TagEntity.ts b/packages/cli/src/databases/entities/TagEntity.ts index a2e84a41a0ef2..de25ba482bd81 100644 --- a/packages/cli/src/databases/entities/TagEntity.ts +++ b/packages/cli/src/databases/entities/TagEntity.ts @@ -1,8 +1,9 @@ -import { Column, Entity, Generated, Index, ManyToMany, PrimaryColumn } from 'typeorm'; +import { Column, Entity, Generated, Index, ManyToMany, OneToMany, PrimaryColumn } from 'typeorm'; import { IsString, Length } from 'class-validator'; import { idStringifier } from '../utils/transformers'; import type { WorkflowEntity } from './WorkflowEntity'; +import type { WorkflowTagMapping } from './WorkflowTagMapping'; import { AbstractEntity } from './AbstractEntity'; @Entity() @@ -19,4 +20,7 @@ export class TagEntity extends AbstractEntity { @ManyToMany('WorkflowEntity', 'tags') workflows: WorkflowEntity[]; + + @OneToMany('WorkflowTagMapping', 'tags') + workflowMappings: WorkflowTagMapping[]; } diff --git a/packages/cli/src/databases/entities/WorkflowEntity.ts b/packages/cli/src/databases/entities/WorkflowEntity.ts index 84bb5fc2ffd40..bee34796f124f 100644 --- a/packages/cli/src/databases/entities/WorkflowEntity.ts +++ b/packages/cli/src/databases/entities/WorkflowEntity.ts @@ -19,6 +19,7 @@ import config from '@/config'; import type { TagEntity } from './TagEntity'; import type { SharedWorkflow } from './SharedWorkflow'; import type { WorkflowStatistics } from './WorkflowStatistics'; +import type { WorkflowTagMapping } from './WorkflowTagMapping'; import { idStringifier, objectRetriever, sqlite } from '../utils/transformers'; import { AbstractEntity, jsonColumnType } from './AbstractEntity'; import type { IWorkflowDb } from '@/Interfaces'; @@ -73,6 +74,9 @@ export class WorkflowEntity extends AbstractEntity implements IWorkflowDb { }) tags?: TagEntity[]; + @OneToMany('WorkflowTagMapping', 'workflows') + tagMappings: WorkflowTagMapping[]; + @OneToMany('SharedWorkflow', 'workflow') shared: SharedWorkflow[]; diff --git a/packages/cli/src/databases/entities/WorkflowTagMapping.ts b/packages/cli/src/databases/entities/WorkflowTagMapping.ts new file mode 100644 index 0000000000000..88b92b0b194bc --- /dev/null +++ b/packages/cli/src/databases/entities/WorkflowTagMapping.ts @@ -0,0 +1,21 @@ +import { Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm'; +import { idStringifier } from '../utils/transformers'; +import type { TagEntity } from './TagEntity'; +import type { WorkflowEntity } from './WorkflowEntity'; + +@Entity({ name: 'workflows_tags' }) +export class WorkflowTagMapping { + @PrimaryColumn({ transformer: idStringifier }) + workflowId: string; + + @ManyToOne('WorkflowEntity', 'tagMappings') + @JoinColumn({ name: 'workflowId' }) + workflows: WorkflowEntity[]; + + @PrimaryColumn() + tagId: string; + + @ManyToOne('TagEntity', 'workflowMappings') + @JoinColumn({ name: 'tagId' }) + tags: TagEntity[]; +} diff --git a/packages/cli/src/databases/entities/index.ts b/packages/cli/src/databases/entities/index.ts index 55ba7b865183e..51e6ab7555664 100644 --- a/packages/cli/src/databases/entities/index.ts +++ b/packages/cli/src/databases/entities/index.ts @@ -14,6 +14,7 @@ import { TagEntity } from './TagEntity'; import { User } from './User'; import { WebhookEntity } from './WebhookEntity'; import { WorkflowEntity } from './WorkflowEntity'; +import { WorkflowTagMapping } from './WorkflowTagMapping'; import { WorkflowStatistics } from './WorkflowStatistics'; import { ExecutionMetadata } from './ExecutionMetadata'; @@ -33,6 +34,7 @@ export const entities = { User, WebhookEntity, WorkflowEntity, + WorkflowTagMapping, WorkflowStatistics, ExecutionMetadata, }; diff --git a/packages/cli/src/workflows/workflows.services.ts b/packages/cli/src/workflows/workflows.services.ts index 8563038e4cdac..e942620e070a9 100644 --- a/packages/cli/src/workflows/workflows.services.ts +++ b/packages/cli/src/workflows/workflows.services.ts @@ -186,7 +186,7 @@ export class WorkflowsService { user: User, workflow: WorkflowEntity, workflowId: string, - tags?: string[], + tagIds?: string[], forceSave?: boolean, roles?: string[], ): Promise { @@ -285,13 +285,11 @@ export class WorkflowsService { ]), ); - if (tags && !config.getEnv('workflowTagsDisabled')) { - const tablePrefix = config.getEnv('database.tablePrefix'); - await TagHelpers.removeRelations(workflowId, tablePrefix); - - if (tags.length) { - await TagHelpers.createRelations(workflowId, tags, tablePrefix); - } + if (tagIds && !config.getEnv('workflowTagsDisabled')) { + await Db.collections.WorkflowTagMapping.delete({ workflowId }); + await Db.collections.WorkflowTagMapping.insert( + tagIds.map((tagId) => ({ tagId, workflowId })), + ); } const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags']; @@ -309,9 +307,9 @@ export class WorkflowsService { ); } - if (updatedWorkflow.tags?.length && tags?.length) { + if (updatedWorkflow.tags?.length && tagIds?.length) { updatedWorkflow.tags = TagHelpers.sortByRequestOrder(updatedWorkflow.tags, { - requestOrder: tags, + requestOrder: tagIds, }); }