Skip to content

Instantly share code, notes, and snippets.

@thatdudedenis
Last active August 3, 2022 12:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thatdudedenis/5f2f195c5fdd36c060802dfc283dbecc to your computer and use it in GitHub Desktop.
Save thatdudedenis/5f2f195c5fdd36c060802dfc283dbecc to your computer and use it in GitHub Desktop.
n8n for Coda Pack
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