mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor(core): Move remaining tags logic to service (no-changelog) (#6920)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
9e3e298aca
commit
9b9b891e68
|
@ -8,7 +8,7 @@ import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { TagRepository } from '@/databases/repositories';
|
import { TagService } from '@/services/tag.service';
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
|
|
||||||
function insertIf(condition: boolean, elements: string[]): string[] {
|
function insertIf(condition: boolean, elements: string[]): string[] {
|
||||||
|
@ -64,7 +64,7 @@ export async function getWorkflowById(id: string): Promise<WorkflowEntity | null
|
||||||
* Intersection! e.g. workflow needs to have all provided tags.
|
* Intersection! e.g. workflow needs to have all provided tags.
|
||||||
*/
|
*/
|
||||||
export async function getWorkflowIdsViaTags(tags: string[]): Promise<string[]> {
|
export async function getWorkflowIdsViaTags(tags: string[]): Promise<string[]> {
|
||||||
const dbTags = await Container.get(TagRepository).find({
|
const dbTags = await Container.get(TagService).findMany({
|
||||||
where: { name: In(tags) },
|
where: { name: In(tags) },
|
||||||
relations: ['workflows'],
|
relations: ['workflows'],
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
import type { EntityManager } from 'typeorm';
|
|
||||||
import type { TagEntity } from '@db/entities/TagEntity';
|
|
||||||
import type { ITagToImport, IWorkflowToImport } from '@/Interfaces';
|
|
||||||
import { TagRepository } from './databases/repositories';
|
|
||||||
import Container from 'typedi';
|
|
||||||
|
|
||||||
// ----------------------------------
|
|
||||||
// utils
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort tags based on the order of the tag IDs in the request.
|
|
||||||
*/
|
|
||||||
export function sortByRequestOrder(
|
|
||||||
tags: TagEntity[],
|
|
||||||
{ requestOrder }: { requestOrder: string[] },
|
|
||||||
) {
|
|
||||||
const tagMap = tags.reduce<Record<string, TagEntity>>((acc, tag) => {
|
|
||||||
acc[tag.id] = tag;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return requestOrder.map((tagId) => tagMap[tagId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------
|
|
||||||
// mutations
|
|
||||||
// ----------------------------------
|
|
||||||
|
|
||||||
const createTag = async (transactionManager: EntityManager, name: string): Promise<TagEntity> => {
|
|
||||||
const tag = Container.get(TagRepository).create({ name: name.trim() });
|
|
||||||
return transactionManager.save<TagEntity>(tag);
|
|
||||||
};
|
|
||||||
|
|
||||||
const findOrCreateTag = async (
|
|
||||||
transactionManager: EntityManager,
|
|
||||||
importTag: ITagToImport,
|
|
||||||
tagsEntities: TagEntity[],
|
|
||||||
): Promise<TagEntity> => {
|
|
||||||
// Assume tag is identical if createdAt date is the same to preserve a changed tag name
|
|
||||||
const identicalMatch = tagsEntities.find(
|
|
||||||
(existingTag) =>
|
|
||||||
existingTag.id === importTag.id &&
|
|
||||||
existingTag.createdAt &&
|
|
||||||
importTag.createdAt &&
|
|
||||||
existingTag.createdAt.getTime() === new Date(importTag.createdAt).getTime(),
|
|
||||||
);
|
|
||||||
if (identicalMatch) {
|
|
||||||
return identicalMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameMatch = tagsEntities.find((existingTag) => existingTag.name === importTag.name);
|
|
||||||
if (nameMatch) {
|
|
||||||
return nameMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
const created = await createTag(transactionManager, importTag.name);
|
|
||||||
tagsEntities.push(created);
|
|
||||||
return created;
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasTags = (workflow: IWorkflowToImport) =>
|
|
||||||
'tags' in workflow && Array.isArray(workflow.tags) && workflow.tags.length > 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set tag IDs to use existing tags, creates a new tag if no matching tag could be found
|
|
||||||
*/
|
|
||||||
export async function setTagsForImport(
|
|
||||||
transactionManager: EntityManager,
|
|
||||||
workflow: IWorkflowToImport,
|
|
||||||
tags: TagEntity[],
|
|
||||||
): Promise<void> {
|
|
||||||
if (!hasTags(workflow)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowTags = workflow.tags;
|
|
||||||
const tagLookupPromises = [];
|
|
||||||
for (let i = 0; i < workflowTags.length; i++) {
|
|
||||||
if (workflowTags[i]?.name) {
|
|
||||||
const lookupPromise = findOrCreateTag(transactionManager, workflowTags[i], tags).then(
|
|
||||||
(tag) => {
|
|
||||||
workflowTags[i] = {
|
|
||||||
id: tag.id,
|
|
||||||
name: tag.name,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
);
|
|
||||||
tagLookupPromises.push(lookupPromise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(tagLookupPromises);
|
|
||||||
}
|
|
|
@ -11,14 +11,13 @@ import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import type { Role } from '@db/entities/Role';
|
import type { Role } from '@db/entities/Role';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { setTagsForImport } from '@/TagHelpers';
|
|
||||||
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
|
||||||
import type { ICredentialsDb, IWorkflowToImport } from '@/Interfaces';
|
import type { ICredentialsDb, IWorkflowToImport } from '@/Interfaces';
|
||||||
import { replaceInvalidCredentials } from '@/WorkflowHelpers';
|
import { replaceInvalidCredentials } from '@/WorkflowHelpers';
|
||||||
import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand';
|
import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand';
|
||||||
import { generateNanoId } from '@db/utils/generators';
|
import { generateNanoId } from '@db/utils/generators';
|
||||||
import { RoleService } from '@/services/role.service';
|
import { RoleService } from '@/services/role.service';
|
||||||
import { TagRepository } from '@/databases/repositories';
|
import { TagService } from '@/services/tag.service';
|
||||||
|
|
||||||
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
|
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
|
||||||
if (!Array.isArray(workflows)) {
|
if (!Array.isArray(workflows)) {
|
||||||
|
@ -66,6 +65,8 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||||
|
|
||||||
private transactionManager: EntityManager;
|
private transactionManager: EntityManager;
|
||||||
|
|
||||||
|
private tagService = Container.get(TagService);
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
disableAutoGeneratedIds(WorkflowEntity);
|
disableAutoGeneratedIds(WorkflowEntity);
|
||||||
await super.init();
|
await super.init();
|
||||||
|
@ -93,7 +94,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||||
const user = flags.userId ? await this.getAssignee(flags.userId) : await this.getOwner();
|
const user = flags.userId ? await this.getAssignee(flags.userId) : await this.getOwner();
|
||||||
|
|
||||||
const credentials = await Db.collections.Credentials.find();
|
const credentials = await Db.collections.Credentials.find();
|
||||||
const tags = await Container.get(TagRepository).find();
|
const tags = await this.tagService.getAll();
|
||||||
|
|
||||||
let totalImported = 0;
|
let totalImported = 0;
|
||||||
|
|
||||||
|
@ -133,7 +134,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(workflow, 'tags')) {
|
if (Object.prototype.hasOwnProperty.call(workflow, 'tags')) {
|
||||||
await setTagsForImport(transactionManager, workflow, tags);
|
await this.tagService.setTagsForImport(transactionManager, workflow, tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workflow.active) {
|
if (workflow.active) {
|
||||||
|
@ -183,7 +184,7 @@ export class ImportWorkflowsCommand extends BaseCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(workflow, 'tags')) {
|
if (Object.prototype.hasOwnProperty.call(workflow, 'tags')) {
|
||||||
await setTagsForImport(transactionManager, workflow, tags);
|
await this.tagService.setTagsForImport(transactionManager, workflow, tags);
|
||||||
}
|
}
|
||||||
if (workflow.active) {
|
if (workflow.active) {
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { Authorized, Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators';
|
import { Authorized, Delete, Get, Middleware, Patch, Post, RestController } from '@/decorators';
|
||||||
import { type ITagWithCountDb } from '@/Interfaces';
|
import { TagService } from '@/services/tag.service';
|
||||||
import type { TagEntity } from '@db/entities/TagEntity';
|
|
||||||
import { TagRepository } from '@db/repositories';
|
|
||||||
import { validateEntity } from '@/GenericHelpers';
|
|
||||||
import { BadRequestError } from '@/ResponseHelper';
|
import { BadRequestError } from '@/ResponseHelper';
|
||||||
import { TagsRequest } from '@/requests';
|
import { TagsRequest } from '@/requests';
|
||||||
import { Service } from 'typedi';
|
import { Service } from 'typedi';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
|
||||||
|
|
||||||
@Authorized()
|
@Authorized()
|
||||||
@RestController('/tags')
|
@RestController('/tags')
|
||||||
|
@ -16,10 +12,7 @@ import { ExternalHooks } from '@/ExternalHooks';
|
||||||
export class TagsController {
|
export class TagsController {
|
||||||
private config = config;
|
private config = config;
|
||||||
|
|
||||||
constructor(
|
constructor(private tagService: TagService) {}
|
||||||
private tagsRepository: TagRepository,
|
|
||||||
private externalHooks: ExternalHooks,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// TODO: move this into a new decorator `@IfEnabled('workflowTagsDisabled')`
|
// TODO: move this into a new decorator `@IfEnabled('workflowTagsDisabled')`
|
||||||
@Middleware()
|
@Middleware()
|
||||||
|
@ -29,61 +22,32 @@ export class TagsController {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves all tags, with or without usage count
|
|
||||||
@Get('/')
|
@Get('/')
|
||||||
async getAll(req: TagsRequest.GetAll): Promise<TagEntity[] | ITagWithCountDb[]> {
|
async getAll(req: TagsRequest.GetAll) {
|
||||||
const { withUsageCount } = req.query;
|
return this.tagService.getAll({ withUsageCount: req.query.withUsageCount === 'true' });
|
||||||
if (withUsageCount === 'true') {
|
|
||||||
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'] });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a tag
|
|
||||||
@Post('/')
|
@Post('/')
|
||||||
async createTag(req: TagsRequest.Create): Promise<TagEntity> {
|
async createTag(req: TagsRequest.Create) {
|
||||||
const newTag = this.tagsRepository.create({ name: req.body.name.trim() });
|
const tag = this.tagService.toEntity({ name: req.body.name });
|
||||||
|
|
||||||
await this.externalHooks.run('tag.beforeCreate', [newTag]);
|
return this.tagService.save(tag, 'create');
|
||||||
await validateEntity(newTag);
|
|
||||||
|
|
||||||
const tag = await this.tagsRepository.save(newTag);
|
|
||||||
await this.externalHooks.run('tag.afterCreate', [tag]);
|
|
||||||
return tag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates a tag
|
|
||||||
@Patch('/:id(\\w+)')
|
@Patch('/:id(\\w+)')
|
||||||
async updateTag(req: TagsRequest.Update): Promise<TagEntity> {
|
async updateTag(req: TagsRequest.Update) {
|
||||||
const newTag = this.tagsRepository.create({ id: req.params.id, name: req.body.name.trim() });
|
const newTag = this.tagService.toEntity({ id: req.params.id, name: req.body.name.trim() });
|
||||||
|
|
||||||
await this.externalHooks.run('tag.beforeUpdate', [newTag]);
|
return this.tagService.save(newTag, 'update');
|
||||||
await validateEntity(newTag);
|
|
||||||
|
|
||||||
const tag = await this.tagsRepository.save(newTag);
|
|
||||||
await this.externalHooks.run('tag.afterUpdate', [tag]);
|
|
||||||
return tag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized(['global', 'owner'])
|
@Authorized(['global', 'owner'])
|
||||||
@Delete('/:id(\\w+)')
|
@Delete('/:id(\\w+)')
|
||||||
async deleteTag(req: TagsRequest.Delete) {
|
async deleteTag(req: TagsRequest.Delete) {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
await this.externalHooks.run('tag.beforeDelete', [id]);
|
|
||||||
|
|
||||||
await this.tagsRepository.delete({ id });
|
await this.tagService.delete(id);
|
||||||
await this.externalHooks.run('tag.afterDelete', [id]);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkfl
|
||||||
import type { ExportableCredential } from './types/exportableCredential';
|
import type { ExportableCredential } from './types/exportableCredential';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { TagRepository } from '@/databases/repositories';
|
import { TagRepository } from '@/databases/repositories';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SourceControlService {
|
export class SourceControlService {
|
||||||
private sshKeyName: string;
|
private sshKeyName: string;
|
||||||
|
|
|
@ -492,15 +492,12 @@ export class SourceControlImportService {
|
||||||
`A tag with the name <strong>${tag.name}</strong> already exists locally.<br />Please either rename the local tag, or the remote one with the id <strong>${tag.id}</strong> in the tags.json file.`,
|
`A tag with the name <strong>${tag.name}</strong> already exists locally.<br />Please either rename the local tag, or the remote one with the id <strong>${tag.id}</strong> in the tags.json file.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await this.tagRepository.upsert(
|
|
||||||
{
|
const tagCopy = this.tagRepository.create(tag);
|
||||||
...tag,
|
await this.tagRepository.upsert(tagCopy, {
|
||||||
},
|
skipUpdateIfNoValuesChanged: true,
|
||||||
{
|
conflictPaths: { id: true },
|
||||||
skipUpdateIfNoValuesChanged: true,
|
});
|
||||||
conflictPaths: { id: true },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
157
packages/cli/src/services/tag.service.ts
Normal file
157
packages/cli/src/services/tag.service.ts
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
import { TagRepository } from '@/databases/repositories';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
import { validateEntity } from '@/GenericHelpers';
|
||||||
|
import type { ITagToImport, ITagWithCountDb, IWorkflowToImport } from '@/Interfaces';
|
||||||
|
import type { TagEntity } from '@/databases/entities/TagEntity';
|
||||||
|
import type { EntityManager, FindManyOptions, FindOneOptions } from 'typeorm';
|
||||||
|
import type { UpsertOptions } from 'typeorm/repository/UpsertOptions';
|
||||||
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
|
|
||||||
|
type GetAllResult<T> = T extends { withUsageCount: true } ? ITagWithCountDb[] : TagEntity[];
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class TagService {
|
||||||
|
constructor(
|
||||||
|
private externalHooks: ExternalHooks,
|
||||||
|
private tagRepository: TagRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
toEntity(attrs: { name: string; id?: string }) {
|
||||||
|
attrs.name = attrs.name.trim();
|
||||||
|
|
||||||
|
return this.tagRepository.create(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(tag: TagEntity, actionKind: 'create' | 'update') {
|
||||||
|
await validateEntity(tag);
|
||||||
|
|
||||||
|
const action = actionKind[0].toUpperCase() + actionKind.slice(1);
|
||||||
|
|
||||||
|
await this.externalHooks.run(`tag.before${action}`, [tag]);
|
||||||
|
|
||||||
|
const savedTag = this.tagRepository.save(tag);
|
||||||
|
|
||||||
|
await this.externalHooks.run(`tag.after${action}`, [tag]);
|
||||||
|
|
||||||
|
return savedTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string) {
|
||||||
|
await this.externalHooks.run('tag.beforeDelete', [id]);
|
||||||
|
|
||||||
|
const deleteResult = this.tagRepository.delete(id);
|
||||||
|
|
||||||
|
await this.externalHooks.run('tag.afterDelete', [id]);
|
||||||
|
|
||||||
|
return deleteResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(options: FindOneOptions<TagEntity>) {
|
||||||
|
return this.tagRepository.findOne(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findMany(options: FindManyOptions<TagEntity>) {
|
||||||
|
return this.tagRepository.find(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsert(tag: TagEntity, options: UpsertOptions<TagEntity>) {
|
||||||
|
return this.tagRepository.upsert(tag, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll<T extends { withUsageCount: boolean }>(options?: T): Promise<GetAllResult<T>> {
|
||||||
|
if (options?.withUsageCount) {
|
||||||
|
const allTags = await this.tagRepository.find({
|
||||||
|
select: ['id', 'name', 'createdAt', 'updatedAt'],
|
||||||
|
relations: ['workflowMappings'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return allTags.map(({ workflowMappings, ...rest }) => {
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
usageCount: workflowMappings.length,
|
||||||
|
} as ITagWithCountDb;
|
||||||
|
}) as GetAllResult<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tagRepository.find({
|
||||||
|
select: ['id', 'name', 'createdAt', 'updatedAt'],
|
||||||
|
}) as Promise<GetAllResult<T>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort tags based on the order of the tag IDs in the request.
|
||||||
|
*/
|
||||||
|
sortByRequestOrder(tags: TagEntity[], { requestOrder }: { requestOrder: string[] }) {
|
||||||
|
const tagMap = tags.reduce<Record<string, TagEntity>>((acc, tag) => {
|
||||||
|
acc[tag.id] = tag;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return requestOrder.map((tagId) => tagMap[tagId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set tag IDs to use existing tags, creates a new tag if no matching tag could be found
|
||||||
|
*/
|
||||||
|
async setTagsForImport(
|
||||||
|
transactionManager: EntityManager,
|
||||||
|
workflow: IWorkflowToImport,
|
||||||
|
tags: TagEntity[],
|
||||||
|
) {
|
||||||
|
if (!this.hasTags(workflow)) return;
|
||||||
|
|
||||||
|
const workflowTags = workflow.tags;
|
||||||
|
const tagLookupPromises = [];
|
||||||
|
for (let i = 0; i < workflowTags.length; i++) {
|
||||||
|
if (workflowTags[i]?.name) {
|
||||||
|
const lookupPromise = this.findOrCreateTag(transactionManager, workflowTags[i], tags).then(
|
||||||
|
(tag) => {
|
||||||
|
workflowTags[i] = {
|
||||||
|
id: tag.id,
|
||||||
|
name: tag.name,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tagLookupPromises.push(lookupPromise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(tagLookupPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
private hasTags(workflow: IWorkflowToImport) {
|
||||||
|
return 'tags' in workflow && Array.isArray(workflow.tags) && workflow.tags.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findOrCreateTag(
|
||||||
|
transactionManager: EntityManager,
|
||||||
|
importTag: ITagToImport,
|
||||||
|
tagsEntities: TagEntity[],
|
||||||
|
) {
|
||||||
|
// Assume tag is identical if createdAt date is the same to preserve a changed tag name
|
||||||
|
const identicalMatch = tagsEntities.find(
|
||||||
|
(existingTag) =>
|
||||||
|
existingTag.id === importTag.id &&
|
||||||
|
existingTag.createdAt &&
|
||||||
|
importTag.createdAt &&
|
||||||
|
existingTag.createdAt.getTime() === new Date(importTag.createdAt).getTime(),
|
||||||
|
);
|
||||||
|
if (identicalMatch) {
|
||||||
|
return identicalMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameMatch = tagsEntities.find((existingTag) => existingTag.name === importTag.name);
|
||||||
|
if (nameMatch) {
|
||||||
|
return nameMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
const created = await this.txCreateTag(transactionManager, importTag.name);
|
||||||
|
tagsEntities.push(created);
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async txCreateTag(transactionManager: EntityManager, name: string) {
|
||||||
|
const tag = this.tagRepository.create({ name: name.trim() });
|
||||||
|
return transactionManager.save<TagEntity>(tag);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ import { EEWorkflowsService as EEWorkflows } from './workflows.services.ee';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import { LoggerProxy } from 'n8n-workflow';
|
import { LoggerProxy } from 'n8n-workflow';
|
||||||
import * as TagHelpers from '@/TagHelpers';
|
|
||||||
import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee';
|
import { EECredentialsService as EECredentials } from '../credentials/credentials.service.ee';
|
||||||
import type { IExecutionPushResponse } from '@/Interfaces';
|
import type { IExecutionPushResponse } from '@/Interfaces';
|
||||||
import * as GenericHelpers from '@/GenericHelpers';
|
import * as GenericHelpers from '@/GenericHelpers';
|
||||||
|
@ -22,7 +21,7 @@ import { InternalHooks } from '@/InternalHooks';
|
||||||
import { RoleService } from '@/services/role.service';
|
import { RoleService } from '@/services/role.service';
|
||||||
import * as utils from '@/utils';
|
import * as utils from '@/utils';
|
||||||
import { listQueryMiddleware } from '@/middlewares';
|
import { listQueryMiddleware } from '@/middlewares';
|
||||||
import { TagRepository } from '@/databases/repositories';
|
import { TagService } from '@/services/tag.service';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export const EEWorkflowController = express.Router();
|
export const EEWorkflowController = express.Router();
|
||||||
|
@ -137,7 +136,7 @@ EEWorkflowController.post(
|
||||||
const { tags: tagIds } = req.body;
|
const { tags: tagIds } = req.body;
|
||||||
|
|
||||||
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
|
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
|
||||||
newWorkflow.tags = await Container.get(TagRepository).find({
|
newWorkflow.tags = await Container.get(TagService).findMany({
|
||||||
select: ['id', 'name'],
|
select: ['id', 'name'],
|
||||||
where: {
|
where: {
|
||||||
id: In(tagIds),
|
id: In(tagIds),
|
||||||
|
@ -188,7 +187,7 @@ EEWorkflowController.post(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
|
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
|
||||||
savedWorkflow.tags = TagHelpers.sortByRequestOrder(savedWorkflow.tags, {
|
savedWorkflow.tags = Container.get(TagService).sortByRequestOrder(savedWorkflow.tags, {
|
||||||
requestOrder: tagIds,
|
requestOrder: tagIds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import * as ResponseHelper from '@/ResponseHelper';
|
||||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||||
import type { IWorkflowResponse, IExecutionPushResponse } from '@/Interfaces';
|
import type { IWorkflowResponse, IExecutionPushResponse } from '@/Interfaces';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as TagHelpers from '@/TagHelpers';
|
|
||||||
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { validateEntity } from '@/GenericHelpers';
|
import { validateEntity } from '@/GenericHelpers';
|
||||||
|
@ -26,7 +25,7 @@ import { InternalHooks } from '@/InternalHooks';
|
||||||
import { RoleService } from '@/services/role.service';
|
import { RoleService } from '@/services/role.service';
|
||||||
import * as utils from '@/utils';
|
import * as utils from '@/utils';
|
||||||
import { listQueryMiddleware } from '@/middlewares';
|
import { listQueryMiddleware } from '@/middlewares';
|
||||||
import { TagRepository } from '@/databases/repositories';
|
import { TagService } from '@/services/tag.service';
|
||||||
|
|
||||||
export const workflowsController = express.Router();
|
export const workflowsController = express.Router();
|
||||||
|
|
||||||
|
@ -65,7 +64,7 @@ workflowsController.post(
|
||||||
const { tags: tagIds } = req.body;
|
const { tags: tagIds } = req.body;
|
||||||
|
|
||||||
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
|
if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) {
|
||||||
newWorkflow.tags = await Container.get(TagRepository).find({
|
newWorkflow.tags = await Container.get(TagService).findMany({
|
||||||
select: ['id', 'name'],
|
select: ['id', 'name'],
|
||||||
where: {
|
where: {
|
||||||
id: In(tagIds),
|
id: In(tagIds),
|
||||||
|
@ -101,7 +100,7 @@ workflowsController.post(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
|
if (tagIds && !config.getEnv('workflowTagsDisabled') && savedWorkflow.tags) {
|
||||||
savedWorkflow.tags = TagHelpers.sortByRequestOrder(savedWorkflow.tags, {
|
savedWorkflow.tags = Container.get(TagService).sortByRequestOrder(savedWorkflow.tags, {
|
||||||
requestOrder: tagIds,
|
requestOrder: tagIds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { User } from '@db/entities/User';
|
||||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import { validateEntity } from '@/GenericHelpers';
|
import { validateEntity } from '@/GenericHelpers';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import * as TagHelpers from '@/TagHelpers';
|
import { TagService } from '@/services/tag.service';
|
||||||
import type { ListQueryOptions, WorkflowRequest } from '@/requests';
|
import type { ListQueryOptions, WorkflowRequest } from '@/requests';
|
||||||
import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces';
|
import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
|
@ -299,7 +299,7 @@ export class WorkflowsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedWorkflow.tags?.length && tagIds?.length) {
|
if (updatedWorkflow.tags?.length && tagIds?.length) {
|
||||||
updatedWorkflow.tags = TagHelpers.sortByRequestOrder(updatedWorkflow.tags, {
|
updatedWorkflow.tags = Container.get(TagService).sortByRequestOrder(updatedWorkflow.tags, {
|
||||||
requestOrder: tagIds,
|
requestOrder: tagIds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue