Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Workflow Execution Statistics #4200

Merged
merged 65 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
42753a3
:tada: - Initial attempt at putting a DB table together
Sep 26, 2022
1d4cba1
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Sep 27, 2022
3c7fc28
:sparkles: - Add statistics to DB.collections
Sep 27, 2022
d78d9ac
:art: - actually run the statistics sqlite migration
Sep 27, 2022
10deae8
:sparkles: - add postgres migration
Sep 27, 2022
f98516c
:bug: - Fixed wrong table in down migrations
Sep 27, 2022
1da308f
:sparkles: - Added mysql migration
Sep 27, 2022
35a24a4
:bug: - Postgres migration tested
Sep 27, 2022
e5098a4
:sparkles: - Add dataLoaded flag to workflow table
Sep 27, 2022
64c945b
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Sep 27, 2022
b239eb4
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Oct 6, 2022
613bc02
:art: - Make latestEvent nullable
Oct 6, 2022
563ff77
:sparkles: - Load current execution counts during migration
Oct 6, 2022
0ff421e
:bug: - Fixed issue with postgres migrations
Oct 10, 2022
b1ad9d0
:art: - Better insertion queries for initial stats
Oct 10, 2022
268862f
:art: - Remove initial counts of executions from migration
Oct 10, 2022
4f401ff
:art: - Implemented change comments
Oct 13, 2022
c1c0aa6
:sparkles: - Add new eventEmitter to core
Oct 13, 2022
e13fe42
:sparkles: - Add workflow finished event handler
Oct 13, 2022
87adfcd
:sparkles: - Trigger workflow finish events
Oct 13, 2022
c217157
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Oct 13, 2022
46e0df1
:art: - Replaced magic strings with key/value pair
Oct 17, 2022
e98c15e
:sparkles: - Add execution counts endpoint to internal api
Oct 20, 2022
f1296ab
:sparkles: - Added timestamp endpoint
Oct 20, 2022
d593e43
:bug: - fix issue where new workflows had null for dataLoaded
Oct 20, 2022
d7b2baf
:sparkles: - Add dataLoaded endpoint
Oct 20, 2022
e671459
:sparkles: - Add nodefetcheddata event handling
Oct 28, 2022
793d40d
:sparkles: - set data loaded flag
Nov 1, 2022
af92711
:sparkles: - workflow exec stats now have user id
Nov 2, 2022
0830fb4
:sparkles: - Add first prod success to posthog
Nov 3, 2022
6f36b54
:sparkles: - Pass node to nodeFetchedData event
Nov 3, 2022
91bac53
:sparkles: - sending node fetch metrics to posthog
Nov 7, 2022
97ac970
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Nov 7, 2022
c0a124b
fix existing tests
Nov 8, 2022
b9dd237
:sparkles: - Added tests for event handler code
Nov 9, 2022
1d0a717
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Nov 9, 2022
d256362
:hammer: - Fixing config imports
Nov 9, 2022
db68f90
Merge branch 'master' into feature/workflow-execution-statistics
krynble Nov 10, 2022
36141f9
:hammer: - fixed update issue for mysql
Nov 10, 2022
25a8dd8
:bug: - Fix first event having null timestamp
Nov 11, 2022
d74cba5
:bug: - Fix issue where creation timestamps were null
Nov 14, 2022
6a205e0
:sparkles: - Add permission checking to stats apis
Nov 14, 2022
633d335
Merge branch 'feature/workflow-execution-statistics' of github.com:n8…
krynble Nov 15, 2022
f116327
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Nov 25, 2022
446acf5
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Nov 28, 2022
152169a
bring files up to date with recent refactors
Nov 28, 2022
84c195d
:arrow_up: - Updating posthog-node to latest
Nov 29, 2022
3d53b55
:sparkles: - Ensuring backend pushes feat flags to PH
Nov 29, 2022
c02060b
:bug: - use getsharedworkflow function for checking permissions in api
Nov 30, 2022
d2cf259
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Nov 30, 2022
ae8a136
:rotating_light: - fixing slight issue in tests
Nov 30, 2022
4af5c2c
:rotating_light: - more test fixes
Nov 30, 2022
8f4772f
Merge branch 'feature/workflow-execution-statistics' of github.com:n8…
krynble Dec 1, 2022
d7af982
fix: Remove commented import
krynble Dec 1, 2022
407c146
:hammer: - moved changeWorkflowID to middleware
Dec 6, 2022
a83c64d
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Dec 6, 2022
53a1fe2
Merge branch 'feature/workflow-execution-statistics' of github.com:n8…
Dec 6, 2022
637018d
Merge branch 'master' of github.com:n8n-io/n8n into feature/workflow-…
Dec 6, 2022
7617ed0
:shirt: - clean up lint errors
Dec 6, 2022
7260b01
:shirt: - accidental typo
Dec 6, 2022
8dbd8f8
:hammer: - Made checkWorkflowId work as middleware
Dec 6, 2022
3698218
:bug: - next doesn't halt execution
Dec 6, 2022
c910933
refactor: add brackets to improve legibility
krynble Dec 6, 2022
850fbb8
:shirt: - remove the name `err`
Dec 6, 2022
c3a2a74
Merge branch 'feature/workflow-execution-statistics' of github.com:n8…
Dec 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/src/Db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export async function init(
collections.Settings = linkRepository(entities.Settings);
collections.InstalledPackages = linkRepository(entities.InstalledPackages);
collections.InstalledNodes = linkRepository(entities.InstalledNodes);
collections.WorkflowStatistics = linkRepository(entities.WorkflowStatistics);

isInitialized = true;

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import type { SharedWorkflow } from './databases/entities/SharedWorkflow';
import type { TagEntity } from './databases/entities/TagEntity';
import type { User } from './databases/entities/User';
import type { WorkflowEntity } from './databases/entities/WorkflowEntity';
import { WorkflowStatistics } from './databases/entities/WorkflowStatistics';

export interface IActivationError {
time: number;
Expand Down Expand Up @@ -83,6 +84,7 @@ export interface IDatabaseCollections {
Settings: Repository<Settings>;
InstalledPackages: Repository<InstalledPackages>;
InstalledNodes: Repository<InstalledNodes>;
WorkflowStatistics: Repository<WorkflowStatistics>;
}

export interface IWebhookDb {
Expand Down
12 changes: 12 additions & 0 deletions packages/cli/src/databases/entities/WorkflowEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
CreateDateColumn,
Entity,
Index,
JoinColumn,
JoinTable,
ManyToMany,
OneToMany,
Expand All @@ -30,6 +31,7 @@ import { DatabaseType, IWorkflowDb } from '../..';
import { TagEntity } from './TagEntity';
import { SharedWorkflow } from './SharedWorkflow';
import { objectRetriever, sqlite } from '../utils/transformers';
import { WorkflowStatistics } from './WorkflowStatistics';

function resolveDataType(dataType: string) {
const dbType = config.getEnv('database.type');
Expand Down Expand Up @@ -124,6 +126,16 @@ export class WorkflowEntity implements IWorkflowDb {
@OneToMany(() => SharedWorkflow, (sharedWorkflow) => sharedWorkflow.workflow)
shared: SharedWorkflow[];

@OneToMany(
() => WorkflowStatistics,
(workflowStatistics: WorkflowStatistics) => workflowStatistics.workflow,
)
@JoinColumn({ referencedColumnName: 'workflow' })
statistics: WorkflowStatistics[];

@Column()
dataLoaded: boolean;

@Column({
type: config.getEnv('database.type') === 'sqlite' ? 'text' : 'json',
nullable: true,
Expand Down
44 changes: 44 additions & 0 deletions packages/cli/src/databases/entities/WorkflowStatistics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable import/no-cycle */
import { Column, Entity, RelationId, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm';
import * as config from '../../../config';
import { DatabaseType } from '../..';
import { WorkflowEntity } from './WorkflowEntity';

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
function getTimestampSyntax() {
const dbType = config.getEnv('database.type');

const map: { [key in DatabaseType]: string } = {
sqlite: "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')",
postgresdb: 'CURRENT_TIMESTAMP(3)',
mysqldb: 'CURRENT_TIMESTAMP(3)',
mariadb: 'CURRENT_TIMESTAMP(3)',
};

return map[dbType];
}

@Entity()
export class WorkflowStatistics {
@Column()
count: number;

@UpdateDateColumn({
precision: 3,
default: () => getTimestampSyntax(),
onUpdate: getTimestampSyntax(),
})
latestEvent: Date;

@PrimaryColumn({ length: 128 })
freyamade marked this conversation as resolved.
Show resolved Hide resolved
name: string;

@ManyToOne(() => WorkflowEntity, (workflow) => workflow.shared, {
primary: true,
onDelete: 'CASCADE',
})
workflow: WorkflowEntity;

@RelationId((workflowStatistics: WorkflowStatistics) => workflowStatistics.workflow)
workflowId: number;
}
2 changes: 2 additions & 0 deletions packages/cli/src/databases/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SharedWorkflow } from './SharedWorkflow';
import { SharedCredentials } from './SharedCredentials';
import { InstalledPackages } from './InstalledPackages';
import { InstalledNodes } from './InstalledNodes';
import { WorkflowStatistics } from './WorkflowStatistics';

export const entities = {
CredentialsEntity,
Expand All @@ -26,4 +27,5 @@ export const entities = {
SharedCredentials,
InstalledPackages,
InstalledNodes,
WorkflowStatistics,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import { logMigrationEnd, logMigrationStart } from '../../utils/migrationHelpers';
import config from '../../../../config';

export class WorkflowStatistics1664196174002 implements MigrationInterface {
name = 'WorkflowStatistics1664196174002';

async up(queryRunner: QueryRunner): Promise<void> {
logMigrationStart(this.name);

const tablePrefix = config.getEnv('database.tablePrefix');

await queryRunner.query(
`CREATE TABLE ${tablePrefix}workflow_statistics (
count INTEGER DEFAULT 0,
latestEvent DATETIME,
name VARCHAR(128) NOT NULL,
workflowId INTEGER,
PRIMARY KEY(workflowId, name),
FOREIGN KEY(workflowId) REFERENCES ${tablePrefix}workflow_entity(id) ON DELETE CASCADE
)`,
);

// Add dataLoaded column to workflow table
await queryRunner.query(
`ALTER TABLE ${tablePrefix}workflow_entity ADD COLUMN dataLoaded BOOLEAN DEFAULT false`,
);

// Fetch data from executions table to populate statistics table
await queryRunner.query(
`INSERT INTO ${tablePrefix}workflow_statistics (count, latestEvent, name, workflowId)
SELECT
COUNT("id") as "count",
COALESCE(MAX("stoppedAt"), MAX("startedAt")) as "latestEvent",
CASE WHEN "finished" = true THEN 'production_success' ELSE 'production_error' END as "name",
CAST ("workflowId" AS UNSIGNED) AS "workflowId"
FROM ${tablePrefix}execution_entity
WHERE "workflowId" IS NOT NULL
AND mode != 'manual'
GROUP BY "workflowId", "finished"
ORDER BY "workflowId";`,
);
await queryRunner.query(
`INSERT INTO ${tablePrefix}workflow_statistics (count, latestEvent, name, workflowId)
SELECT
COUNT("id") as "count",
COALESCE(MAX("stoppedAt"), MAX("startedAt")) as "latestEvent",
CASE WHEN "finished" = true THEN 'production_success' ELSE 'production_error' END as "name",
CAST ("workflowId" AS UNSIGNED) AS "workflowId"
FROM ${tablePrefix}execution_entity
WHERE "workflowId" IS NOT NULL
AND mode = 'manual'
GROUP BY "workflowId", "finished"
ORDER BY "workflowId";`,
);

logMigrationEnd(this.name);
}

async down(queryRunner: QueryRunner): Promise<void> {
const tablePrefix = config.getEnv('database.tablePrefix');

await queryRunner.query(`DROP TABLE "${tablePrefix}workflow_statistics"`);
await queryRunner.query(`ALTER TABLE ${tablePrefix}workflow_entity DROP COLUMN dataLoaded`);
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/databases/migrations/mysqldb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IntroducePinData1654090101303 } from './1654090101303-IntroducePinData'
import { AddNodeIds1658932910559 } from './1658932910559-AddNodeIds';
import { AddJsonKeyPinData1659895550980 } from './1659895550980-AddJsonKeyPinData';
import { CreateCredentialsUserRole1660062385367 } from './1660062385367-CreateCredentialsUserRole';
import { WorkflowStatistics1664196174002 } from './1664196174002-WorkflowStatistics';

export const mysqlMigrations = [
InitialMigration1588157391238,
Expand All @@ -44,4 +45,5 @@ export const mysqlMigrations = [
AddNodeIds1658932910559,
AddJsonKeyPinData1659895550980,
CreateCredentialsUserRole1660062385367,
WorkflowStatistics1664196174002,
freyamade marked this conversation as resolved.
Show resolved Hide resolved
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import { logMigrationEnd, logMigrationStart } from '../../utils/migrationHelpers';
import config from '../../../../config';

export class WorkflowStatistics1664196174001 implements MigrationInterface {
name = 'WorkflowStatistics1664196174001';

async up(queryRunner: QueryRunner): Promise<void> {
logMigrationStart(this.name);

let tablePrefix = config.getEnv('database.tablePrefix');
const schema = config.getEnv('database.postgresdb.schema');
if (schema) {
tablePrefix = schema + '.' + tablePrefix;
}
freyamade marked this conversation as resolved.
Show resolved Hide resolved

// Create statistics table
await queryRunner.query(
`CREATE TABLE ${tablePrefix}workflow_statistics (
"count" INTEGER DEFAULT 0,
"latestEvent" DATE,
"name" VARCHAR(128) NOT NULL,
"workflowId" INTEGER,
PRIMARY KEY("workflowId", "name"),
FOREIGN KEY("workflowId") REFERENCES ${tablePrefix}workflow_entity("id") ON DELETE CASCADE
)`,
);

// Add dataLoaded column to workflow table
await queryRunner.query(
`ALTER TABLE ${tablePrefix}workflow_entity ADD COLUMN "dataLoaded" BOOLEAN DEFAULT false;`,
);

// Fetch data from executions table to populate statistics table
await queryRunner.query(
`INSERT INTO ${tablePrefix}workflow_statistics ("count", "latestEvent", "name", "workflowId")
SELECT
COUNT("id") as "count",
COALESCE(MAX("stoppedAt"), MAX("startedAt")) as "latestEvent",
CASE WHEN "finished" = true THEN 'production_success' ELSE 'production_error' END as "name",
CAST ("workflowId" AS INTEGER) AS "workflowId"
FROM ${tablePrefix}execution_entity
WHERE "workflowId" IS NOT NULL
AND mode != 'manual'
GROUP BY "workflowId", "finished"
ORDER BY "workflowId";`,
);
await queryRunner.query(
`INSERT INTO ${tablePrefix}workflow_statistics ("count", "latestEvent", "name", "workflowId")
SELECT
COUNT("id") as "count",
COALESCE(MAX("stoppedAt"), MAX("startedAt")) as "latestEvent",
CASE WHEN "finished" = true THEN 'production_success' ELSE 'production_error' END as "name",
CAST ("workflowId" AS INTEGER) AS "workflowId"
FROM ${tablePrefix}execution_entity
WHERE "workflowId" IS NOT NULL
AND mode == 'manual'
GROUP BY "workflowId", "finished"
ORDER BY "workflowId";`,
);

logMigrationEnd(this.name);
}

async down(queryRunner: QueryRunner): Promise<void> {
let tablePrefix = config.getEnv('database.tablePrefix');
const schema = config.getEnv('database.postgresdb.schema');
if (schema) {
tablePrefix = schema + '.' + tablePrefix;
}

await queryRunner.query(`DROP TABLE ${tablePrefix}workflow_statistics`);
await queryRunner.query(`ALTER TABLE ${tablePrefix}workflow_entity DROP COLUMN dataLoaded`);
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/databases/migrations/postgresdb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { IntroducePinData1654090467022 } from './1654090467022-IntroducePinData'
import { AddNodeIds1658932090381 } from './1658932090381-AddNodeIds';
import { AddJsonKeyPinData1659902242948 } from './1659902242948-AddJsonKeyPinData';
import { CreateCredentialsUserRole1660062385367 } from './1660062385367-CreateCredentialsUserRole';
import { WorkflowStatistics1664196174001 } from './1664196174001-WorkflowStatistics';

export const postgresMigrations = [
InitialMigration1587669153312,
Expand All @@ -40,4 +41,5 @@ export const postgresMigrations = [
CreateCredentialsUserRole1660062385367,
AddNodeIds1658932090381,
AddJsonKeyPinData1659902242948,
WorkflowStatistics1664196174001,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import { logMigrationEnd, logMigrationStart } from '../../utils/migrationHelpers';
import config from '../../../../config';

export class WorkflowStatistics1664196174000 implements MigrationInterface {
name = 'WorkflowStatistics1664196174000';

async up(queryRunner: QueryRunner): Promise<void> {
logMigrationStart(this.name);

const tablePrefix = config.getEnv('database.tablePrefix');

await queryRunner.query(
`CREATE TABLE \`${tablePrefix}workflow_statistics\` (
"count" INTEGER DEFAULT 0,
"latestEvent" DATETIME,
freyamade marked this conversation as resolved.
Show resolved Hide resolved
"name" VARCHAR(128) NOT NULL,
"workflowId" INTEGER,
PRIMARY KEY("workflowId", "name"),
FOREIGN KEY("workflowId") REFERENCES \`${tablePrefix}workflow_entity\`("id") ON DELETE CASCADE
)`,
);

// Add dataLoaded column to workflow table
await queryRunner.query(
`ALTER TABLE \`${tablePrefix}workflow_entity\` ADD COLUMN "dataLoaded" BOOLEAN DEFAULT false`,
);

// Fetch data from executions table to populate statistics table
await queryRunner.query(
`INSERT INTO \`${tablePrefix}workflow_statistics\` ("count", "latestEvent", "name", "workflowId")
SELECT
COUNT("id") as "count",
IFNULL(MAX("stoppedAt"), MAX("startedAt")) as "latestEvent",
IIF("finished" == 1, 'production_success', 'production_error') as "name",
"workflowId"
FROM \`${tablePrefix}execution_entity\`
WHERE "workflowId" IS NOT NULL
AND mode != 'manual'
GROUP BY "workflowId", "finished"
ORDER BY "workflowId";`,
);
await queryRunner.query(
`INSERT INTO \`${tablePrefix}workflow_statistics\` ("count", "latestEvent", "name", "workflowId")
SELECT
COUNT("id") as "count",
IFNULL(MAX("stoppedAt"), MAX("startedAt")) as "latestEvent",
IIF("finished" == 1, 'production_success', 'production_error') as "name",
"workflowId"
FROM \`${tablePrefix}execution_entity\`
WHERE "workflowId" IS NOT NULL
AND mode == 'manual'
GROUP BY "workflowId", "finished"
ORDER BY "workflowId";`,
);

logMigrationEnd(this.name);
}

async down(queryRunner: QueryRunner): Promise<void> {
const tablePrefix = config.getEnv('database.tablePrefix');

await queryRunner.query(`DROP TABLE "${tablePrefix}workflow_statistics"`);
await queryRunner.query(
`ALTER TABLE \`${tablePrefix}workflow_entity\` DROP COLUMN "dataLoaded"`,
);
}
}
2 changes: 2 additions & 0 deletions packages/cli/src/databases/migrations/sqlite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { IntroducePinData1654089251344 } from './1654089251344-IntroducePinData'
import { AddNodeIds1658930531669 } from './1658930531669-AddNodeIds';
import { AddJsonKeyPinData1659888469333 } from './1659888469333-AddJsonKeyPinData';
import { CreateCredentialsUserRole1660062385367 } from './1660062385367-CreateCredentialsUserRole';
import { WorkflowStatistics1664196174000 } from './1664196174000-WorkflowStatistics';

const sqliteMigrations = [
InitialMigration1588102412422,
Expand All @@ -38,6 +39,7 @@ const sqliteMigrations = [
AddNodeIds1658930531669,
AddJsonKeyPinData1659888469333,
CreateCredentialsUserRole1660062385367,
WorkflowStatistics1664196174000,
];

export { sqliteMigrations };