feat(core): Support importing a singular workflow object (#14041)

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Mike Arvela
2025-03-19 11:12:40 +02:00
committed by GitHub
parent 6817abe47f
commit 91b27964d8
3 changed files with 123 additions and 19 deletions

View File

@@ -17,13 +17,9 @@ import { ImportService } from '@/services/import.service';
import { BaseCommand } from '../base-command';
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
if (!Array.isArray(workflows)) {
throw new UserError(
'File does not seem to contain workflows. Make sure the workflows are contained in an array.',
);
}
function assertHasWorkflowsToImport(
workflows: unknown[],
): asserts workflows is IWorkflowToImport[] {
for (const workflow of workflows) {
if (
typeof workflow !== 'object' ||
@@ -185,30 +181,28 @@ export class ImportWorkflowsCommand extends BaseCommand {
path = path.replace(/\\/g, '/');
}
const workflowRepository = Container.get(WorkflowRepository);
if (separate) {
const files = await glob('*.json', {
cwd: path,
absolute: true,
});
const workflowInstances = files.map((file) => {
return files.map((file) => {
const workflow = jsonParse<IWorkflowToImport>(fs.readFileSync(file, { encoding: 'utf8' }));
if (!workflow.id) {
workflow.id = generateNanoId();
}
const workflowInstance = Container.get(WorkflowRepository).create(workflow);
return workflowInstance;
return workflowRepository.create(workflow);
});
return workflowInstances;
} else {
const workflows = jsonParse<IWorkflowToImport[]>(fs.readFileSync(path, { encoding: 'utf8' }));
const workflows = jsonParse<IWorkflowToImport | IWorkflowToImport[]>(
fs.readFileSync(path, { encoding: 'utf8' }),
);
const workflowsArray = Array.isArray(workflows) ? workflows : [workflows];
assertHasWorkflowsToImport(workflowsArray);
const workflowInstances = workflows.map((w) => Container.get(WorkflowRepository).create(w));
assertHasWorkflowsToImport(workflows);
return workflowInstances;
return workflowRepository.create(workflowsArray);
}
}

View File

@@ -0,0 +1,79 @@
{
"name": "active-workflow",
"nodes": [
{
"parameters": {
"path": "e20b4873-fcf7-4bce-88fc-a1a56d66b138",
"responseMode": "responseNode",
"options": {}
},
"id": "c26d8782-bd57-43d0-86dc-0c618a7e4024",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [800, 580],
"webhookId": "e20b4873-fcf7-4bce-88fc-a1a56d66b138"
},
{
"parameters": {
"values": {
"boolean": [
{
"name": "hooked",
"value": true
}
]
},
"options": {}
},
"id": "9701b1ef-9ab0-432a-b086-cf76981b097d",
"name": "Set",
"type": "n8n-nodes-base.set",
"typeVersion": 1,
"position": [1020, 580]
},
{
"parameters": {
"options": {}
},
"id": "d0f086b8-c2b2-4404-b347-95d3f91e555a",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [1240, 580]
}
],
"pinData": {},
"connections": {
"Webhook": {
"main": [
[
{
"node": "Set",
"type": "main",
"index": 0
}
]
]
},
"Set": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {},
"versionId": "40a70df1-740f-47e7-8e16-50a0bcd5b70f",
"id": "998",
"meta": {
"instanceId": "95977dc4769098fc608439605527ee75d23f10d551aed6b87a3eea1a252c0ba9"
},
"tags": []
}

View File

@@ -100,6 +100,37 @@ test('import:workflow should import active workflow from combined file and deact
});
});
test('import:workflow can import a single workflow object', async () => {
//
// ARRANGE
//
const owner = await createOwner();
const ownerProject = await getPersonalProject(owner);
//
// ACT
//
await command.run(['--input=./test/integration/commands/import-workflows/combined/single.json']);
//
// ASSERT
//
const after = {
workflows: await getAllWorkflows(),
sharings: await getAllSharedWorkflows(),
};
expect(after).toMatchObject({
workflows: [expect.objectContaining({ name: 'active-workflow', active: false })],
sharings: [
expect.objectContaining({
workflowId: '998',
projectId: ownerProject.id,
role: 'workflow:owner',
}),
],
});
});
test('`import:workflow --userId ...` should fail if the workflow exists already and is owned by somebody else', async () => {
//
// ARRANGE