n8n
Version:
n8n Workflow Automation Tool
182 lines • 9.37 kB
JavaScript
;
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