-
-
Save thatdudedenis/5f2f195c5fdd36c060802dfc283dbecc to your computer and use it in GitHub Desktop.
n8n for Coda Pack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as coda from "@codahq/packs-sdk"; | |
export const pack = coda.newPack(); | |
// Per-user authentication to the n8n API, using a custom token prefix and | |
// account-specific endpoints. | |
pack.setUserAuthentication({ | |
type: coda.AuthenticationType.CustomHeaderToken, | |
headerName: "X-N8N-API-KEY", | |
instructionsUrl: "https://docs.n8n.io/api/authentication/", | |
// Ask users for their n8n domain | |
requiresEndpointUrl: true | |
}); | |
// How many items to fetch in each sync formula execution. | |
const BATCH_SIZE = 250; | |
// Status parameter for executions. Used as filter in Sync Table. | |
// Set list with 3 options | |
const StatusParameter = coda.makeParameter({ | |
type: coda.ParameterType.String, | |
name: "status", | |
description: "Only executions with this status will be shown", | |
autocomplete: ["error", "success", "waiting"], | |
optional: true | |
}); | |
// Workflow ID parameter for executions. Used as filter in Sync Table. | |
// List comes from existing workflows | |
const WorkflowParameter = coda.makeParameter({ | |
type: coda.ParameterType.String, | |
name: "workflowID", | |
description: "Only executions of this workflow will be shown", | |
autocomplete: async function (context, search) { | |
// Retrieve the endpoint that the user set. | |
let endpoint = context.endpoint; | |
let url = endpoint + "api/v1/workflows"; | |
let response = await context.fetcher.fetch({ | |
url: url, | |
method: "GET" | |
}); | |
let workflowIDs = []; | |
for (let workflow of response.body.data) { | |
workflowIDs.push(workflow.id); | |
} | |
return coda.simpleAutocomplete(search, workflowIDs); | |
}, | |
optional: true | |
}); | |
// Active parameter for executions. Used as filter in Sync Table. | |
const ActiveParameter = coda.makeParameter({ | |
type: coda.ParameterType.Boolean, | |
name: "active", | |
description: "Sync only workflows that are active or not", | |
optional: true | |
}); | |
// Tag parameter for workflows. Used as filter in Sync Table. | |
// List comes from existing workflows | |
const TagParameter = coda.makeParameter({ | |
type: coda.ParameterType.String, | |
name: "tag", | |
description: "Only workflows with this tag will be shown", | |
autocomplete: async function (context, search) { | |
// Retrieve the endpoint that the user set. | |
let endpoint = context.endpoint; | |
let url = endpoint + "api/v1/workflows"; | |
let response = await context.fetcher.fetch({ | |
url: url, | |
method: "GET" | |
}); | |
let workflowTags = []; | |
for (let workflow of response.body.data) { | |
for (let tag of workflow.tags) { | |
if (workflowTags.includes(tag.name)!=true){ | |
workflowTags.push(tag.name); | |
} | |
} | |
} | |
return coda.simpleAutocomplete(search, workflowTags); | |
}, | |
optional: true | |
}); | |
// Schema for a node. | |
const NodeSchema = coda.makeObjectSchema({ | |
properties: { | |
nodeName: { | |
description: "Name", | |
type: coda.ValueType.String, | |
required: true | |
}, | |
nodeType: { | |
description: "Type", | |
type: coda.ValueType.String, | |
required: true | |
}, | |
nodeWebhookID: { | |
description: "Webhook ID", | |
type: coda.ValueType.String | |
}, | |
nodeDisabled: { | |
description: "Disabled?", | |
type: coda.ValueType.Boolean | |
}, | |
}, | |
displayProperty: "nodeName", | |
idProperty: "nodeName", | |
featuredProperties: ["nodeType", "nodeWebhookID", "nodeDisabled"], | |
}); | |
// Schema for a workflow. | |
const WorkflowSchema = coda.makeObjectSchema({ | |
properties: { | |
name: { | |
description: "Workflow name", | |
type: coda.ValueType.String, | |
required: true, | |
}, | |
active: { | |
description: "Active?", | |
type: coda.ValueType.Boolean | |
}, | |
createdAt: { | |
description: "Created", | |
type: coda.ValueType.String, | |
codaType: coda.ValueHintType.DateTime, | |
}, | |
updatedAt: { | |
description: "Updated", | |
type: coda.ValueType.String, | |
codaType: coda.ValueHintType.DateTime, | |
}, | |
validToActivate: { | |
description: "Does it contain a Trigger or Webhook Node?", | |
type: coda.ValueType.Boolean | |
}, | |
workflowId: { | |
description: "The ID of the workflow.", | |
type: coda.ValueType.Number, | |
required: true, | |
}, | |
// This is an array of nodes that exist in this workflow | |
workflowNodes: { | |
description: "The nodes of a workflow.", | |
type: coda.ValueType.Array, | |
items: NodeSchema, | |
required: true, | |
}, | |
}, | |
displayProperty: "name", | |
idProperty: "workflowId", | |
featuredProperties: ["active","createdAt", "updatedAt","workflowNodes"], | |
}); | |
// Reference schema to link an execution to a workflow | |
const WorkflowReferenceSchema = coda.makeReferenceSchemaFromObjectSchema( | |
WorkflowSchema, "Workflow"); | |
// Schema for a execution | |
const ExecutionSchema = coda.makeObjectSchema({ | |
properties: { | |
startedAt: { | |
description: "Started", | |
type: coda.ValueType.String, | |
codaType: coda.ValueHintType.DateTime, | |
}, | |
stoppedAt: { | |
description: "Stopped", | |
type: coda.ValueType.String, | |
codaType: coda.ValueHintType.DateTime, | |
}, | |
workflow: WorkflowReferenceSchema, | |
finished: { | |
description: "Finished?", | |
type: coda.ValueType.Boolean | |
}, | |
mode: { | |
description: "Type of execution", | |
type: coda.ValueType.String | |
}, | |
retryOf: { | |
description: "Retry of workflow ID", | |
type: coda.ValueType.String | |
}, | |
executionId: { type: coda.ValueType.String }, | |
}, | |
displayProperty: "executionId", | |
idProperty: "executionId", | |
featuredProperties: ["workflow","startedAt", "stoppedAt", "finished","mode","retryOf"], | |
}); | |
// Reformat the API response for an execution to fit the schema. | |
function formatExecution(execution) { | |
let startedAt = execution.startedAt; | |
let stoppedAt = execution.stoppedAt; | |
let finished = execution.finished; | |
let mode = execution.mode; | |
let retryOf = execution.retryOf; | |
let workflow = { | |
workflowId: execution.workflowId, | |
name: "Not found", // Placeholder name, if not synced yet. | |
}; | |
let executionId = execution.id; | |
var output: any = {}; | |
startedAt && (output.startedAt = startedAt); | |
stoppedAt && (output.stoppedAt = stoppedAt); | |
finished && (output.finished = finished); | |
mode && (output.mode = mode); | |
retryOf && (output.retryOf = retryOf); | |
executionId && (output.executionId = executionId); | |
workflow && (output.workflow = workflow); | |
return output; | |
} | |
// Sync table for executions | |
pack.addSyncTable({ | |
name: "Executions", | |
identityName: "Execution", | |
schema: ExecutionSchema, | |
formula: { | |
name: "SyncExecutions", | |
description: "Sync the executions", | |
parameters: [StatusParameter, WorkflowParameter], | |
execute: async function ([status, workflowID], context) { | |
// Retrieve the endpoint that the user set. | |
let endpoint = context.endpoint; | |
let url = coda.withQueryParams(endpoint + "api/v1/executions", { | |
status: status, | |
workflowId: workflowID, | |
limit: BATCH_SIZE | |
}); | |
let response = await context.fetcher.fetch({ | |
method: "GET", | |
url: url, | |
cacheTtlSecs: 0 | |
}); | |
let executions = response.body.data; | |
let result = []; | |
for (let execution of executions) { | |
result.push(formatExecution(execution)); | |
} | |
return { | |
result: result | |
}; | |
}, | |
}, | |
}); | |
// Action formula to activate workflow | |
pack.addFormula({ | |
name: "ActivateWorkflow", | |
description: "Activates a workflow", | |
isAction: true, | |
parameters: [ | |
coda.makeParameter({ | |
type: coda.ParameterType.String, | |
name: "workflowID", | |
description: "The ID of the workflow", | |
optional: false | |
}) | |
], | |
resultType: coda.ValueType.String, | |
execute: async function ([workflowID], context) { | |
// Retrieve the endpoint that the user set. | |
let endpoint = context.endpoint; | |
let url = endpoint + "api/v1/workflows/" + workflowID + "/activate"; | |
let response = await context.fetcher.fetch({ | |
url: url, | |
method: "POST" | |
}); | |
return "Success"; | |
}, | |
}); | |
// Action formula to de-activate workflow | |
pack.addFormula({ | |
name: "DeactivateWorkflow", | |
description: "Deactivates a workflow", | |
isAction: true, | |
parameters: [ | |
coda.makeParameter({ | |
type: coda.ParameterType.String, | |
name: "workflowID", | |
description: "The ID of the workflow", | |
optional: false | |
}) | |
], | |
resultType: coda.ValueType.String, | |
execute: async function ([workflowID], context) { | |
// Retrieve the endpoint that the user set. | |
let endpoint = context.endpoint; | |
let url = endpoint + "api/v1/workflows/" + workflowID + "/deactivate"; | |
let response = await context.fetcher.fetch({ | |
url: url, | |
method: "POST" | |
}); | |
return "Success"; | |
}, | |
}); | |
// Action formula to delet workflow | |
pack.addFormula({ | |
name: "DeleteWorkflow", | |
description: "Deletes a workflow", | |
isAction: true, | |
parameters: [ | |
coda.makeParameter({ | |
type: coda.ParameterType.String, | |
name: "workflowID", | |
description: "The ID of the workflow", | |
optional: false | |
}) | |
], | |
resultType: coda.ValueType.String, | |
execute: async function ([workflowID], context) { | |
// Retrieve the endpoint that the user set. | |
let endpoint = context.endpoint; | |
let url = endpoint + "api/v1/workflows/" + workflowID; | |
let response = await context.fetcher.fetch({ | |
url: url, | |
method: "DELETE" | |
}); | |
return "Success"; | |
}, | |
}); | |
// Sync table for workflows | |
pack.addSyncTable({ | |
name: "Workflows", | |
identityName: "Workflow", | |
schema: WorkflowSchema, | |
formula: { | |
name: "SyncWorkflows", | |
description: "Sync the workflows", | |
parameters: [ActiveParameter, TagParameter], | |
execute: async function ([active, tag], context) { | |
// Retrieve the endpoint that the user set. | |
let endpoint = context.endpoint; | |
let url = coda.withQueryParams(endpoint + "api/v1/workflows", { | |
active: active, | |
tags: tag, | |
limit: BATCH_SIZE | |
}); | |
let response = await context.fetcher.fetch({ | |
method: "GET", | |
url: url, | |
cacheTtlSecs: 0 | |
}); | |
let workflows = response.body.data; | |
let result = []; | |
// Detect whether workflow can actually be turned on or not (if any of its nodes is a trigger or webhook node) | |
for (let workflow of workflows) { | |
let triggerOrWebhook = false; | |
let workflowNodes = []; | |
for (let node of workflow.nodes) { | |
// List of node types that n8n requires to exist in order for the workflow to be activated | |
if ( | |
node.type.includes("trigger") || | |
node.type.includes("webhook") || | |
node.type.includes("cron") || | |
node.type.includes("interval") || | |
node.type.includes("emailReadImap") | |
){ | |
triggerOrWebhook = true; | |
}; | |
// each node gets added to the node array of that workflow | |
workflowNodes.push({ | |
nodeName: node.name, | |
nodeType: node.type.replace("n8n-nodes-base.", ""), // clean up node type name to remove the repeated beginning | |
nodeWebhookID: node.webhookId, | |
nodeDisabled: node.disabled | |
}); | |
} | |
result.push({ | |
name: workflow.name, | |
active: workflow.active, | |
createdAt: workflow.createdAt, | |
updatedAt: workflow.updatedAt, | |
workflowId: workflow.id, | |
validToActivate: triggerOrWebhook, | |
workflowNodes: workflowNodes | |
}); | |
} | |
return { | |
result: result, | |
}; | |
}, | |
}, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment