n8n
Version:
n8n Workflow Automation Tool
215 lines • 9.73 kB
JavaScript
"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.InsightsCollectionService = void 0;
const backend_common_1 = require("@n8n/backend-common");
const db_1 = require("@n8n/db");
const decorators_1 = require("@n8n/decorators");
const di_1 = require("@n8n/di");
const typeorm_1 = require("@n8n/typeorm");
const luxon_1 = require("luxon");
const n8n_workflow_1 = require("n8n-workflow");
const insights_metadata_1 = require("../../modules/insights/database/entities/insights-metadata");
const insights_raw_1 = require("../../modules/insights/database/entities/insights-raw");
const insights_metadata_repository_1 = require("./database/repositories/insights-metadata.repository");
const insights_raw_repository_1 = require("./database/repositories/insights-raw.repository");
const insights_config_1 = require("./insights.config");
const shouldSkipStatus = {
success: false,
crashed: false,
error: false,
canceled: true,
new: true,
running: true,
unknown: true,
waiting: true,
};
const shouldSkipMode = {
cli: false,
error: false,
retry: false,
trigger: false,
webhook: false,
evaluation: false,
integrated: true,
internal: true,
manual: true,
};
let InsightsCollectionService = class InsightsCollectionService {
constructor(sharedWorkflowRepository, insightsRawRepository, insightsMetadataRepository, insightsConfig, logger) {
this.sharedWorkflowRepository = sharedWorkflowRepository;
this.insightsRawRepository = insightsRawRepository;
this.insightsMetadataRepository = insightsMetadataRepository;
this.insightsConfig = insightsConfig;
this.logger = logger;
this.cachedMetadata = new Map();
this.bufferedInsights = new Set();
this.isAsynchronouslySavingInsights = true;
this.flushesInProgress = new Set();
this.logger = this.logger.scoped('insights');
}
startFlushingTimer() {
this.isAsynchronouslySavingInsights = true;
this.scheduleFlushing();
this.logger.debug('Started flushing timer');
}
scheduleFlushing() {
this.cancelScheduledFlushing();
this.flushInsightsRawBufferTimer = setTimeout(async () => await this.flushEvents(), this.insightsConfig.flushIntervalSeconds * 1000);
}
cancelScheduledFlushing() {
if (this.flushInsightsRawBufferTimer !== undefined) {
clearTimeout(this.flushInsightsRawBufferTimer);
this.flushInsightsRawBufferTimer = undefined;
}
}
stopFlushingTimer() {
this.cancelScheduledFlushing();
this.logger.debug('Stopped flushing timer');
}
async shutdown() {
this.stopFlushingTimer();
this.isAsynchronouslySavingInsights = false;
this.logger.debug('Flushing remaining insights before shutdown');
await Promise.all([...this.flushesInProgress, this.flushEvents()]);
}
async handleWorkflowExecuteAfter(ctx) {
if (shouldSkipStatus[ctx.runData.status] || shouldSkipMode[ctx.runData.mode]) {
return;
}
const status = ctx.runData.status === 'success' ? 'success' : 'failure';
const commonWorkflowData = {
workflowId: ctx.workflow.id,
workflowName: ctx.workflow.name,
timestamp: luxon_1.DateTime.utc().toJSDate(),
};
this.bufferedInsights.add({
...commonWorkflowData,
type: status,
value: 1,
});
if (ctx.runData.stoppedAt) {
const value = ctx.runData.stoppedAt.getTime() - ctx.runData.startedAt.getTime();
this.bufferedInsights.add({
...commonWorkflowData,
type: 'runtime_ms',
value,
});
}
if (status === 'success' && ctx.workflow.settings?.timeSavedPerExecution) {
this.bufferedInsights.add({
...commonWorkflowData,
type: 'time_saved_min',
value: ctx.workflow.settings.timeSavedPerExecution,
});
}
if (!this.isAsynchronouslySavingInsights) {
this.logger.debug('Flushing insights synchronously (shutdown in progress)');
await this.flushEvents();
}
if (this.bufferedInsights.size >= this.insightsConfig.flushBatchSize) {
this.logger.debug(`Buffer is full (${this.bufferedInsights.size} insights), flushing events`);
void this.flushEvents();
}
}
async saveInsightsMetadataAndRaw(insightsRawToInsertBuffer) {
this.logger.debug(`Flushing ${insightsRawToInsertBuffer.size} insights`);
const workflowIdNames = new Map();
for (const event of insightsRawToInsertBuffer) {
workflowIdNames.set(event.workflowId, event.workflowName);
}
const sharedWorkflows = await this.sharedWorkflowRepository.find({
where: { workflowId: (0, typeorm_1.In)([...workflowIdNames.keys()]), role: 'workflow:owner' },
relations: { project: true },
});
const metadataToUpsert = sharedWorkflows.reduce((acc, workflow) => {
const cachedMetadata = this.cachedMetadata.get(workflow.workflowId);
if (!cachedMetadata ||
cachedMetadata.projectId !== workflow.projectId ||
cachedMetadata.projectName !== workflow.project.name ||
cachedMetadata.workflowName !== workflowIdNames.get(workflow.workflowId)) {
const metadata = new insights_metadata_1.InsightsMetadata();
metadata.projectId = workflow.projectId;
metadata.projectName = workflow.project.name;
metadata.workflowId = workflow.workflowId;
metadata.workflowName = workflowIdNames.get(workflow.workflowId);
acc.push(metadata);
}
return acc;
}, []);
this.logger.debug(`Saving ${metadataToUpsert.length} insights metadata for workflows`);
await this.insightsMetadataRepository.upsert(metadataToUpsert, ['workflowId']);
const upsertMetadata = await this.insightsMetadataRepository.findBy({
workflowId: (0, typeorm_1.In)(metadataToUpsert.map((m) => m.workflowId)),
});
for (const metadata of upsertMetadata) {
this.cachedMetadata.set(metadata.workflowId, metadata);
}
const events = [];
for (const event of insightsRawToInsertBuffer) {
const insight = new insights_raw_1.InsightsRaw();
const metadata = this.cachedMetadata.get(event.workflowId);
if (!metadata) {
throw new n8n_workflow_1.UnexpectedError(`Could not find shared workflow for insight with workflowId ${event.workflowId}`);
}
insight.metaId = metadata.metaId;
insight.type = event.type;
insight.value = event.value;
insight.timestamp = event.timestamp;
events.push(insight);
}
this.logger.debug(`Inserting ${events.length} insights raw`);
await this.insightsRawRepository.insert(events);
}
async flushEvents() {
if (this.bufferedInsights.size === 0) {
this.scheduleFlushing();
return;
}
this.cancelScheduledFlushing();
const bufferedInsightsToFlush = new Set(this.bufferedInsights);
this.bufferedInsights.clear();
let flushPromise = undefined;
flushPromise = (async () => {
try {
await this.saveInsightsMetadataAndRaw(bufferedInsightsToFlush);
}
catch (e) {
this.logger.error('Error while saving insights metadata and raw data', { error: e });
for (const event of bufferedInsightsToFlush) {
this.bufferedInsights.add(event);
}
}
finally {
this.scheduleFlushing();
this.flushesInProgress.delete(flushPromise);
}
})();
this.flushesInProgress.add(flushPromise);
await flushPromise;
}
};
exports.InsightsCollectionService = InsightsCollectionService;
__decorate([
(0, decorators_1.OnLifecycleEvent)('workflowExecuteAfter'),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], InsightsCollectionService.prototype, "handleWorkflowExecuteAfter", null);
exports.InsightsCollectionService = InsightsCollectionService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [db_1.SharedWorkflowRepository,
insights_raw_repository_1.InsightsRawRepository,
insights_metadata_repository_1.InsightsMetadataRepository,
insights_config_1.InsightsConfig,
backend_common_1.Logger])
], InsightsCollectionService);
//# sourceMappingURL=insights-collection.service.js.map