@n8n/n8n-benchmark
Version:
Cli for running benchmark tests for n8n
166 lines (138 loc) • 5.76 kB
text/typescript
import type { AuthenticatedN8nApiClient } from '@/n8n-api-client/authenticated-n8n-api-client';
import { CredentialApiClient } from '@/n8n-api-client/credentials-api-client';
import { DataTableApiClient } from '@/n8n-api-client/data-table-api-client';
import type { Workflow, Credential, DataTable } from '@/n8n-api-client/n8n-api-client.types';
import { ProjectApiClient } from '@/n8n-api-client/project-api-client';
import { WorkflowApiClient } from '@/n8n-api-client/workflows-api-client';
import type { LoadableScenarioData } from '@/scenario/scenario-data-loader';
/**
* Imports scenario data into an n8n instance
*/
export class ScenarioDataImporter {
private readonly workflowApiClient: WorkflowApiClient;
private readonly credentialApiClient: CredentialApiClient;
private readonly dataTableApiClient: DataTableApiClient;
private readonly projectApiClient: ProjectApiClient;
constructor(n8nApiClient: AuthenticatedN8nApiClient) {
this.workflowApiClient = new WorkflowApiClient(n8nApiClient);
this.credentialApiClient = new CredentialApiClient(n8nApiClient);
this.dataTableApiClient = new DataTableApiClient(n8nApiClient);
this.projectApiClient = new ProjectApiClient(n8nApiClient);
}
private replaceValuesInObject(obj: unknown, searchText: string, targetText: string) {
if (Array.isArray(obj)) {
obj.map((item) => this.replaceValuesInObject(item, searchText, targetText));
} else if (typeof obj === 'object' && obj !== null) {
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string' && value === searchText) {
(obj as Record<string, unknown>)[key] = targetText;
} else {
this.replaceValuesInObject(value, searchText, targetText);
}
}
}
}
async importTestScenarioData(data: LoadableScenarioData) {
const existingWorkflows = await this.workflowApiClient.getAllWorkflows();
const existingCredentials = await this.credentialApiClient.getAllCredentials();
const existingDataTables = await this.dataTableApiClient.getAllDataTables();
for (const credential of data.credentials) {
const createdCredential = await this.importCredentials({ existingCredentials, credential });
// We need to update the id and name of the credential in the workflows
for (const workflow of data.workflows) {
this.replaceValuesInObject(workflow, credential.id, createdCredential.id);
this.replaceValuesInObject(workflow, credential.name, createdCredential.name);
}
}
for (const workflow of data.workflows) {
await this.importWorkflow({ existingWorkflows, workflow });
}
let dataTableId: string | undefined;
if (data.dataTable) {
dataTableId = await this.importDataTable({ existingDataTables, dataTable: data.dataTable });
}
return { dataTableId };
}
/**
* Imports a single credential into n8n removing any existing credentials with the same name
* @param opts
* @returns
*/
private async importCredentials(opts: {
existingCredentials: Credential[];
credential: Credential;
}) {
const existingCredentials = this.findExistingCredentials(
opts.existingCredentials,
opts.credential,
);
if (existingCredentials.length > 0) {
for (const toDelete of existingCredentials) {
await this.credentialApiClient.deleteCredential(toDelete.id);
}
}
return await this.credentialApiClient.createCredential({
...opts.credential,
name: this.getBenchmarkCredentialName(opts.credential),
});
}
private async importDataTable(opts: { existingDataTables: DataTable[]; dataTable: DataTable }) {
const { existingDataTables, dataTable } = opts;
const projectId = await this.projectApiClient.getPersonalProject();
const existingTable = existingDataTables.find(
(dt) => dt.name === this.getBenchmarkDataTableName(dataTable),
);
if (existingTable) {
await this.dataTableApiClient.deleteDataTable(projectId, existingTable.id);
}
const { id } = await this.dataTableApiClient.createDataTable(projectId, {
...dataTable,
name: this.getBenchmarkDataTableName(dataTable),
});
return id;
}
/**
* Imports a single workflow into n8n removing any existing workflows with the same name
*/
private async importWorkflow(opts: { existingWorkflows: Workflow[]; workflow: Workflow }) {
const existingWorkflows = this.findExistingWorkflows(opts.existingWorkflows, opts.workflow);
if (existingWorkflows.length > 0) {
for (const toDelete of existingWorkflows) {
await this.workflowApiClient.archiveWorkflow(toDelete.id);
await this.workflowApiClient.deleteWorkflow(toDelete.id);
}
}
const createdWorkflow = await this.workflowApiClient.createWorkflow({
...opts.workflow,
name: this.getBenchmarkWorkflowName(opts.workflow),
});
return await this.workflowApiClient.activateWorkflow(createdWorkflow);
}
private findExistingCredentials(
existingCredentials: Credential[],
credentialToImport: Credential,
): Credential[] {
const benchmarkCredentialName = this.getBenchmarkCredentialName(credentialToImport);
return existingCredentials.filter(
(existingCredential) => existingCredential.name === benchmarkCredentialName,
);
}
private findExistingWorkflows(
existingWorkflows: Workflow[],
workflowToImport: Workflow,
): Workflow[] {
const benchmarkWorkflowName = this.getBenchmarkWorkflowName(workflowToImport);
return existingWorkflows.filter(
(existingWorkflow) => existingWorkflow.name === benchmarkWorkflowName,
);
}
private getBenchmarkCredentialName(credential: Credential) {
return `[BENCHMARK] ${credential.name}`;
}
private getBenchmarkWorkflowName(workflow: Workflow) {
return `[BENCHMARK] ${workflow.name}`;
}
private getBenchmarkDataTableName(dataTable: DataTable) {
return `[BENCHMARK] ${dataTable.name}`;
}
}