UNPKG

n8n

Version:

n8n Workflow Automation Tool

182 lines • 9.37 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.EvaluationDatasetService = void 0; const db_1 = require("@n8n/db"); const di_1 = require("@n8n/di"); const n8n_workflow_1 = require("n8n-workflow"); const bad_request_error_1 = require("../errors/response-errors/bad-request.error"); const forbidden_error_1 = require("../errors/response-errors/forbidden.error"); const not_found_error_1 = require("../errors/response-errors/not-found.error"); const execution_persistence_1 = require("../executions/execution-persistence"); const data_table_service_1 = require("../modules/data-table/data-table.service"); const source_control_preferences_service_ee_1 = require("../modules/source-control.ee/source-control-preferences.service.ee"); const check_access_1 = require("../permissions.ee/check-access"); let EvaluationDatasetService = class EvaluationDatasetService { constructor(configRepository, executionPersistence, dataTableService, sourceControlPreferencesService) { this.configRepository = configRepository; this.executionPersistence = executionPersistence; this.dataTableService = dataTableService; this.sourceControlPreferencesService = sourceControlPreferencesService; } async getCandidate(user, workflowId, configId, executionId) { const config = await this.loadDataTableConfig(workflowId, configId); const { workflowData, runData } = await this.loadSuccessfulExecution(workflowId, executionId); const dataTableId = this.dataTableId(config); await this.assertDataTableAccess(user, dataTableId, 'dataTable:readRow'); const projectId = await this.dataTableService.getProjectIdForDataTable(dataTableId); const columns = await this.dataTableService.getColumns(dataTableId, projectId); const { inputs, outputs } = this.extractFields(config, workflowData, runData); return { dataTableId, columns: columns.map((c) => ({ name: c.name, type: c.type })), fields: { inputs: toCandidateFields(inputs), outputs: toCandidateFields(outputs), }, suggestedMapping: this.suggestMapping(columns, inputs, outputs), }; } async addRow(user, workflowId, configId, dto) { this.assertInstanceWriteAccess(); const config = await this.loadDataTableConfig(workflowId, configId); const { workflowData, runData } = await this.loadSuccessfulExecution(workflowId, dto.executionId); const dataTableId = this.dataTableId(config); await this.assertDataTableAccess(user, dataTableId, 'dataTable:writeRow'); const projectId = await this.dataTableService.getProjectIdForDataTable(dataTableId); const columns = await this.dataTableService.getColumns(dataTableId, projectId); const { inputs, outputs } = this.extractFields(config, workflowData, runData); const row = {}; for (const column of columns) { const mapping = dto.mapping[column.name]; if (!mapping) continue; const source = mapping.source === 'input' ? inputs : outputs; const value = source[mapping.field]; if (value === undefined) continue; row[column.name] = toDataTableCellValue(value, column); } return await this.dataTableService.insertRows(dataTableId, projectId, [row], 'id'); } assertInstanceWriteAccess() { const { branchReadOnly } = this.sourceControlPreferencesService.getPreferences(); if (branchReadOnly) { throw new forbidden_error_1.ForbiddenError('Cannot modify data tables on a protected instance. This instance is in read-only mode.'); } } async loadDataTableConfig(workflowId, configId) { const config = await this.configRepository.findByIdAndWorkflowId(configId, workflowId); if (!config) { throw new not_found_error_1.NotFoundError('Evaluation config not found'); } if (config.datasetSource !== 'data_table') { throw new bad_request_error_1.BadRequestError('Only evaluation configs backed by a data table can receive executions'); } return config; } async loadSuccessfulExecution(workflowId, executionId) { const execution = await this.executionPersistence.findSingleExecution(executionId, { includeData: true, unflattenData: true, where: { workflowId }, }); if (!execution) { throw new not_found_error_1.NotFoundError('Execution not found'); } if (execution.status !== 'success') { throw new bad_request_error_1.BadRequestError('Only successful executions can be added to a dataset'); } if (execution.mode === 'evaluation') { throw new bad_request_error_1.BadRequestError('Evaluation runs cannot be added to a dataset'); } const runData = execution.data?.resultData?.runData; if (!runData) { throw new bad_request_error_1.BadRequestError('This execution has no stored data to add to a dataset'); } return { workflowData: execution.workflowData, runData, }; } dataTableId(config) { return config.datasetRef.dataTableId; } async assertDataTableAccess(user, dataTableId, scope) { const allowed = await (0, check_access_1.userHasScopes)(user, [scope], false, { dataTableId }); if (!allowed) { throw new forbidden_error_1.ForbiddenError('You do not have access to this data table'); } } extractFields(config, workflow, runData) { return { inputs: this.extractInputFields(workflow, runData, config.startNodeName), outputs: this.extractOutputFields(runData, config.endNodeName), }; } extractInputFields(workflow, runData, startNodeName) { const byDestination = (0, n8n_workflow_1.mapConnectionsByDestination)(workflow.connections); const parents = (0, n8n_workflow_1.getParentNodes)(byDestination, startNodeName, 'main', 1); const sourceNode = parents.length === 1 ? parents[0] : startNodeName; return getNodeOutputJson(runData, sourceNode); } extractOutputFields(runData, endNodeName) { return getNodeOutputJson(runData, endNodeName); } suggestMapping(columns, inputs, outputs) { const inputKeys = new Map(Object.keys(inputs).map((k) => [k.toLowerCase(), k])); const outputKeys = new Map(Object.keys(outputs).map((k) => [k.toLowerCase(), k])); const suggestion = {}; for (const column of columns) { const lookup = column.name.toLowerCase(); const inputMatch = inputKeys.get(lookup); if (inputMatch !== undefined) { suggestion[column.name] = { source: 'input', field: inputMatch }; continue; } const outputMatch = outputKeys.get(lookup); suggestion[column.name] = outputMatch !== undefined ? { source: 'output', field: outputMatch } : null; } return suggestion; } }; exports.EvaluationDatasetService = EvaluationDatasetService; exports.EvaluationDatasetService = EvaluationDatasetService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [db_1.EvaluationConfigRepository, execution_persistence_1.ExecutionPersistence, data_table_service_1.DataTableService, source_control_preferences_service_ee_1.SourceControlPreferencesService]) ], EvaluationDatasetService); function getNodeOutputJson(runData, nodeName) { const taskData = runData[nodeName]; if (!taskData?.length) return {}; const lastRun = taskData[taskData.length - 1]; const items = lastRun?.data?.main?.[0]; if (!items?.length) return {}; return items[0]?.json ?? {}; } function toCandidateFields(fields) { return Object.entries(fields).map(([key, sample]) => ({ key, sample })); } function toDataTableCellValue(value, column) { if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value === null) { return value; } if (column.type !== 'string') { throw new bad_request_error_1.BadRequestError(`The field mapped to column '${column.name}' is an object, which cannot be stored in a '${column.type}' column`); } return (0, n8n_workflow_1.jsonStringify)(value); } //# sourceMappingURL=evaluation-dataset.service.js.map