UNPKG

n8n

Version:

n8n Workflow Automation Tool

264 lines 14.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InsightsByPeriodRepository = void 0; const config_1 = require("@n8n/config"); const di_1 = require("@n8n/di"); const typeorm_1 = require("@n8n/typeorm"); const luxon_1 = require("luxon"); const zod_1 = require("zod"); const sql_1 = require("../../../../utils/sql"); const insights_by_period_1 = require("../entities/insights-by-period"); const insights_shared_1 = require("../entities/insights-shared"); const dbType = di_1.Container.get(config_1.GlobalConfig).database.type; const summaryParser = zod_1.z .object({ period: zod_1.z.enum(['previous', 'current']), type: zod_1.z.union([zod_1.z.literal(0), zod_1.z.literal(1), zod_1.z.literal(2), zod_1.z.literal(3)]), total_value: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]), }) .array(); const aggregatedInsightsByWorkflowParser = zod_1.z .object({ workflowId: zod_1.z.string(), workflowName: zod_1.z.string(), projectId: zod_1.z.string(), projectName: zod_1.z.string(), total: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), succeeded: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), failed: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), failureRate: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), runTime: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), averageRunTime: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), timeSaved: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), }) .array(); const aggregatedInsightsByTimeParser = zod_1.z .object({ periodStart: zod_1.z .union([zod_1.z.date(), zod_1.z.string()]) .transform((value) => value instanceof Date ? value.toISOString() : luxon_1.DateTime.fromSQL(value.toString(), { zone: 'utc' }).toISO()), runTime: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), succeeded: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), failed: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), timeSaved: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).transform((value) => Number(value)), }) .array(); let InsightsByPeriodRepository = class InsightsByPeriodRepository extends typeorm_1.Repository { constructor(dataSource) { super(insights_by_period_1.InsightsByPeriod, dataSource.manager); } escapeField(fieldName) { return this.manager.connection.driver.escape(fieldName); } getPeriodFilterExpr(maxAgeInDays = 0) { let periodStartExpr = `date('now', '-${maxAgeInDays} days')`; if (dbType === 'postgresdb') { periodStartExpr = `CURRENT_DATE - INTERVAL '${maxAgeInDays} day'`; } else if (dbType === 'mysqldb' || dbType === 'mariadb') { periodStartExpr = `DATE_SUB(CURRENT_DATE, INTERVAL ${maxAgeInDays} DAY)`; } return periodStartExpr; } getPeriodStartExpr(periodUnitToCompactInto) { let periodStartExpr = periodUnitToCompactInto === 'week' ? "strftime('%Y-%m-%d 00:00:00.000', date(periodStart, 'weekday 0', '-6 days'))" : `strftime('%Y-%m-%d ${periodUnitToCompactInto === 'hour' ? '%H' : '00'}:00:00.000', periodStart)`; if (dbType === 'mysqldb' || dbType === 'mariadb') { periodStartExpr = periodUnitToCompactInto === 'week' ? "DATE_FORMAT(DATE_SUB(periodStart, INTERVAL WEEKDAY(periodStart) DAY), '%Y-%m-%d 00:00:00')" : `DATE_FORMAT(periodStart, '%Y-%m-%d ${periodUnitToCompactInto === 'hour' ? '%H' : '00'}:00:00')`; } else if (dbType === 'postgresdb') { periodStartExpr = `DATE_TRUNC('${periodUnitToCompactInto}', ${this.escapeField('periodStart')})`; } return periodStartExpr; } getPeriodInsightsBatchQuery({ periodUnitToCompactFrom, compactionBatchSize, maxAgeInDays, }) { const batchQuery = this.createQueryBuilder() .select(['id', 'metaId', 'type', 'periodStart', 'value'].map((fieldName) => this.escapeField(fieldName))) .where(`${this.escapeField('periodUnit')} = ${insights_shared_1.PeriodUnitToNumber[periodUnitToCompactFrom]}`) .andWhere(`${this.escapeField('periodStart')} < ${this.getPeriodFilterExpr(maxAgeInDays)}`) .orderBy(this.escapeField('periodStart'), 'ASC') .limit(compactionBatchSize); return batchQuery; } getAggregationQuery(periodUnit) { const periodStartExpr = this.getPeriodStartExpr(periodUnit); const aggregationQuery = this.manager .createQueryBuilder() .select(this.escapeField('metaId')) .addSelect(this.escapeField('type')) .addSelect(insights_shared_1.PeriodUnitToNumber[periodUnit].toString(), 'periodUnit') .addSelect(periodStartExpr, 'periodStart') .addSelect(`SUM(${this.escapeField('value')})`, 'value') .from('rows_to_compact', 'rtc') .groupBy(this.escapeField('metaId')) .addGroupBy(this.escapeField('type')) .addGroupBy(periodStartExpr); return aggregationQuery; } async compactSourceDataIntoInsightPeriod({ sourceBatchQuery, sourceTableName = this.metadata.tableName, periodUnitToCompactInto, }) { const getBatchAndStoreInTemporaryTable = (0, sql_1.sql) ` CREATE TEMPORARY TABLE rows_to_compact AS ${sourceBatchQuery.getSql()}; `; const countBatch = (0, sql_1.sql) ` SELECT COUNT(*) ${this.escapeField('rowsInBatch')} FROM rows_to_compact; `; const targetColumnNamesStr = ['metaId', 'type', 'periodUnit', 'periodStart'] .map((param) => this.escapeField(param)) .join(', '); const targetColumnNamesWithValue = `${targetColumnNamesStr}, value`; const aggregationQuery = this.getAggregationQuery(periodUnitToCompactInto); const insertQueryBase = (0, sql_1.sql) ` INSERT INTO ${this.metadata.tableName} (${targetColumnNamesWithValue}) ${aggregationQuery.getSql()} `; let deduplicateQuery; if (dbType === 'mysqldb' || dbType === 'mariadb') { deduplicateQuery = (0, sql_1.sql) ` ON DUPLICATE KEY UPDATE value = value + VALUES(value)`; } else { deduplicateQuery = (0, sql_1.sql) ` ON CONFLICT(${targetColumnNamesStr}) DO UPDATE SET value = ${this.metadata.tableName}.value + excluded.value RETURNING *`; } const upsertEvents = (0, sql_1.sql) ` ${insertQueryBase} ${deduplicateQuery} `; const deleteBatch = (0, sql_1.sql) ` DELETE FROM ${sourceTableName} WHERE id IN (SELECT id FROM rows_to_compact); `; const dropTemporaryTable = (0, sql_1.sql) ` DROP TABLE rows_to_compact; `; const result = await this.manager.transaction(async (trx) => { await trx.query(getBatchAndStoreInTemporaryTable); await trx.query(upsertEvents); const rowsInBatch = await trx.query(countBatch); await trx.query(deleteBatch); await trx.query(dropTemporaryTable); return Number(rowsInBatch[0].rowsInBatch); }); return result; } getAgeLimitQuery(maxAgeInDays) { if (maxAgeInDays === 0) { return dbType === 'sqlite' ? "datetime('now')" : 'NOW()'; } return dbType === 'sqlite' ? `datetime('now', '-${maxAgeInDays} days')` : dbType === 'postgresdb' ? `NOW() - INTERVAL '${maxAgeInDays} days'` : `DATE_SUB(NOW(), INTERVAL ${maxAgeInDays} DAY)`; } async getPreviousAndCurrentPeriodTypeAggregates({ periodLengthInDays, }) { const cte = (0, sql_1.sql) ` SELECT ${this.getAgeLimitQuery(periodLengthInDays)} AS current_start, ${this.getAgeLimitQuery(0)} AS current_end, ${this.getAgeLimitQuery(periodLengthInDays * 2)} AS previous_start `; const rawRows = await this.createQueryBuilder('insights') .addCommonTableExpression(cte, 'date_ranges') .select((0, sql_1.sql) ` CASE WHEN insights.periodStart >= date_ranges.current_start AND insights.periodStart <= date_ranges.current_end THEN 'current' ELSE 'previous' END `, 'period') .addSelect('insights.type', 'type') .addSelect('SUM(value)', 'total_value') .innerJoin('date_ranges', 'date_ranges', '1=1') .where('insights.periodStart >= date_ranges.previous_start') .andWhere('insights.periodStart <= date_ranges.current_end') .groupBy('period') .addGroupBy('insights.type') .getRawMany(); return summaryParser.parse(rawRows); } parseSortingParams(sortBy) { const [column, order] = sortBy.split(':'); return [column, order.toUpperCase()]; } async getInsightsByWorkflow({ maxAgeInDays, skip = 0, take = 20, sortBy = 'total:desc', }) { const [sortField, sortOrder] = this.parseSortingParams(sortBy); const sumOfExecutions = (0, sql_1.sql) `SUM(CASE WHEN insights.type IN (${insights_shared_1.TypeToNumber.success.toString()}, ${insights_shared_1.TypeToNumber.failure.toString()}) THEN value ELSE 0 END)`; const cte = (0, sql_1.sql) `SELECT ${this.getAgeLimitQuery(maxAgeInDays)} AS start_date`; const rawRowsQuery = this.createQueryBuilder('insights') .addCommonTableExpression(cte, 'date_range') .select([ 'metadata.workflowId AS "workflowId"', 'metadata.workflowName AS "workflowName"', 'metadata.projectId AS "projectId"', 'metadata.projectName AS "projectName"', `SUM(CASE WHEN insights.type = ${insights_shared_1.TypeToNumber.success} THEN value ELSE 0 END) AS "succeeded"`, `SUM(CASE WHEN insights.type = ${insights_shared_1.TypeToNumber.failure} THEN value ELSE 0 END) AS "failed"`, `SUM(CASE WHEN insights.type IN (${insights_shared_1.TypeToNumber.success}, ${insights_shared_1.TypeToNumber.failure}) THEN value ELSE 0 END) AS "total"`, (0, sql_1.sql) `CASE WHEN ${sumOfExecutions} = 0 THEN 0 ELSE 1.0 * SUM(CASE WHEN insights.type = ${insights_shared_1.TypeToNumber.failure.toString()} THEN value ELSE 0 END) / ${sumOfExecutions} END AS "failureRate"`, `SUM(CASE WHEN insights.type = ${insights_shared_1.TypeToNumber.runtime_ms} THEN value ELSE 0 END) AS "runTime"`, `SUM(CASE WHEN insights.type = ${insights_shared_1.TypeToNumber.time_saved_min} THEN value ELSE 0 END) AS "timeSaved"`, (0, sql_1.sql) `CASE WHEN ${sumOfExecutions} = 0 THEN 0 ELSE 1.0 * SUM(CASE WHEN insights.type = ${insights_shared_1.TypeToNumber.runtime_ms.toString()} THEN value ELSE 0 END) / ${sumOfExecutions} END AS "averageRunTime"`, ]) .innerJoin('insights.metadata', 'metadata') .innerJoin('date_range', 'date_range', '1=1') .where('insights.periodStart >= date_range.start_date') .groupBy('metadata.workflowId') .addGroupBy('metadata.workflowName') .addGroupBy('metadata.projectId') .addGroupBy('metadata.projectName') .orderBy(this.escapeField(sortField), sortOrder); const count = (await rawRowsQuery.getRawMany()).length; const rawRows = await rawRowsQuery.offset(skip).limit(take).getRawMany(); return { count, rows: aggregatedInsightsByWorkflowParser.parse(rawRows) }; } async getInsightsByTime({ maxAgeInDays, periodUnit, }) { const cte = (0, sql_1.sql) `SELECT ${this.getAgeLimitQuery(maxAgeInDays)} AS start_date`; const rawRowsQuery = this.createQueryBuilder() .addCommonTableExpression(cte, 'date_range') .select([ `${this.getPeriodStartExpr(periodUnit)} as "periodStart"`, `SUM(CASE WHEN type = ${insights_shared_1.TypeToNumber.runtime_ms} THEN value ELSE 0 END) AS "runTime"`, `SUM(CASE WHEN type = ${insights_shared_1.TypeToNumber.success} THEN value ELSE 0 END) AS "succeeded"`, `SUM(CASE WHEN type = ${insights_shared_1.TypeToNumber.failure} THEN value ELSE 0 END) AS "failed"`, `SUM(CASE WHEN type = ${insights_shared_1.TypeToNumber.time_saved_min} THEN value ELSE 0 END) AS "timeSaved"`, ]) .innerJoin('date_range', 'date_range', '1=1') .where(`${this.escapeField('periodStart')} >= date_range.start_date`) .addGroupBy(this.getPeriodStartExpr(periodUnit)) .orderBy(this.getPeriodStartExpr(periodUnit), 'ASC'); const rawRows = await rawRowsQuery.getRawMany(); return aggregatedInsightsByTimeParser.parse(rawRows); } }; exports.InsightsByPeriodRepository = InsightsByPeriodRepository; exports.InsightsByPeriodRepository = InsightsByPeriodRepository = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [typeorm_1.DataSource]) ], InsightsByPeriodRepository); //# sourceMappingURL=insights-by-period.repository.js.map