mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-10 06:34:05 -08:00
refactor(core): Switch Tags queries from QueryBuilder to Repository API (no-changelog) (#5819)
Co-authored-by: Omar Ajoue <krynble@gmail.com>
This commit is contained in:
parent
f8f584c136
commit
41cdee7bc7
|
@ -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);
|
||||
|
|
|
@ -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<WorkflowEntity>;
|
||||
Webhook: Repository<WebhookEntity>;
|
||||
Tag: Repository<TagEntity>;
|
||||
WorkflowTagMapping: Repository<WorkflowTagMapping>;
|
||||
Role: Repository<Role>;
|
||||
User: Repository<User>;
|
||||
SharedCredentials: Repository<SharedCredentials>;
|
||||
|
@ -108,7 +110,8 @@ export type UsageCount = {
|
|||
usageCount: number;
|
||||
};
|
||||
|
||||
export type ITagWithCountDb = TagEntity & UsageCount;
|
||||
export type ITagWithCountDb = Pick<TagEntity, 'id' | 'name' | 'createdAt' | 'updatedAt'> &
|
||||
UsageCount;
|
||||
|
||||
// ----------------------------------
|
||||
// workflows
|
||||
|
|
|
@ -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<ITagWithCountDb[]> {
|
||||
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<TagEntity> => {
|
||||
const tag = new TagEntity();
|
||||
tag.name = name;
|
||||
|
|
|
@ -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<TagEntity[] | ITagWithCountDb[]> {
|
||||
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'] });
|
||||
|
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
@ -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[];
|
||||
|
||||
|
|
21
packages/cli/src/databases/entities/WorkflowTagMapping.ts
Normal file
21
packages/cli/src/databases/entities/WorkflowTagMapping.ts
Normal file
|
@ -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[];
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -186,7 +186,7 @@ export class WorkflowsService {
|
|||
user: User,
|
||||
workflow: WorkflowEntity,
|
||||
workflowId: string,
|
||||
tags?: string[],
|
||||
tagIds?: string[],
|
||||
forceSave?: boolean,
|
||||
roles?: string[],
|
||||
): Promise<WorkflowEntity> {
|
||||
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue