Skip to content

Commit ac91020

Browse files
authored
feat(core): Use active version instead of current version (no-changelog) (#21202)
1 parent c734897 commit ac91020

File tree

126 files changed

+2534
-394
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+2534
-394
lines changed

packages/@n8n/backend-test-utils/src/db/workflows.ts

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ export async function createManyWorkflows(
8989
return await Promise.all(workflowRequests);
9090
}
9191

92+
export async function createManyActiveWorkflows(
93+
amount: number,
94+
attributes: Partial<IWorkflowDb> = {},
95+
userOrProject?: User | Project,
96+
) {
97+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
98+
const workflowRequests = [...Array(amount)].map(
99+
async (_) => await createActiveWorkflow(attributes, userOrProject),
100+
);
101+
return await Promise.all(workflowRequests);
102+
}
103+
92104
export async function shareWorkflowWithUsers(workflow: IWorkflowBase, users: User[]) {
93105
const sharedWorkflows: Array<DeepPartial<SharedWorkflow>> = await Promise.all(
94106
users.map(async (user) => {
@@ -135,7 +147,7 @@ export async function getWorkflowSharing(workflow: IWorkflowBase) {
135147
*/
136148
export async function createWorkflowWithTrigger(
137149
attributes: Partial<IWorkflowDb> = {},
138-
user?: User,
150+
userOrProject?: User | Project,
139151
) {
140152
const workflow = await createWorkflow(
141153
{
@@ -170,7 +182,7 @@ export async function createWorkflowWithTrigger(
170182
},
171183
...attributes,
172184
},
173-
user,
185+
userOrProject,
174186
);
175187

176188
return workflow;
@@ -201,12 +213,12 @@ export async function createWorkflowWithHistory(
201213
*/
202214
export async function createWorkflowWithTriggerAndHistory(
203215
attributes: Partial<IWorkflowDb> = {},
204-
user?: User,
216+
userOrProject?: User | Project,
205217
) {
206-
const workflow = await createWorkflowWithTrigger(attributes, user);
218+
const workflow = await createWorkflowWithTrigger(attributes, userOrProject);
207219

208220
// Create workflow history for the initial version
209-
await createWorkflowHistory(workflow, user);
221+
await createWorkflowHistory(workflow, userOrProject);
210222

211223
return workflow;
212224
}
@@ -227,12 +239,78 @@ export const getWorkflowById = async (id: string) =>
227239
* @param workflow workflow to create history for
228240
* @param user user who created the version (optional)
229241
*/
230-
export async function createWorkflowHistory(workflow: IWorkflowDb, user?: User): Promise<void> {
242+
export async function createWorkflowHistory(
243+
workflow: IWorkflowDb,
244+
userOrProject?: User | Project,
245+
): Promise<void> {
231246
await Container.get(WorkflowHistoryRepository).insert({
232247
workflowId: workflow.id,
233248
versionId: workflow.versionId,
234249
nodes: workflow.nodes,
235250
connections: workflow.connections,
251+
authors: userOrProject instanceof User ? userOrProject.email : '[email protected]',
252+
});
253+
}
254+
255+
/**
256+
* Set the active version for a workflow
257+
* @param workflowId workflow ID
258+
* @param versionId version ID to set as active
259+
*/
260+
export async function setActiveVersion(workflowId: string, versionId: string): Promise<void> {
261+
await Container.get(WorkflowRepository)
262+
.createQueryBuilder()
263+
.update()
264+
.set({ activeVersionId: versionId })
265+
.where('id = :workflowId', { workflowId })
266+
.execute();
267+
}
268+
269+
/**
270+
* Create an active workflow with trigger, history, and activeVersionId set to the current version.
271+
* This simulates a workflow that has been activated and is running.
272+
* @param attributes workflow attributes
273+
* @param user user to assign the workflow to
274+
*/
275+
export async function createActiveWorkflow(
276+
attributes: Partial<IWorkflowDb> = {},
277+
userOrProject?: User | Project,
278+
) {
279+
const workflow = await createWorkflowWithTriggerAndHistory(
280+
{ active: true, ...attributes },
281+
userOrProject,
282+
);
283+
284+
await setActiveVersion(workflow.id, workflow.versionId);
285+
286+
workflow.activeVersionId = workflow.versionId;
287+
return workflow;
288+
}
289+
290+
/**
291+
* Create a workflow with a specific active version.
292+
* This simulates a workflow where the active version differs from the current version.
293+
* @param activeVersionId the version ID to set as active
294+
* @param attributes workflow attributes
295+
* @param user user to assign the workflow to
296+
*/
297+
export async function createWorkflowWithActiveVersion(
298+
activeVersionId: string,
299+
attributes: Partial<IWorkflowDb> = {},
300+
user?: User,
301+
) {
302+
const workflow = await createWorkflowWithTriggerAndHistory({ active: true, ...attributes }, user);
303+
304+
await Container.get(WorkflowHistoryRepository).insert({
305+
workflowId: workflow.id,
306+
versionId: activeVersionId,
307+
nodes: workflow.nodes,
308+
connections: workflow.connections,
236309
authors: user?.email ?? '[email protected]',
237310
});
311+
312+
await setActiveVersion(workflow.id, activeVersionId);
313+
314+
workflow.activeVersionId = activeVersionId;
315+
return workflow;
238316
}

packages/@n8n/backend-test-utils/src/test-db.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,44 @@ type EntityName =
9494
*/
9595
export async function truncate(entities: EntityName[]) {
9696
const connection = Container.get(Connection);
97+
const dbType = connection.options.type;
9798

98-
for (const name of entities) {
99-
await connection.getRepository(name).delete({});
99+
// Disable FK checks for MySQL/MariaDB to handle circular dependencies
100+
if (dbType === 'mysql' || dbType === 'mariadb') {
101+
await connection.query('SET FOREIGN_KEY_CHECKS=0');
102+
}
103+
104+
try {
105+
// Collect junction tables to clean
106+
const junctionTablesToClean = new Set<string>();
107+
108+
// Find all junction tables associated with the entities being truncated
109+
for (const name of entities) {
110+
try {
111+
const metadata = connection.getMetadata(name);
112+
for (const relation of metadata.manyToManyRelations) {
113+
if (relation.junctionEntityMetadata) {
114+
const junctionTableName = relation.junctionEntityMetadata.tablePath;
115+
junctionTablesToClean.add(junctionTableName);
116+
}
117+
}
118+
} catch (error) {
119+
// Skip
120+
}
121+
}
122+
123+
// Clean junction tables first (since they reference the entities)
124+
for (const tableName of junctionTablesToClean) {
125+
await connection.query(`DELETE FROM ${tableName}`);
126+
}
127+
128+
for (const name of entities) {
129+
await connection.getRepository(name).delete({});
130+
}
131+
} finally {
132+
// Re-enable FK checks
133+
if (dbType === 'mysql' || dbType === 'mariadb') {
134+
await connection.query('SET FOREIGN_KEY_CHECKS=1');
135+
}
100136
}
101137
}

packages/@n8n/db/src/entities/types-db.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type { SharedWorkflow } from './shared-workflow';
2626
import type { TagEntity } from './tag-entity';
2727
import type { User } from './user';
2828
import type { WorkflowEntity } from './workflow-entity';
29+
import type { WorkflowHistory } from './workflow-history';
2930

3031
export type UsageCount = {
3132
usageCount: number;
@@ -79,6 +80,7 @@ export interface IWorkflowDb extends IWorkflowBase {
7980
triggerCount: number;
8081
tags?: TagEntity[];
8182
parentFolder?: Folder | null;
83+
activeVersion?: WorkflowHistory | null;
8284
}
8385

8486
export interface ICredentialsDb extends ICredentialsBase, ICredentialsEncrypted {
@@ -221,6 +223,7 @@ export namespace ListQueryDb {
221223
| 'name'
222224
| 'active'
223225
| 'versionId'
226+
| 'activeVersionId'
224227
| 'createdAt'
225228
| 'updatedAt'
226229
| 'tags'

packages/@n8n/db/src/entities/workflow-entity.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type { SharedWorkflow } from './shared-workflow';
1818
import type { TagEntity } from './tag-entity';
1919
import type { TestRun } from './test-run.ee';
2020
import type { ISimplifiedPinData, IWorkflowDb } from './types-db';
21+
import type { WorkflowHistory } from './workflow-history';
2122
import type { WorkflowStatistics } from './workflow-statistics';
2223
import type { WorkflowTagMapping } from './workflow-tag-mapping';
2324
import { objectRetriever, sqlite } from '../utils/transformers';
@@ -103,6 +104,13 @@ export class WorkflowEntity extends WithTimestampsAndStringId implements IWorkfl
103104
@Column({ length: 36 })
104105
versionId: string;
105106

107+
@Column({ name: 'activeVersionId', length: 36, nullable: true })
108+
activeVersionId: string | null;
109+
110+
@ManyToOne('WorkflowHistory', { nullable: true })
111+
@JoinColumn({ name: 'activeVersionId', referencedColumnName: 'versionId' })
112+
activeVersion: WorkflowHistory | null;
113+
106114
@Column({ default: 1 })
107115
versionCounter: number;
108116

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { MigrationContext, ReversibleMigration } from '../migration-types';
2+
3+
const WORKFLOWS_TABLE_NAME = 'workflow_entity';
4+
const WORKFLOW_HISTORY_TABLE_NAME = 'workflow_history';
5+
6+
export class AddActiveVersionIdColumn1763047800000 implements ReversibleMigration {
7+
async up({
8+
schemaBuilder: { addColumns, column, addForeignKey },
9+
queryRunner,
10+
escape,
11+
}: MigrationContext) {
12+
const workflowsTableName = escape.tableName(WORKFLOWS_TABLE_NAME);
13+
14+
await addColumns(WORKFLOWS_TABLE_NAME, [column('activeVersionId').varchar(36)]);
15+
16+
await addForeignKey(
17+
WORKFLOWS_TABLE_NAME,
18+
'activeVersionId',
19+
[WORKFLOW_HISTORY_TABLE_NAME, 'versionId'],
20+
undefined,
21+
'RESTRICT',
22+
);
23+
24+
// For existing ACTIVE workflows, set activeVersionId = versionId
25+
const versionIdColumn = escape.columnName('versionId');
26+
const activeColumn = escape.columnName('active');
27+
const activeVersionIdColumn = escape.columnName('activeVersionId');
28+
29+
await queryRunner.query(
30+
`UPDATE ${workflowsTableName}
31+
SET ${activeVersionIdColumn} = ${versionIdColumn}
32+
WHERE ${activeColumn} = true`,
33+
);
34+
}
35+
36+
async down({ schemaBuilder: { dropColumns, dropForeignKey } }: MigrationContext) {
37+
await dropForeignKey(WORKFLOWS_TABLE_NAME, 'activeVersionId', [
38+
WORKFLOW_HISTORY_TABLE_NAME,
39+
'versionId',
40+
]);
41+
await dropColumns(WORKFLOWS_TABLE_NAME, ['activeVersionId']);
42+
}
43+
}

packages/@n8n/db/src/migrations/mysqldb/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { AddMfaColumns1690000000030 } from './../common/1690000000040-AddMfaColumns';
2-
import { AddWorkflowHistoryAutoSaveFields1762847206508 } from './../common/1762847206508-AddWorkflowHistoryAutoSaveFields';
31
import { InitialMigration1588157391238 } from './1588157391238-InitialMigration';
42
import { WebhookModel1592447867632 } from './1592447867632-WebhookModel';
53
import { CreateIndexStoppedAt1594902918301 } from './1594902918301-CreateIndexStoppedAt';
@@ -55,6 +53,7 @@ import { AddToolsColumnToChatHubTables1761830340990 } from './1761830340990-AddT
5553
import { CreateLdapEntities1674509946020 } from '../common/1674509946020-CreateLdapEntities';
5654
import { PurgeInvalidWorkflowConnections1675940580449 } from '../common/1675940580449-PurgeInvalidWorkflowConnections';
5755
import { RemoveResetPasswordColumns1690000000030 } from '../common/1690000000030-RemoveResetPasswordColumns';
56+
import { AddMfaColumns1690000000030 } from '../common/1690000000040-AddMfaColumns';
5857
import { CreateWorkflowNameIndex1691088862123 } from '../common/1691088862123-CreateWorkflowNameIndex';
5958
import { CreateWorkflowHistoryTable1692967111175 } from '../common/1692967111175-CreateWorkflowHistoryTable';
6059
import { ExecutionSoftDelete1693491613982 } from '../common/1693491613982-ExecutionSoftDelete';
@@ -114,6 +113,8 @@ import { CreateWorkflowDependencyTable1760314000000 } from '../common/1760314000
114113
import { AddAttachmentsToChatHubMessages1761773155024 } from '../common/1761773155024-AddAttachmentsToChatHubMessages';
115114
import { AddWorkflowDescriptionColumn1762177736257 } from '../common/1762177736257-AddWorkflowDescriptionColumn';
116115
import { BackfillMissingWorkflowHistoryRecords1762763704614 } from '../common/1762763704614-BackfillMissingWorkflowHistoryRecords';
116+
import { AddWorkflowHistoryAutoSaveFields1762847206508 } from '../common/1762847206508-AddWorkflowHistoryAutoSaveFields';
117+
import { AddActiveVersionIdColumn1763047800000 } from '../common/1763047800000-AddActiveVersionIdColumn';
117118
import { ChangeOAuthStateColumnToUnboundedVarchar1763572724000 } from '../common/1763572724000-ChangeOAuthStateColumnToUnboundedVarchar';
118119
import type { Migration } from '../migration-types';
119120

@@ -235,4 +236,5 @@ export const mysqlMigrations: Migration[] = [
235236
AddToolsColumnToChatHubTables1761830340990,
236237
ChangeOAuthStateColumnToUnboundedVarchar1763572724000,
237238
AddAttachmentsToChatHubMessages1761773155024,
239+
AddActiveVersionIdColumn1763047800000,
238240
];

packages/@n8n/db/src/migrations/postgresdb/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ import { AddToolsColumnToChatHubTables1761830340990 } from '../common/1761830340
114114
import { AddWorkflowDescriptionColumn1762177736257 } from '../common/1762177736257-AddWorkflowDescriptionColumn';
115115
import { BackfillMissingWorkflowHistoryRecords1762763704614 } from '../common/1762763704614-BackfillMissingWorkflowHistoryRecords';
116116
import { AddWorkflowHistoryAutoSaveFields1762847206508 } from '../common/1762847206508-AddWorkflowHistoryAutoSaveFields';
117+
import { AddActiveVersionIdColumn1763047800000 } from '../common/1763047800000-AddActiveVersionIdColumn';
117118
import { ChangeOAuthStateColumnToUnboundedVarchar1763572724000 } from '../common/1763572724000-ChangeOAuthStateColumnToUnboundedVarchar';
118119
import type { Migration } from '../migration-types';
119120

@@ -235,4 +236,5 @@ export const postgresMigrations: Migration[] = [
235236
AddToolsColumnToChatHubTables1761830340990,
236237
ChangeOAuthStateColumnToUnboundedVarchar1763572724000,
237238
AddAttachmentsToChatHubMessages1761773155024,
239+
AddActiveVersionIdColumn1763047800000,
238240
];

packages/@n8n/db/src/migrations/sqlite/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import { AddToolsColumnToChatHubTables1761830340990 } from '../common/1761830340
110110
import { AddWorkflowDescriptionColumn1762177736257 } from '../common/1762177736257-AddWorkflowDescriptionColumn';
111111
import { BackfillMissingWorkflowHistoryRecords1762763704614 } from '../common/1762763704614-BackfillMissingWorkflowHistoryRecords';
112112
import { AddWorkflowHistoryAutoSaveFields1762847206508 } from '../common/1762847206508-AddWorkflowHistoryAutoSaveFields';
113+
import { AddActiveVersionIdColumn1763047800000 } from '../common/1763047800000-AddActiveVersionIdColumn';
113114
import { ChangeOAuthStateColumnToUnboundedVarchar1763572724000 } from '../common/1763572724000-ChangeOAuthStateColumnToUnboundedVarchar';
114115
import type { Migration } from '../migration-types';
115116

@@ -227,6 +228,7 @@ const sqliteMigrations: Migration[] = [
227228
AddToolsColumnToChatHubTables1761830340990,
228229
ChangeOAuthStateColumnToUnboundedVarchar1763572724000,
229230
AddAttachmentsToChatHubMessages1761773155024,
231+
AddActiveVersionIdColumn1763047800000,
230232
];
231233

232234
export { sqliteMigrations };

packages/@n8n/db/src/repositories/__tests__/workflow.repository.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,7 @@ describe('WorkflowRepository', () => {
249249
}),
250250
);
251251

252-
expect(queryBuilder.andWhere).toHaveBeenCalledWith('workflow.active = :active', {
253-
active: true,
254-
});
252+
expect(queryBuilder.andWhere).toHaveBeenCalledWith('workflow.activeVersionId IS NOT NULL');
255253

256254
expect(queryBuilder.innerJoin).toHaveBeenCalledWith('workflow.shared', 'shared');
257255
expect(queryBuilder.andWhere).toHaveBeenCalledWith('shared.projectId = :projectId', {

packages/@n8n/db/src/repositories/license-metrics.repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class LicenseMetricsRepository extends Repository<LicenseMetrics> {
5858
SELECT
5959
(SELECT COUNT(*) FROM ${userTable} WHERE disabled = false) AS enabled_user_count,
6060
(SELECT COUNT(*) FROM ${userTable}) AS total_user_count,
61-
(SELECT COUNT(*) FROM ${workflowTable} WHERE active = true) AS active_workflow_count,
61+
(SELECT COUNT(*) FROM ${workflowTable} WHERE ${this.toColumnName('activeVersionId')} IS NOT NULL) AS active_workflow_count,
6262
(SELECT COUNT(*) FROM ${workflowTable}) AS total_workflow_count,
6363
(SELECT COUNT(*) FROM ${credentialTable}) AS total_credentials_count,
6464
(SELECT SUM(count) FROM ${workflowStatsTable} WHERE name IN ('production_success', 'production_error')) AS production_executions_count,

0 commit comments

Comments
 (0)