feat(core): Add migration to create model for folders feature (#13060)

This commit is contained in:
Ricardo Espinoza 2025-02-12 08:51:04 -05:00 committed by GitHub
parent aedea7a76c
commit 03f4ed8445
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 147 additions and 1 deletions

View file

@ -0,0 +1,45 @@
import {
Column,
Entity,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
} from '@n8n/typeorm';
import { WithTimestampsAndStringId } from './abstract-entity';
import { Project } from './project';
import { TagEntity } from './tag-entity';
import { type WorkflowEntity } from './workflow-entity';
@Entity()
export class Folder extends WithTimestampsAndStringId {
@Column()
name: string;
@ManyToOne(() => Folder, { nullable: true })
@JoinColumn({ name: 'parentFolderId' })
parentFolder: Folder | null;
@ManyToOne(() => Project)
@JoinColumn({ name: 'projectId' })
project: Project;
@OneToMany('WorkflowEntity', 'parentFolder')
workflows: WorkflowEntity[];
@ManyToMany(() => TagEntity)
@JoinTable({
name: 'folder_tag',
joinColumn: {
name: 'folderId',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'tagId',
referencedColumnName: 'id',
},
})
tags: TagEntity[];
}

View file

@ -10,6 +10,7 @@ import { ExecutionAnnotation } from './execution-annotation.ee';
import { ExecutionData } from './execution-data';
import { ExecutionEntity } from './execution-entity';
import { ExecutionMetadata } from './execution-metadata';
import { Folder } from './folder';
import { InstalledNodes } from './installed-nodes';
import { InstalledPackages } from './installed-packages';
import { InvalidAuthToken } from './invalid-auth-token';
@ -66,4 +67,5 @@ export const entities = {
TestMetric,
TestRun,
TestCaseExecution,
Folder,
};

View file

@ -1,4 +1,13 @@
import { Column, Entity, Index, JoinColumn, JoinTable, ManyToMany, OneToMany } from '@n8n/typeorm';
import {
Column,
Entity,
Index,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
} from '@n8n/typeorm';
import { Length } from 'class-validator';
import { IConnections, IDataObject, IWorkflowSettings, WorkflowFEMeta } from 'n8n-workflow';
import type { IBinaryKeyData, INode, IPairedItemData } from 'n8n-workflow';
@ -6,6 +15,7 @@ import type { IBinaryKeyData, INode, IPairedItemData } from 'n8n-workflow';
import type { IWorkflowDb } from '@/interfaces';
import { WithTimestampsAndStringId, dbType, jsonColumnType } from './abstract-entity';
import { type Folder } from './folder';
import type { SharedWorkflow } from './shared-workflow';
import type { TagEntity } from './tag-entity';
import type { WorkflowStatistics } from './workflow-statistics';
@ -88,6 +98,13 @@ export class WorkflowEntity extends WithTimestampsAndStringId implements IWorkfl
@Column({ default: 0 })
triggerCount: number;
@ManyToOne('Folder', 'workflows', {
nullable: true,
onDelete: 'SET NULL',
})
@JoinColumn({ name: 'parentFolderId' })
parentFolder: Folder | null;
display() {
return `"${this.name}" (ID: ${this.id})`;
}

View file

@ -0,0 +1,60 @@
import type { MigrationContext, ReversibleMigration } from '@/databases/types';
export class CreateFolderTable1738709609940 implements ReversibleMigration {
async up({ runQuery, escape, schemaBuilder: { createTable, column } }: MigrationContext) {
const workflowTable = escape.tableName('workflow_entity');
const workflowFolderId = escape.columnName('parentFolderId');
const folderTable = escape.tableName('folder');
const folderId = escape.columnName('id');
await createTable('folder')
.withColumns(
column('id').varchar(36).primary.notNull,
column('name').varchar(128).notNull,
column('parentFolderId').varchar(36).default(null),
column('projectId').varchar(36).notNull,
)
.withForeignKey('projectId', {
tableName: 'project',
columnName: 'id',
onDelete: 'CASCADE',
})
.withForeignKey('parentFolderId', {
tableName: 'folder',
columnName: 'id',
onDelete: 'CASCADE',
})
.withIndexOn(['projectId', 'id'], true).withTimestamps;
await createTable('folder_tag')
.withColumns(
column('folderId').varchar(36).primary.notNull,
column('tagId').varchar(36).primary.notNull,
)
.withForeignKey('folderId', {
tableName: 'folder',
columnName: 'id',
onDelete: 'CASCADE',
})
.withForeignKey('tagId', {
tableName: 'tag_entity',
columnName: 'id',
onDelete: 'CASCADE',
});
await runQuery(
`ALTER TABLE ${workflowTable} ADD COLUMN ${workflowFolderId} VARCHAR(36) DEFAULT NULL REFERENCES ${folderTable}(${folderId}) ON DELETE SET NULL`,
);
}
async down({ runQuery, escape, schemaBuilder: { dropTable } }: MigrationContext) {
const workflowTable = escape.tableName('workflow_entity');
const workflowFolderId = escape.columnName('parentFolderId');
await runQuery(`ALTER TABLE ${workflowTable} DROP COLUMN ${workflowFolderId}`);
await dropTable('folder_tag');
await dropTable('folder');
}
}

View file

@ -79,6 +79,7 @@ import { AddManagedColumnToCredentialsTable1734479635324 } from '../common/17344
import { AddStatsColumnsToTestRun1736172058779 } from '../common/1736172058779-AddStatsColumnsToTestRun';
import { CreateTestCaseExecutionTable1736947513045 } from '../common/1736947513045-CreateTestCaseExecutionTable';
import { AddErrorColumnsToTestRuns1737715421462 } from '../common/1737715421462-AddErrorColumnsToTestRuns';
import { CreateFolderTable1738709609940 } from '../common/1738709609940-CreateFolderTable';
export const mysqlMigrations: Migration[] = [
InitialMigration1588157391238,
@ -160,4 +161,5 @@ export const mysqlMigrations: Migration[] = [
AddStatsColumnsToTestRun1736172058779,
CreateTestCaseExecutionTable1736947513045,
AddErrorColumnsToTestRuns1737715421462,
CreateFolderTable1738709609940,
];

View file

@ -79,6 +79,7 @@ import { AddManagedColumnToCredentialsTable1734479635324 } from '../common/17344
import { AddStatsColumnsToTestRun1736172058779 } from '../common/1736172058779-AddStatsColumnsToTestRun';
import { CreateTestCaseExecutionTable1736947513045 } from '../common/1736947513045-CreateTestCaseExecutionTable';
import { AddErrorColumnsToTestRuns1737715421462 } from '../common/1737715421462-AddErrorColumnsToTestRuns';
import { CreateFolderTable1738709609940 } from '../common/1738709609940-CreateFolderTable';
export const postgresMigrations: Migration[] = [
InitialMigration1587669153312,
@ -160,4 +161,5 @@ export const postgresMigrations: Migration[] = [
AddStatsColumnsToTestRun1736172058779,
CreateTestCaseExecutionTable1736947513045,
AddErrorColumnsToTestRuns1737715421462,
CreateFolderTable1738709609940,
];

View file

@ -0,0 +1,5 @@
import { CreateFolderTable1738709609940 as BaseMigration } from '../common/1738709609940-CreateFolderTable';
export class CreateFolderTable1738709609940 extends BaseMigration {
transaction = false as const;
}

View file

@ -42,6 +42,7 @@ import { AddMissingPrimaryKeyOnAnnotationTagMapping1728659839644 } from './17286
import { AddProjectIcons1729607673469 } from './1729607673469-AddProjectIcons';
import { AddDescriptionToTestDefinition1731404028106 } from './1731404028106-AddDescriptionToTestDefinition';
import { MigrateTestDefinitionKeyToString1731582748663 } from './1731582748663-MigrateTestDefinitionKeyToString';
import { CreateFolderTable1738709609940 } from './1738709609940-CreateFolderTable';
import { UniqueWorkflowNames1620821879465 } from '../common/1620821879465-UniqueWorkflowNames';
import { UpdateWorkflowCredentials1630330987096 } from '../common/1630330987096-UpdateWorkflowCredentials';
import { AddNodeIds1658930531669 } from '../common/1658930531669-AddNodeIds';
@ -154,6 +155,7 @@ const sqliteMigrations: Migration[] = [
AddStatsColumnsToTestRun1736172058779,
CreateTestCaseExecutionTable1736947513045,
AddErrorColumnsToTestRuns1737715421462,
CreateFolderTable1738709609940,
];
export { sqliteMigrations };

View file

@ -0,0 +1,11 @@
import { Service } from '@n8n/di';
import { DataSource, Repository } from '@n8n/typeorm';
import { Folder } from '../entities/folder';
@Service()
export class FolderRepository extends Repository<Folder> {
constructor(dataSource: DataSource) {
super(Folder, dataSource.manager);
}
}