mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
Merge branch 'master' into expand-zoho-node
This commit is contained in:
commit
6ad64ae830
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.125.0",
|
||||
"version": "0.126.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -107,8 +107,8 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"mysql2": "~2.2.0",
|
||||
"n8n-core": "~0.75.0",
|
||||
"n8n-editor-ui": "~0.95.0",
|
||||
"n8n-nodes-base": "~0.122.0",
|
||||
"n8n-editor-ui": "~0.96.0",
|
||||
"n8n-nodes-base": "~0.123.0",
|
||||
"n8n-workflow": "~0.62.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.95.0",
|
||||
"version": "0.96.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -87,7 +87,7 @@
|
|||
"vue-router": "^3.0.6",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"vue-typed-mixins": "^0.2.0",
|
||||
"vue2-touch-events": "^2.3.2",
|
||||
"vue2-touch-events": "^3.2.1",
|
||||
"vuex": "^3.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,6 +221,15 @@ export interface IWorkflowDataUpdate {
|
|||
tags?: ITag[] | string[]; // string[] when store or requested, ITag[] from API response
|
||||
}
|
||||
|
||||
export interface IWorkflowTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
workflow: {
|
||||
nodes: INodeUi[];
|
||||
connections: IConnections;
|
||||
};
|
||||
}
|
||||
|
||||
// Almost identical to cli.Interfaces.ts
|
||||
export interface IWorkflowDb {
|
||||
id: string;
|
||||
|
@ -602,3 +611,8 @@ export interface IRestApiContext {
|
|||
baseUrl: string;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export interface IZoomConfig {
|
||||
scale: number;
|
||||
offset: XYPositon;
|
||||
}
|
||||
|
|
|
@ -42,15 +42,13 @@ class ResponseError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export async function makeRestApiRequest(context: IRestApiContext, method: Method, endpoint: string, data?: IDataObject) {
|
||||
const { baseUrl, sessionId } = context;
|
||||
async function request(config: {method: Method, baseURL: string, endpoint: string, headers?: IDataObject, data?: IDataObject}) {
|
||||
const { method, baseURL, endpoint, headers, data } = config;
|
||||
const options: AxiosRequestConfig = {
|
||||
method,
|
||||
url: endpoint,
|
||||
baseURL: baseUrl,
|
||||
headers: {
|
||||
sessionid: sessionId,
|
||||
},
|
||||
baseURL,
|
||||
headers,
|
||||
};
|
||||
if (['PATCH', 'POST', 'PUT'].includes(method)) {
|
||||
options.data = data;
|
||||
|
@ -60,7 +58,7 @@ export async function makeRestApiRequest(context: IRestApiContext, method: Metho
|
|||
|
||||
try {
|
||||
const response = await axios.request(options);
|
||||
return response.data.data;
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.message === 'Network Error') {
|
||||
throw new ResponseError('API-Server can not be reached. It is probably down.');
|
||||
|
@ -79,3 +77,20 @@ export async function makeRestApiRequest(context: IRestApiContext, method: Metho
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function makeRestApiRequest(context: IRestApiContext, method: Method, endpoint: string, data?: IDataObject) {
|
||||
const response = await request({
|
||||
method,
|
||||
baseURL: context.baseUrl,
|
||||
endpoint,
|
||||
headers: {sessionid: context.sessionId},
|
||||
data,
|
||||
});
|
||||
|
||||
// @ts-ignore all cli rest api endpoints return data wrapped in `data` key
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function get(baseURL: string, endpoint: string, params?: IDataObject) {
|
||||
return await request({method: 'GET', baseURL, endpoint, data: params});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from './helpers';
|
||||
import { IRestApiContext, IWorkflowTemplate } from '@/Interface';
|
||||
import { makeRestApiRequest, get } from './helpers';
|
||||
import { TEMPLATES_BASE_URL } from '@/constants';
|
||||
|
||||
export async function getNewWorkflow(context: IRestApiContext, name?: string) {
|
||||
return await makeRestApiRequest(context, 'GET', `/workflows/new`, name ? { name } : {});
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorkflowTemplate(templateId: string): Promise<IWorkflowTemplate> {
|
||||
return await get(TEMPLATES_BASE_URL, `/workflows/templates/${templateId}`);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
class="json-data"
|
||||
/>
|
||||
<span v-else>
|
||||
The exact cause can sadly not displayed right now as the returned data is too large.
|
||||
<font-awesome-icon icon="info-circle" /> The error cause is too large to be displayed.
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
|
|
@ -68,6 +68,9 @@ export const genericHelpers = mixins(showMessage).extend({
|
|||
},
|
||||
);
|
||||
},
|
||||
setLoadingText (text: string) {
|
||||
this.loadingService.text = text;
|
||||
},
|
||||
stopLoading () {
|
||||
if (this.loadingService !== null) {
|
||||
this.loadingService.close();
|
||||
|
|
|
@ -15,12 +15,12 @@ export const showMessage = mixins(externalHooks).extend({
|
|||
return Notification(messageData);
|
||||
},
|
||||
|
||||
$showError(error: Error, title: string, message: string) {
|
||||
$showError(error: Error, title: string, message?: string) {
|
||||
const messageLine = message ? `${message}<br/>` : '';
|
||||
this.$showMessage({
|
||||
title,
|
||||
message: `
|
||||
${message}
|
||||
<br>
|
||||
${messageLine}
|
||||
<i>${error.message}</i>
|
||||
${this.collapsableDetails(error)}`,
|
||||
type: 'error',
|
||||
|
|
|
@ -21,6 +21,11 @@ export const BREAKPOINT_MD = 992;
|
|||
export const BREAKPOINT_LG = 1200;
|
||||
export const BREAKPOINT_XL = 1920;
|
||||
|
||||
|
||||
// templates
|
||||
export const TEMPLATES_BASE_URL = `https://api.n8n.io/`;
|
||||
export const START_NODE_TYPE = 'n8n-nodes-base.start';
|
||||
|
||||
// Node creator
|
||||
export const CORE_NODES_CATEGORY = 'Core Nodes';
|
||||
export const CUSTOM_NODES_CATEGORY = 'Custom Nodes';
|
||||
|
|
|
@ -51,6 +51,7 @@ import {
|
|||
faEnvelope,
|
||||
faEye,
|
||||
faExclamationTriangle,
|
||||
faExpand,
|
||||
faExternalLinkAlt,
|
||||
faExchangeAlt,
|
||||
faFile,
|
||||
|
@ -139,6 +140,7 @@ library.add(faEdit);
|
|||
library.add(faEnvelope);
|
||||
library.add(faEye);
|
||||
library.add(faExclamationTriangle);
|
||||
library.add(faExpand);
|
||||
library.add(faExternalLinkAlt);
|
||||
library.add(faExchangeAlt);
|
||||
library.add(faFile);
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
import { getNewWorkflow } from '@/api/workflows';
|
||||
import { getNewWorkflow, getWorkflowTemplate } from '@/api/workflows';
|
||||
import { DUPLICATE_POSTFFIX, MAX_WORKFLOW_NAME_LENGTH, DEFAULT_NEW_WORKFLOW_NAME } from '@/constants';
|
||||
import { ActionContext, Module } from 'vuex';
|
||||
import {
|
||||
IRootState,
|
||||
IWorkflowsState,
|
||||
IWorkflowTemplate,
|
||||
} from '../Interface';
|
||||
|
||||
const module: Module<IWorkflowsState, IRootState> = {
|
||||
namespaced: true,
|
||||
state: {},
|
||||
actions: {
|
||||
setNewWorkflowName: async (context: ActionContext<IWorkflowsState, IRootState>): Promise<void> => {
|
||||
setNewWorkflowName: async (context: ActionContext<IWorkflowsState, IRootState>, name?: string): Promise<void> => {
|
||||
let newName = '';
|
||||
try {
|
||||
const newWorkflow = await getNewWorkflow(context.rootGetters.getRestApiContext);
|
||||
const newWorkflow = await getNewWorkflow(context.rootGetters.getRestApiContext, name);
|
||||
newName = newWorkflow.name;
|
||||
}
|
||||
catch (e) {
|
||||
// in case of error, default to original name
|
||||
newName = DEFAULT_NEW_WORKFLOW_NAME;
|
||||
newName = name || DEFAULT_NEW_WORKFLOW_NAME;
|
||||
}
|
||||
|
||||
context.commit('setWorkflowName', { newName }, { root: true });
|
||||
|
@ -42,6 +43,9 @@ const module: Module<IWorkflowsState, IRootState> = {
|
|||
|
||||
return newName;
|
||||
},
|
||||
getWorkflowTemplate: async (context: ActionContext<IWorkflowsState, IRootState>, templateId: string): Promise<IWorkflowTemplate> => {
|
||||
return await getWorkflowTemplate(templateId);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -48,5 +48,14 @@ export default new Router({
|
|||
path: '/',
|
||||
redirect: '/workflow',
|
||||
},
|
||||
{
|
||||
path: '/workflows/templates/:id',
|
||||
name: 'WorkflowTemplate',
|
||||
components: {
|
||||
default: NodeView,
|
||||
header: MainHeader,
|
||||
sidebar: MainSidebar,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
@ -39,15 +39,18 @@
|
|||
@closeNodeCreator="closeNodeCreator"
|
||||
></node-creator>
|
||||
<div :class="{ 'zoom-menu': true, expanded: !sidebarMenuCollapsed }">
|
||||
<button @click="setZoom('in')" class="button-white" title="Zoom In">
|
||||
<button @click="zoomToFit" class="button-white" title="Zoom to Fit">
|
||||
<font-awesome-icon icon="expand"/>
|
||||
</button>
|
||||
<button @click="zoomIn()" class="button-white" title="Zoom In">
|
||||
<font-awesome-icon icon="search-plus"/>
|
||||
</button>
|
||||
<button @click="setZoom('out')" class="button-white" title="Zoom Out">
|
||||
<button @click="zoomOut()" class="button-white" title="Zoom Out">
|
||||
<font-awesome-icon icon="search-minus"/>
|
||||
</button>
|
||||
<button
|
||||
v-if="nodeViewScale !== 1"
|
||||
@click="setZoom('reset')"
|
||||
@click="resetZoom()"
|
||||
class="button-white"
|
||||
title="Reset Zoom"
|
||||
>
|
||||
|
@ -113,7 +116,7 @@ import {
|
|||
} from 'jsplumb';
|
||||
import { MessageBoxInputData } from 'element-ui/types/message-box';
|
||||
import { jsPlumb, Endpoint, OnConnectionBindInfo } from 'jsplumb';
|
||||
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
|
||||
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE } from '@/constants';
|
||||
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
|
@ -133,9 +136,10 @@ import NodeCreator from '@/components/NodeCreator/NodeCreator.vue';
|
|||
import NodeSettings from '@/components/NodeSettings.vue';
|
||||
import RunData from '@/components/RunData.vue';
|
||||
|
||||
import { getLeftmostTopNode, getWorkflowCorners, scaleSmaller, scaleBigger, scaleReset } from './helpers';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
IConnection,
|
||||
IConnections,
|
||||
|
@ -144,7 +148,6 @@ import {
|
|||
INodeConnections,
|
||||
INodeIssues,
|
||||
INodeTypeDescription,
|
||||
IRunData,
|
||||
NodeInputConnections,
|
||||
NodeHelpers,
|
||||
Workflow,
|
||||
|
@ -155,19 +158,35 @@ import {
|
|||
IExecutionResponse,
|
||||
IExecutionsStopData,
|
||||
IN8nUISettings,
|
||||
IStartRunData,
|
||||
IWorkflowDb,
|
||||
IWorkflowData,
|
||||
INodeUi,
|
||||
IRunDataUi,
|
||||
IUpdateInformation,
|
||||
IWorkflowDataUpdate,
|
||||
XYPositon,
|
||||
IPushDataExecutionFinished,
|
||||
ITag,
|
||||
IWorkflowTemplate,
|
||||
} from '../Interface';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
const NODE_SIZE = 100;
|
||||
const DEFAULT_START_POSITION_X = 250;
|
||||
const DEFAULT_START_POSITION_Y = 300;
|
||||
const HEADER_HEIGHT = 65;
|
||||
const SIDEBAR_WIDTH = 65;
|
||||
|
||||
const DEFAULT_START_NODE = {
|
||||
name: 'Start',
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
position: [
|
||||
DEFAULT_START_POSITION_X,
|
||||
DEFAULT_START_POSITION_Y,
|
||||
] as XYPositon,
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
export default mixins(
|
||||
copyPaste,
|
||||
externalHooks,
|
||||
|
@ -311,6 +330,7 @@ export default mixins(
|
|||
nodeViewScale: 1,
|
||||
ctrlKeyPressed: false,
|
||||
stopExecutionInProgress: false,
|
||||
blankRedirect: false,
|
||||
};
|
||||
},
|
||||
beforeDestroy () {
|
||||
|
@ -349,9 +369,67 @@ export default mixins(
|
|||
this.$store.commit('setWorkflowExecutionData', data);
|
||||
|
||||
await this.addNodes(JSON.parse(JSON.stringify(data.workflowData.nodes)), JSON.parse(JSON.stringify(data.workflowData.connections)));
|
||||
this.$nextTick(() => {
|
||||
this.zoomToFit();
|
||||
this.$store.commit('setStateDirty', false);
|
||||
});
|
||||
|
||||
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
|
||||
},
|
||||
async openWorkflowTemplate (templateId: string) {
|
||||
this.setLoadingText('Loading template');
|
||||
this.resetWorkspace();
|
||||
|
||||
let data: IWorkflowTemplate | undefined;
|
||||
try {
|
||||
this.$externalHooks().run('template.requested', { templateId });
|
||||
data = await this.$store.dispatch('workflows/getWorkflowTemplate', templateId);
|
||||
|
||||
if (!data) {
|
||||
throw new Error(`Workflow template with id "${templateId}" could not be found!`);
|
||||
}
|
||||
|
||||
data.workflow.nodes.forEach((node) => {
|
||||
if (!this.$store.getters.nodeType(node.type)) {
|
||||
const name = node.type.replace('n8n-nodes-base.', '');
|
||||
throw new Error(`The ${name} node is not supported`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
this.$showError(error, `Couldn't import workflow`);
|
||||
this.$router.push({ name: 'NodeViewNew' });
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = data.workflow.nodes;
|
||||
const hasStartNode = !!nodes.find(node => node.type === START_NODE_TYPE);
|
||||
|
||||
const leftmostTop = getLeftmostTopNode(nodes);
|
||||
|
||||
const diffX = DEFAULT_START_POSITION_X - leftmostTop.position[0];
|
||||
const diffY = DEFAULT_START_POSITION_Y - leftmostTop.position[1];
|
||||
|
||||
data.workflow.nodes.map((node) => {
|
||||
node.position[0] += diffX + (hasStartNode? 0 : NODE_SIZE * 2);
|
||||
node.position[1] += diffY;
|
||||
});
|
||||
|
||||
if (!hasStartNode) {
|
||||
data.workflow.nodes.push(DEFAULT_START_NODE);
|
||||
}
|
||||
|
||||
this.blankRedirect = true;
|
||||
this.$router.push({ name: 'NodeViewNew' });
|
||||
|
||||
await this.addNodes(data.workflow.nodes, data.workflow.connections);
|
||||
await this.$store.dispatch('workflows/setNewWorkflowName', data.name);
|
||||
this.$nextTick(() => {
|
||||
this.zoomToFit();
|
||||
this.$store.commit('setStateDirty', true);
|
||||
});
|
||||
|
||||
this.$externalHooks().run('template.open', { templateId, templateName: data.name, workflow: data.workflow });
|
||||
},
|
||||
async openWorkflow (workflowId: string) {
|
||||
this.resetWorkspace();
|
||||
|
||||
|
@ -381,6 +459,7 @@ export default mixins(
|
|||
await this.addNodes(data.nodes, data.connections);
|
||||
|
||||
this.$store.commit('setStateDirty', false);
|
||||
this.zoomToFit();
|
||||
|
||||
this.$externalHooks().run('workflow.open', { workflowId, workflowName: data.name });
|
||||
|
||||
|
@ -412,9 +491,9 @@ export default mixins(
|
|||
//* Control + scroll zoom
|
||||
if (e.ctrlKey) {
|
||||
if (e.deltaY > 0) {
|
||||
this.setZoom('out');
|
||||
this.zoomOut();
|
||||
} else {
|
||||
this.setZoom('in');
|
||||
this.zoomIn();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
@ -435,7 +514,9 @@ export default mixins(
|
|||
// else which should ignore the default keybindings
|
||||
for (let index = 0; index < path.length; index++) {
|
||||
if (path[index].className && typeof path[index].className === 'string' && (
|
||||
path[index].className.includes('el-message-box') || path[index].className.includes('ignore-key-press')
|
||||
path[index].className.includes('el-message-box') ||
|
||||
path[index].className.includes('el-select') ||
|
||||
path[index].className.includes('ignore-key-press')
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
@ -465,12 +546,14 @@ export default mixins(
|
|||
if (lastSelectedNode !== null) {
|
||||
this.callDebounced('renameNodePrompt', 1500, lastSelectedNode.name);
|
||||
}
|
||||
} else if (e.key === '+') {
|
||||
this.callDebounced('setZoom', 300, 'in');
|
||||
} else if (e.key === '-') {
|
||||
this.callDebounced('setZoom', 300, 'out');
|
||||
} else if ((e.key === '0') && (this.isCtrlKeyPressed(e) === true)) {
|
||||
this.callDebounced('setZoom', 300, 'reset');
|
||||
} else if ((e.key === '=' || e.key === '+') && !this.isCtrlKeyPressed(e)) {
|
||||
this.zoomIn();
|
||||
} else if ((e.key === '_' || e.key === '-') && !this.isCtrlKeyPressed(e)) {
|
||||
this.zoomOut();
|
||||
} else if ((e.key === '0') && !this.isCtrlKeyPressed(e)) {
|
||||
this.resetZoom();
|
||||
} else if ((e.key === '1') && !this.isCtrlKeyPressed(e)) {
|
||||
this.zoomToFit();
|
||||
} else if ((e.key === 'a') && (this.isCtrlKeyPressed(e) === true)) {
|
||||
// Select all nodes
|
||||
e.stopPropagation();
|
||||
|
@ -704,18 +787,32 @@ export default mixins(
|
|||
});
|
||||
},
|
||||
|
||||
setZoom (zoom: string) {
|
||||
if (zoom === 'in') {
|
||||
this.nodeViewScale *= 1.25;
|
||||
} else if (zoom === 'out') {
|
||||
this.nodeViewScale /= 1.25;
|
||||
} else {
|
||||
this.nodeViewScale = 1;
|
||||
}
|
||||
resetZoom () {
|
||||
const { scale, offset } = scaleReset({scale: this.nodeViewScale, offset: this.$store.getters.getNodeViewOffsetPosition});
|
||||
|
||||
const zoomLevel = this.nodeViewScale;
|
||||
this.setZoomLevel(scale);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: offset});
|
||||
},
|
||||
|
||||
zoomIn() {
|
||||
const { scale, offset: [xOffset, yOffset] } = scaleBigger({scale: this.nodeViewScale, offset: this.$store.getters.getNodeViewOffsetPosition});
|
||||
|
||||
this.setZoomLevel(scale);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [xOffset, yOffset]});
|
||||
},
|
||||
|
||||
zoomOut() {
|
||||
const { scale, offset: [xOffset, yOffset] } = scaleSmaller({scale: this.nodeViewScale, offset: this.$store.getters.getNodeViewOffsetPosition});
|
||||
|
||||
this.setZoomLevel(scale);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [xOffset, yOffset]});
|
||||
},
|
||||
|
||||
setZoomLevel (zoomLevel: number) {
|
||||
this.nodeViewScale = zoomLevel; // important for background
|
||||
const element = this.instance.getContainer() as HTMLElement;
|
||||
|
||||
// https://docs.jsplumbtoolkit.com/community/current/articles/zooming.html
|
||||
const prependProperties = ['webkit', 'moz', 'ms', 'o'];
|
||||
const scaleString = 'scale(' + zoomLevel + ')';
|
||||
|
||||
|
@ -729,6 +826,36 @@ export default mixins(
|
|||
this.instance.setZoom(zoomLevel);
|
||||
},
|
||||
|
||||
zoomToFit () {
|
||||
const nodes = this.$store.getters.allNodes as INodeUi[];
|
||||
|
||||
if (nodes.length === 0) { // some unknown workflow executions
|
||||
return;
|
||||
}
|
||||
|
||||
const {minX, minY, maxX, maxY} = getWorkflowCorners(nodes);
|
||||
|
||||
const PADDING = NODE_SIZE * 4;
|
||||
|
||||
const editorWidth = window.innerWidth;
|
||||
const diffX = maxX - minX + SIDEBAR_WIDTH + PADDING;
|
||||
const scaleX = editorWidth / diffX;
|
||||
|
||||
const editorHeight = window.innerHeight;
|
||||
const diffY = maxY - minY + HEADER_HEIGHT + PADDING;
|
||||
const scaleY = editorHeight / diffY;
|
||||
|
||||
const zoomLevel = Math.min(scaleX, scaleY, 1);
|
||||
let xOffset = (minX * -1) * zoomLevel + SIDEBAR_WIDTH; // find top right corner
|
||||
xOffset += (editorWidth - SIDEBAR_WIDTH - (maxX - minX + NODE_SIZE) * zoomLevel) / 2; // add padding to center workflow
|
||||
|
||||
let yOffset = (minY * -1) * zoomLevel + HEADER_HEIGHT; // find top right corner
|
||||
yOffset += (editorHeight - HEADER_HEIGHT - (maxY - minY + NODE_SIZE * 2) * zoomLevel) / 2; // add padding to center workflow
|
||||
|
||||
this.setZoomLevel(zoomLevel);
|
||||
this.$store.commit('setNodeViewOffsetPosition', {newOffset: [xOffset, yOffset]});
|
||||
},
|
||||
|
||||
async stopExecution () {
|
||||
const executionId = this.$store.getters.activeExecutionId;
|
||||
if (executionId === null) {
|
||||
|
@ -1406,23 +1533,10 @@ export default mixins(
|
|||
await this.$store.dispatch('workflows/setNewWorkflowName');
|
||||
this.$store.commit('setStateDirty', false);
|
||||
|
||||
// Create start node
|
||||
const defaultNodes = [
|
||||
{
|
||||
name: 'Start',
|
||||
type: 'n8n-nodes-base.start',
|
||||
typeVersion: 1,
|
||||
position: [
|
||||
250,
|
||||
300,
|
||||
] as XYPositon,
|
||||
parameters: {},
|
||||
},
|
||||
];
|
||||
|
||||
await this.addNodes(defaultNodes);
|
||||
await this.addNodes([DEFAULT_START_NODE]);
|
||||
this.$store.commit('setStateDirty', false);
|
||||
|
||||
this.setZoomLevel(1);
|
||||
},
|
||||
async initView (): Promise<void> {
|
||||
if (this.$route.params.action === 'workflowSave') {
|
||||
|
@ -1432,7 +1546,14 @@ export default mixins(
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (this.$route.name === 'ExecutionById') {
|
||||
if (this.blankRedirect) {
|
||||
this.blankRedirect = false;
|
||||
}
|
||||
else if (this.$route.name === 'WorkflowTemplate') {
|
||||
const templateId = this.$route.params.id;
|
||||
await this.openWorkflowTemplate(templateId);
|
||||
}
|
||||
else if (this.$route.name === 'ExecutionById') {
|
||||
// Load an execution
|
||||
const executionId = this.$route.params.id;
|
||||
await this.openExecution(executionId);
|
||||
|
|
85
packages/editor-ui/src/views/helpers.ts
Normal file
85
packages/editor-ui/src/views/helpers.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { INodeUi, IZoomConfig } from "@/Interface";
|
||||
|
||||
interface ICorners {
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
}
|
||||
|
||||
export const getLeftmostTopNode = (nodes: INodeUi[]): INodeUi => {
|
||||
return nodes.reduce((leftmostTop, node) => {
|
||||
if (node.position[0] > leftmostTop.position[0] || node.position[1] > leftmostTop.position[1]) {
|
||||
return leftmostTop;
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
};
|
||||
|
||||
export const getWorkflowCorners = (nodes: INodeUi[]): ICorners => {
|
||||
return nodes.reduce((accu: ICorners, node: INodeUi) => {
|
||||
if (node.position[0] < accu.minX) {
|
||||
accu.minX = node.position[0];
|
||||
}
|
||||
if (node.position[1] < accu.minY) {
|
||||
accu.minY = node.position[1];
|
||||
}
|
||||
if (node.position[0] > accu.maxX) {
|
||||
accu.maxX = node.position[0];
|
||||
}
|
||||
if (node.position[1] > accu.maxY) {
|
||||
accu.maxY = node.position[1];
|
||||
}
|
||||
|
||||
return accu;
|
||||
}, {
|
||||
minX: nodes[0].position[0],
|
||||
minY: nodes[0].position[1],
|
||||
maxX: nodes[0].position[0],
|
||||
maxY: nodes[0].position[1],
|
||||
});
|
||||
};
|
||||
|
||||
export const scaleSmaller = ({scale, offset: [xOffset, yOffset]}: IZoomConfig): IZoomConfig => {
|
||||
scale /= 1.25;
|
||||
xOffset /= 1.25;
|
||||
yOffset /= 1.25;
|
||||
xOffset += window.innerWidth / 10;
|
||||
yOffset += window.innerHeight / 10;
|
||||
|
||||
return {
|
||||
scale,
|
||||
offset: [xOffset, yOffset],
|
||||
};
|
||||
};
|
||||
|
||||
export const scaleBigger = ({scale, offset: [xOffset, yOffset]}: IZoomConfig): IZoomConfig => {
|
||||
scale *= 1.25;
|
||||
xOffset -= window.innerWidth / 10;
|
||||
yOffset -= window.innerHeight / 10;
|
||||
xOffset *= 1.25;
|
||||
yOffset *= 1.25;
|
||||
|
||||
return {
|
||||
scale,
|
||||
offset: [xOffset, yOffset],
|
||||
};
|
||||
};
|
||||
|
||||
export const scaleReset = (config: IZoomConfig): IZoomConfig => {
|
||||
if (config.scale > 1) { // zoomed in
|
||||
while (config.scale > 1) {
|
||||
config = scaleSmaller(config);
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (config.scale < 1) {
|
||||
config = scaleBigger(config);
|
||||
}
|
||||
}
|
||||
|
||||
config.scale = 1;
|
||||
|
||||
return config;
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class ActionNetworkApi implements ICredentialType {
|
||||
name = 'actionNetworkApi';
|
||||
displayName = 'Action Network API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/documents',
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
];
|
||||
|
||||
export class GoogleDocsOAuth2Api implements ICredentialType {
|
||||
name = 'googleDocsOAuth2Api';
|
||||
extends = [
|
||||
'googleOAuth2Api',
|
||||
];
|
||||
displayName = 'Google Docs OAuth2 API';
|
||||
documentationUrl = 'google';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
];
|
||||
}
|
|
@ -17,6 +17,10 @@ export class Mqtt implements ICredentialType {
|
|||
name: 'mqtt',
|
||||
value: 'mqtt',
|
||||
},
|
||||
{
|
||||
name: 'mqtts',
|
||||
value: 'mqtts',
|
||||
},
|
||||
{
|
||||
name: 'ws',
|
||||
value: 'ws',
|
||||
|
|
|
@ -3,11 +3,6 @@ import {
|
|||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'com.intuit.quickbooks.accounting',
|
||||
'com.intuit.quickbooks.payment',
|
||||
];
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization
|
||||
|
||||
export class QuickBooksOAuth2Api implements ICredentialType {
|
||||
|
@ -15,7 +10,7 @@ export class QuickBooksOAuth2Api implements ICredentialType {
|
|||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'QuickBooks OAuth2 API';
|
||||
displayName = 'QuickBooks Online OAuth2 API';
|
||||
documentationUrl = 'quickbooks';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
|
@ -34,7 +29,7 @@ export class QuickBooksOAuth2Api implements ICredentialType {
|
|||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden',
|
||||
default: scopes.join(' '),
|
||||
default: 'com.intuit.quickbooks.accounting',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
|
|
551
packages/nodes-base/nodes/ActionNetwork/ActionNetwork.node.ts
Normal file
551
packages/nodes-base/nodes/ActionNetwork/ActionNetwork.node.ts
Normal file
|
@ -0,0 +1,551 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
actionNetworkApiRequest,
|
||||
adjustEventPayload,
|
||||
adjustPersonPayload,
|
||||
adjustPetitionPayload,
|
||||
handleListing,
|
||||
makeOsdiLink,
|
||||
resourceLoaders,
|
||||
simplifyResponse,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
attendanceFields,
|
||||
attendanceOperations,
|
||||
eventFields,
|
||||
eventOperations,
|
||||
personFields,
|
||||
personOperations,
|
||||
personTagFields,
|
||||
personTagOperations,
|
||||
petitionFields,
|
||||
petitionOperations,
|
||||
signatureFields,
|
||||
signatureOperations,
|
||||
tagFields,
|
||||
tagOperations,
|
||||
} from './descriptions';
|
||||
|
||||
import {
|
||||
AllFieldsUi,
|
||||
EmailAddressUi,
|
||||
Operation,
|
||||
PersonResponse,
|
||||
Resource,
|
||||
Response,
|
||||
} from './types';
|
||||
|
||||
export class ActionNetwork implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Action Network',
|
||||
name: 'actionNetwork',
|
||||
icon: 'file:actionNetwork.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
|
||||
description: 'Consume the Action Network API',
|
||||
defaults: {
|
||||
name: 'Action Network',
|
||||
color: '#9dd3ed',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'actionNetworkApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Attendance',
|
||||
value: 'attendance',
|
||||
},
|
||||
{
|
||||
name: 'Event',
|
||||
value: 'event',
|
||||
},
|
||||
{
|
||||
name: 'Person',
|
||||
value: 'person',
|
||||
},
|
||||
{
|
||||
name: 'Person Tag',
|
||||
value: 'personTag',
|
||||
},
|
||||
{
|
||||
name: 'Petition',
|
||||
value: 'petition',
|
||||
},
|
||||
{
|
||||
name: 'Signature',
|
||||
value: 'signature',
|
||||
},
|
||||
{
|
||||
name: 'Tag',
|
||||
value: 'tag',
|
||||
},
|
||||
],
|
||||
default: 'attendance',
|
||||
description: 'Resource to consume',
|
||||
},
|
||||
...attendanceOperations,
|
||||
...attendanceFields,
|
||||
...eventOperations,
|
||||
...eventFields,
|
||||
...personOperations,
|
||||
...personFields,
|
||||
...petitionOperations,
|
||||
...petitionFields,
|
||||
...signatureOperations,
|
||||
...signatureFields,
|
||||
...tagOperations,
|
||||
...tagFields,
|
||||
...personTagOperations,
|
||||
...personTagFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: resourceLoaders,
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as Resource;
|
||||
const operation = this.getNodeParameter('operation', 0) as Operation;
|
||||
|
||||
let response;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
if (resource === 'attendance') {
|
||||
|
||||
// **********************************************************************
|
||||
// attendance
|
||||
// **********************************************************************
|
||||
|
||||
// https://actionnetwork.org/docs/v2/attendances
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// attendance: create
|
||||
// ----------------------------------------
|
||||
|
||||
const personId = this.getNodeParameter('personId', i) as string;
|
||||
const eventId = this.getNodeParameter('eventId', i);
|
||||
|
||||
const body = makeOsdiLink(personId) as IDataObject;
|
||||
|
||||
const endpoint = `/events/${eventId}/attendances`;
|
||||
response = await actionNetworkApiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// attendance: get
|
||||
// ----------------------------------------
|
||||
|
||||
const eventId = this.getNodeParameter('eventId', i);
|
||||
const attendanceId = this.getNodeParameter('attendanceId', i);
|
||||
|
||||
const endpoint = `/events/${eventId}/attendances/${attendanceId}`;
|
||||
response = await actionNetworkApiRequest.call(this, 'GET', endpoint);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// attendance: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
const eventId = this.getNodeParameter('eventId', i);
|
||||
|
||||
const endpoint = `/events/${eventId}/attendances`;
|
||||
response = await handleListing.call(this, 'GET', endpoint);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'event') {
|
||||
|
||||
// **********************************************************************
|
||||
// event
|
||||
// **********************************************************************
|
||||
|
||||
// https://actionnetwork.org/docs/v2/events
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// event: create
|
||||
// ----------------------------------------
|
||||
|
||||
const body = {
|
||||
origin_system: this.getNodeParameter('originSystem', i),
|
||||
title: this.getNodeParameter('title', i),
|
||||
} as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as AllFieldsUi;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, adjustEventPayload(additionalFields));
|
||||
}
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'POST', '/events', body);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// event: get
|
||||
// ----------------------------------------
|
||||
|
||||
const eventId = this.getNodeParameter('eventId', i);
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'GET', `/events/${eventId}`);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// event: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
response = await handleListing.call(this, 'GET', '/events');
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'person') {
|
||||
|
||||
// **********************************************************************
|
||||
// person
|
||||
// **********************************************************************
|
||||
|
||||
// https://actionnetwork.org/docs/v2/people
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: create
|
||||
// ----------------------------------------
|
||||
|
||||
const emailAddresses = this.getNodeParameter('email_addresses', i) as EmailAddressUi;
|
||||
|
||||
const body = {
|
||||
person: {
|
||||
email_addresses: [emailAddresses.email_addresses_fields], // only one accepted by API
|
||||
},
|
||||
} as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body.person, adjustPersonPayload(additionalFields));
|
||||
}
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'POST', '/people', body);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: get
|
||||
// ----------------------------------------
|
||||
|
||||
const personId = this.getNodeParameter('personId', i);
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'GET', `/people/${personId}`) as PersonResponse;
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
response = await handleListing.call(this, 'GET', '/people') as PersonResponse[];
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// person: update
|
||||
// ----------------------------------------
|
||||
|
||||
const personId = this.getNodeParameter('personId', i);
|
||||
const body = {} as IDataObject;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, adjustPersonPayload(updateFields));
|
||||
} else {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Please enter at least one field to update for the ${resource}.`,
|
||||
);
|
||||
}
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'PUT', `/people/${personId}`, body);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'petition') {
|
||||
|
||||
// **********************************************************************
|
||||
// petition
|
||||
// **********************************************************************
|
||||
|
||||
// https://actionnetwork.org/docs/v2/petitions
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// petition: create
|
||||
// ----------------------------------------
|
||||
|
||||
const body = {
|
||||
origin_system: this.getNodeParameter('originSystem', i),
|
||||
title: this.getNodeParameter('title', i),
|
||||
} as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as AllFieldsUi;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, adjustPetitionPayload(additionalFields));
|
||||
}
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'POST', '/petitions', body);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// petition: get
|
||||
// ----------------------------------------
|
||||
|
||||
const petitionId = this.getNodeParameter('petitionId', i);
|
||||
|
||||
const endpoint = `/petitions/${petitionId}`;
|
||||
response = await actionNetworkApiRequest.call(this, 'GET', endpoint);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// petition: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
response = await handleListing.call(this, 'GET', '/petitions');
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// petition: update
|
||||
// ----------------------------------------
|
||||
|
||||
const petitionId = this.getNodeParameter('petitionId', i);
|
||||
const body = {} as IDataObject;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as AllFieldsUi;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, adjustPetitionPayload(updateFields));
|
||||
} else {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Please enter at least one field to update for the ${resource}.`,
|
||||
);
|
||||
}
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'PUT', `/petitions/${petitionId}`, body);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'signature') {
|
||||
|
||||
// **********************************************************************
|
||||
// signature
|
||||
// **********************************************************************
|
||||
|
||||
// https://actionnetwork.org/docs/v2/signatures
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// signature: create
|
||||
// ----------------------------------------
|
||||
|
||||
const personId = this.getNodeParameter('personId', i) as string;
|
||||
const petitionId = this.getNodeParameter('petitionId', i);
|
||||
|
||||
const body = makeOsdiLink(personId) as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, additionalFields);
|
||||
}
|
||||
|
||||
const endpoint = `/petitions/${petitionId}/signatures`;
|
||||
response = await actionNetworkApiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// signature: get
|
||||
// ----------------------------------------
|
||||
|
||||
const petitionId = this.getNodeParameter('petitionId', i);
|
||||
const signatureId = this.getNodeParameter('signatureId', i);
|
||||
|
||||
const endpoint = `/petitions/${petitionId}/signatures/${signatureId}`;
|
||||
response = await actionNetworkApiRequest.call(this, 'GET', endpoint);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// signature: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
const petitionId = this.getNodeParameter('petitionId', i);
|
||||
|
||||
const endpoint = `/petitions/${petitionId}/signatures`;
|
||||
response = await handleListing.call(this, 'GET', endpoint);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// signature: update
|
||||
// ----------------------------------------
|
||||
|
||||
const petitionId = this.getNodeParameter('petitionId', i);
|
||||
const signatureId = this.getNodeParameter('signatureId', i);
|
||||
const body = {};
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as AllFieldsUi;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, updateFields);
|
||||
} else {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Please enter at least one field to update for the ${resource}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const endpoint = `/petitions/${petitionId}/signatures/${signatureId}`;
|
||||
response = await actionNetworkApiRequest.call(this, 'PUT', endpoint, body);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'tag') {
|
||||
|
||||
// **********************************************************************
|
||||
// tag
|
||||
// **********************************************************************
|
||||
|
||||
// https://actionnetwork.org/docs/v2/tags
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// tag: create
|
||||
// ----------------------------------------
|
||||
|
||||
const body = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
} as IDataObject;
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'POST', '/tags', body);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// tag: get
|
||||
// ----------------------------------------
|
||||
|
||||
const tagId = this.getNodeParameter('tagId', i);
|
||||
|
||||
response = await actionNetworkApiRequest.call(this, 'GET', `/tags/${tagId}`);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// tag: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
response = await handleListing.call(this, 'GET', '/tags');
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'personTag') {
|
||||
|
||||
// **********************************************************************
|
||||
// personTag
|
||||
// **********************************************************************
|
||||
|
||||
// https://actionnetwork.org/docs/v2/taggings
|
||||
|
||||
if (operation === 'add') {
|
||||
|
||||
// ----------------------------------------
|
||||
// personTag: add
|
||||
// ----------------------------------------
|
||||
|
||||
const personId = this.getNodeParameter('personId', i) as string;
|
||||
const tagId = this.getNodeParameter('tagId', i);
|
||||
|
||||
const body = makeOsdiLink(personId) as IDataObject;
|
||||
|
||||
const endpoint = `/tags/${tagId}/taggings`;
|
||||
response = await actionNetworkApiRequest.call(this, 'POST', endpoint, body);
|
||||
|
||||
} else if (operation === 'remove') {
|
||||
|
||||
// ----------------------------------------
|
||||
// personTag: remove
|
||||
// ----------------------------------------
|
||||
|
||||
const tagId = this.getNodeParameter('tagId', i);
|
||||
const taggingId = this.getNodeParameter('taggingId', i);
|
||||
|
||||
const endpoint = `/tags/${tagId}/taggings/${taggingId}`;
|
||||
response = await actionNetworkApiRequest.call(this, 'DELETE', endpoint);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const simplify = this.getNodeParameter('simple', i, false) as boolean;
|
||||
|
||||
if (simplify) {
|
||||
response = operation === 'getAll'
|
||||
? response.map((i: Response) => simplifyResponse(i, resource))
|
||||
: simplifyResponse(response, resource);
|
||||
}
|
||||
|
||||
Array.isArray(response)
|
||||
? returnData.push(...response)
|
||||
: returnData.push(response);
|
||||
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.message });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
348
packages/nodes-base/nodes/ActionNetwork/GenericFunctions.ts
Normal file
348
packages/nodes-base/nodes/ActionNetwork/GenericFunctions.ts
Normal file
|
@ -0,0 +1,348 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
flow,
|
||||
omit,
|
||||
} from 'lodash';
|
||||
|
||||
import {
|
||||
AllFieldsUi,
|
||||
FieldWithPrimaryField,
|
||||
LinksFieldContainer,
|
||||
PersonResponse,
|
||||
PetitionResponse,
|
||||
Resource,
|
||||
Response,
|
||||
} from './types';
|
||||
|
||||
export async function actionNetworkApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
) {
|
||||
const credentials = this.getCredentials('actionNetworkApi') as { apiKey: string } | undefined;
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'OSDI-API-Token': credentials.apiKey,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: `https://actionnetwork.org/api/v2${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
if (!Object.keys(qs).length) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleListing(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
options?: { returnAll: true },
|
||||
) {
|
||||
const returnData: IDataObject[] = [];
|
||||
let responseData;
|
||||
|
||||
qs.perPage = 25; // max
|
||||
qs.page = 1;
|
||||
|
||||
const returnAll = options?.returnAll ?? this.getNodeParameter('returnAll', 0, false) as boolean;
|
||||
const limit = this.getNodeParameter('limit', 0, 0) as number;
|
||||
|
||||
const itemsKey = toItemsKey(endpoint);
|
||||
|
||||
do {
|
||||
responseData = await actionNetworkApiRequest.call(this, method, endpoint, body, qs);
|
||||
const items = responseData._embedded[itemsKey];
|
||||
returnData.push(...items);
|
||||
|
||||
if (!returnAll && returnData.length >= limit) {
|
||||
return returnData.slice(0, limit);
|
||||
}
|
||||
|
||||
qs.page = responseData.page as number;
|
||||
} while (responseData.total_pages && qs.page < responseData.total_pages);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// helpers
|
||||
// ----------------------------------------
|
||||
|
||||
/**
|
||||
* Convert an endpoint to the key needed to access data in the response.
|
||||
*/
|
||||
const toItemsKey = (endpoint: string) => {
|
||||
|
||||
// handle two-resource endpoint
|
||||
if (
|
||||
endpoint.includes('/signatures') ||
|
||||
endpoint.includes('/attendances') ||
|
||||
endpoint.includes('/taggings')
|
||||
) {
|
||||
endpoint = endpoint.split('/').pop()!;
|
||||
}
|
||||
|
||||
return `osdi:${endpoint.replace(/\//g, '')}`;
|
||||
};
|
||||
|
||||
export const extractId = (response: LinksFieldContainer) => {
|
||||
return response._links.self.href.split('/').pop() ?? 'No ID';
|
||||
};
|
||||
|
||||
export const makeOsdiLink = (personId: string) => {
|
||||
return {
|
||||
_links: {
|
||||
'osdi:person': {
|
||||
href: `https://actionnetwork.org/api/v2/people/${personId}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const isPrimary = (field: FieldWithPrimaryField) => field.primary;
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// field adjusters
|
||||
// ----------------------------------------
|
||||
|
||||
function adjustLanguagesSpoken(allFields: AllFieldsUi) {
|
||||
if (!allFields.languages_spoken) return allFields;
|
||||
|
||||
return {
|
||||
...omit(allFields, ['languages_spoken']),
|
||||
languages_spoken: [allFields.languages_spoken],
|
||||
};
|
||||
}
|
||||
|
||||
function adjustPhoneNumbers(allFields: AllFieldsUi) {
|
||||
if (!allFields.phone_numbers) return allFields;
|
||||
|
||||
return {
|
||||
...omit(allFields, ['phone_numbers']),
|
||||
phone_numbers: [
|
||||
allFields.phone_numbers.phone_numbers_fields,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function adjustPostalAddresses(allFields: AllFieldsUi) {
|
||||
if (!allFields.postal_addresses) return allFields;
|
||||
|
||||
if (allFields.postal_addresses.postal_addresses_fields.length) {
|
||||
const adjusted = allFields.postal_addresses.postal_addresses_fields.map((field) => {
|
||||
const copy: IDataObject = {
|
||||
...omit(field, ['address_lines', 'location']),
|
||||
};
|
||||
|
||||
if (field.address_lines) {
|
||||
copy.address_lines = [field.address_lines];
|
||||
}
|
||||
|
||||
if (field.location) {
|
||||
copy.location = field.location.location_fields;
|
||||
}
|
||||
|
||||
return copy;
|
||||
});
|
||||
|
||||
return {
|
||||
...omit(allFields, ['postal_addresses']),
|
||||
postal_addresses: adjusted,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function adjustLocation(allFields: AllFieldsUi) {
|
||||
if (!allFields.location) return allFields;
|
||||
|
||||
const locationFields = allFields.location.postal_addresses_fields;
|
||||
|
||||
const adjusted: IDataObject = {
|
||||
...omit(locationFields, ['address_lines', 'location']),
|
||||
};
|
||||
|
||||
if (locationFields.address_lines) {
|
||||
adjusted.address_lines = [locationFields.address_lines];
|
||||
}
|
||||
|
||||
if (locationFields.location) {
|
||||
adjusted.location = locationFields.location.location_fields;
|
||||
}
|
||||
|
||||
return {
|
||||
...omit(allFields, ['location']),
|
||||
location: adjusted,
|
||||
};
|
||||
}
|
||||
|
||||
function adjustTargets(allFields: AllFieldsUi) {
|
||||
if (!allFields.target) return allFields;
|
||||
|
||||
const adjusted = allFields.target.split(',').map(value => ({ name: value }));
|
||||
|
||||
return {
|
||||
...omit(allFields, ['target']),
|
||||
target: adjusted,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// payload adjusters
|
||||
// ----------------------------------------
|
||||
|
||||
export const adjustPersonPayload = flow(
|
||||
adjustLanguagesSpoken,
|
||||
adjustPhoneNumbers,
|
||||
adjustPostalAddresses,
|
||||
);
|
||||
|
||||
export const adjustPetitionPayload = adjustTargets;
|
||||
|
||||
export const adjustEventPayload = adjustLocation;
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// resource loaders
|
||||
// ----------------------------------------
|
||||
|
||||
async function loadResource(this: ILoadOptionsFunctions, resource: string) {
|
||||
return await handleListing.call(this, 'GET', `/${resource}`, {}, {}, { returnAll: true });
|
||||
}
|
||||
|
||||
export const resourceLoaders = {
|
||||
|
||||
async getTags(this: ILoadOptionsFunctions) {
|
||||
const tags = await loadResource.call(this, 'tags') as Array<{ name: string } & LinksFieldContainer>;
|
||||
|
||||
return tags.map((tag) => ({ name: tag.name, value: extractId(tag) }));
|
||||
},
|
||||
|
||||
async getTaggings(this: ILoadOptionsFunctions) {
|
||||
const tagId = this.getNodeParameter('tagId', 0);
|
||||
const endpoint = `/tags/${tagId}/taggings`;
|
||||
|
||||
// two-resource endpoint, so direct call
|
||||
const taggings = await handleListing.call(
|
||||
this, 'GET', endpoint, {}, {}, { returnAll: true },
|
||||
) as LinksFieldContainer[];
|
||||
|
||||
return taggings.map((tagging) => {
|
||||
const taggingId = extractId(tagging);
|
||||
|
||||
return {
|
||||
name: taggingId,
|
||||
value: taggingId,
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// response simplifiers
|
||||
// ----------------------------------------
|
||||
|
||||
export const simplifyResponse = (response: Response, resource: Resource) => {
|
||||
if (resource === 'person') {
|
||||
return simplifyPersonResponse(response as PersonResponse);
|
||||
} else if (resource === 'petition') {
|
||||
return simplifyPetitionResponse(response as PetitionResponse);
|
||||
}
|
||||
|
||||
const fieldsToSimplify = [
|
||||
'identifiers',
|
||||
'_links',
|
||||
'action_network:sponsor',
|
||||
'reminders',
|
||||
];
|
||||
|
||||
return {
|
||||
id: extractId(response),
|
||||
...omit(response, fieldsToSimplify),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
const simplifyPetitionResponse = (response: PetitionResponse) => {
|
||||
const fieldsToSimplify = [
|
||||
'identifiers',
|
||||
'_links',
|
||||
'action_network:hidden',
|
||||
'_embedded',
|
||||
];
|
||||
|
||||
return {
|
||||
id: extractId(response),
|
||||
...omit(response, fieldsToSimplify),
|
||||
creator: simplifyPersonResponse(response._embedded['osdi:creator']),
|
||||
};
|
||||
};
|
||||
|
||||
const simplifyPersonResponse = (response: PersonResponse) => {
|
||||
const emailAddress = response.email_addresses.filter(isPrimary);
|
||||
const phoneNumber = response.phone_numbers.filter(isPrimary);
|
||||
const postalAddress = response.postal_addresses.filter(isPrimary);
|
||||
|
||||
const fieldsToSimplify = [
|
||||
'identifiers',
|
||||
'email_addresses',
|
||||
'phone_numbers',
|
||||
'postal_addresses',
|
||||
'languages_spoken',
|
||||
'_links',
|
||||
];
|
||||
|
||||
return {
|
||||
id: extractId(response),
|
||||
...omit(response, fieldsToSimplify),
|
||||
...{ email_address: emailAddress[0].address || '' },
|
||||
...{ phone_number: phoneNumber[0].number || '' },
|
||||
...{
|
||||
postal_address: {
|
||||
...postalAddress && omit(postalAddress[0], 'address_lines'),
|
||||
address_lines: postalAddress[0].address_lines ?? '',
|
||||
},
|
||||
},
|
||||
language_spoken: response.languages_spoken[0],
|
||||
};
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 1 79.89 75.96"><defs><style>.cls-1{fill:#9dd3ed;}</style></defs><title>actionnetwork</title><circle class="cls-1" cx="40.42" cy="5.43" r="5.43"/><path class="cls-1" d="M26.32,15.92c9.94-2.11,15.06,5.25,16.52,11.47.92,3.93,4.28,3.87,6.58,2.73S56,28.48,53,21.23c-1.82-4.41-4.14-8.44-8.26-11.08A6.42,6.42,0,0,1,34.66,8.3a6.88,6.88,0,0,1-.44-1.14c-8.85.1-14.7,8-16.45,13.26a18.85,18.85,0,0,1,8.55-4.5"/><circle class="cls-1" cx="5.43" cy="29.87" r="5.43"/><path class="cls-1" d="M11.23,46.45c1-10.11,9.51-12.8,15.88-12.33,4,.29,5-2.93,4.57-5.47s.4-6.78-7.41-6.1C19.52,23,15,24,11.23,27.12a6.37,6.37,0,0,1,.34,4.67,6.62,6.62,0,0,1-.93,1.86,6.39,6.39,0,0,1-4.19,2.57,6.31,6.31,0,0,1-1.22.08C2.68,44.78,8.52,52.73,13,56a18.92,18.92,0,0,1-1.75-9.51"/><circle class="cls-1" cx="18.08" cy="70.13" r="5.43"/><path class="cls-1" d="M35.65,69.73c-9.33-4-9.25-13-6.83-18.91,1.52-3.74-1.25-5.64-3.79-6S18.7,42.31,16.94,50c-1.07,4.64-1.51,9.27.32,13.81a6.44,6.44,0,0,1,4.55,1.12,6.47,6.47,0,0,1,2.63,6.24,7.55,7.55,0,0,1-.3,1.18c7.27,5,16.64,2,21.09-1.3a18.8,18.8,0,0,1-9.58-1.27"/><circle class="cls-1" cx="60.58" cy="70.53" r="5.43"/><path class="cls-1" d="M65.83,53.76c-6.81,7.55-15.28,4.6-20.11.42-3-2.64-5.74-.62-6.93,1.66S34.42,61,41.1,65.16c4.06,2.5,8.3,4.4,13.19,4.11a6.43,6.43,0,0,1,2.51-4,6.76,6.76,0,0,1,1.86-.93,6.38,6.38,0,0,1,4.9.44,5.94,5.94,0,0,1,1,.66c7.1-5.28,7.17-15.14,5.52-20.4a18.91,18.91,0,0,1-4.27,8.67"/><circle class="cls-1" cx="74.46" cy="30.68" r="5.43"/><path class="cls-1" d="M60.13,20.51c5.08,8.81-.34,16-5.8,19.25C50.87,41.85,52,45,53.76,46.87s3.6,5.76,9.57.68c3.64-3.08,6.75-6.54,8-11.27A6.42,6.42,0,0,1,70,26.09a6.21,6.21,0,0,1,.94-.77c-2.83-8.39-12.19-11.49-17.69-11.55a18.94,18.94,0,0,1,6.92,6.74"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,185 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
makeSimpleField,
|
||||
} from './SharedFields';
|
||||
|
||||
export const attendanceOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attendance',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const attendanceFields = [
|
||||
// ----------------------------------------
|
||||
// attendance: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'personId',
|
||||
description: 'ID of the person to create an attendance for.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attendance',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Event ID',
|
||||
name: 'eventId',
|
||||
description: 'ID of the event to create an attendance for.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attendance',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('attendance', 'create'),
|
||||
|
||||
// ----------------------------------------
|
||||
// attendance: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Event ID',
|
||||
name: 'eventId',
|
||||
description: 'ID of the event whose attendance to retrieve.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attendance',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Attendance ID',
|
||||
name: 'attendanceId',
|
||||
description: 'ID of the attendance to retrieve.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attendance',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('attendance', 'get'),
|
||||
|
||||
// ----------------------------------------
|
||||
// attendance: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Event ID',
|
||||
name: 'eventId',
|
||||
description: 'ID of the event to create an attendance for.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attendance',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attendance',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attendance',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('attendance', 'getAll'),
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,168 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
eventAdditionalFieldsOptions,
|
||||
makeSimpleField,
|
||||
} from './SharedFields';
|
||||
|
||||
export const eventOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const eventFields = [
|
||||
// ----------------------------------------
|
||||
// event: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Origin System',
|
||||
name: 'originSystem',
|
||||
description: 'Source where the event originated.',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
description: 'Title of the event to create.',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('event', 'create'),
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: eventAdditionalFieldsOptions,
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// event: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Event ID',
|
||||
name: 'eventId',
|
||||
description: 'ID of the event to retrieve.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('event', 'get'),
|
||||
|
||||
// ----------------------------------------
|
||||
// event: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('event', 'getAll'),
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,250 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
makeSimpleField,
|
||||
personAdditionalFieldsOptions,
|
||||
} from './SharedFields';
|
||||
|
||||
export const personOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'person',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const personFields = [
|
||||
// ----------------------------------------
|
||||
// person: create
|
||||
// ----------------------------------------
|
||||
makeSimpleField('person', 'create'),
|
||||
{
|
||||
displayName: 'Email Address', // on create, only _one_ must be passed in
|
||||
name: 'email_addresses',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
placeholder: 'Add Email Address Field',
|
||||
description: 'Person’s email addresses.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'person',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Email Addresses Fields',
|
||||
name: 'email_addresses_fields',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Person\'s email address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Primary',
|
||||
name: 'primary',
|
||||
type: 'hidden',
|
||||
default: true,
|
||||
description: 'Whether this is the person\'s primary email address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
default: 'subscribed',
|
||||
description: 'Subscription status of this email address.',
|
||||
options: [
|
||||
{
|
||||
name: 'Bouncing',
|
||||
value: 'bouncing',
|
||||
},
|
||||
{
|
||||
name: 'Previous Bounce',
|
||||
value: 'previous bounce',
|
||||
},
|
||||
{
|
||||
name: 'Previous Spam Complaint',
|
||||
value: 'previous spam complaint',
|
||||
},
|
||||
{
|
||||
name: 'Spam Complaint',
|
||||
value: 'spam complaint',
|
||||
},
|
||||
{
|
||||
name: 'Subscribed',
|
||||
value: 'subscribed',
|
||||
},
|
||||
{
|
||||
name: 'Unsubscribed',
|
||||
value: 'unsubscribed',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'person',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: personAdditionalFieldsOptions,
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// person: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'personId',
|
||||
description: 'ID of the person to retrieve.',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'person',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('person', 'get'),
|
||||
|
||||
// ----------------------------------------
|
||||
// person: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'person',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'person',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('person', 'getAll'),
|
||||
|
||||
// ----------------------------------------
|
||||
// person: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'personId',
|
||||
description: 'ID of the person to update.',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'person',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('person', 'update'),
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'person',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: personAdditionalFieldsOptions,
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,122 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const personTagOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'personTag',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
},
|
||||
{
|
||||
name: 'Remove',
|
||||
value: 'remove',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const personTagFields = [
|
||||
// ----------------------------------------
|
||||
// personTag: add
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Tag ID',
|
||||
name: 'tagId',
|
||||
description: 'ID of the tag to add.',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
required: true,
|
||||
default: [],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'personTag',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'personId',
|
||||
description: 'ID of the person to add the tag to.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'personTag',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// personTag: remove
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Tag ID',
|
||||
name: 'tagId',
|
||||
description: 'ID of the tag whose tagging to delete.',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTags',
|
||||
},
|
||||
default: [],
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'personTag',
|
||||
],
|
||||
operation: [
|
||||
'remove',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Tagging ID',
|
||||
name: 'taggingId',
|
||||
description: 'ID of the tagging to remove.',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: 'tagId',
|
||||
loadOptionsMethod: 'getTaggings',
|
||||
},
|
||||
required: true,
|
||||
default: [],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'personTag',
|
||||
],
|
||||
operation: [
|
||||
'remove',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,213 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
makeSimpleField,
|
||||
petitionAdditionalFieldsOptions,
|
||||
} from './SharedFields';
|
||||
|
||||
export const petitionOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const petitionFields = [
|
||||
// ----------------------------------------
|
||||
// petition: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Origin System',
|
||||
name: 'originSystem',
|
||||
description: 'Source where the petition originated.',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
description: 'Title of the petition to create.',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('petition', 'create'),
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: petitionAdditionalFieldsOptions,
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// petition: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Petition ID',
|
||||
name: 'petitionId',
|
||||
description: 'ID of the petition to retrieve.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('petition', 'get'),
|
||||
|
||||
// ----------------------------------------
|
||||
// petition: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('petition', 'getAll'),
|
||||
|
||||
// ----------------------------------------
|
||||
// petition: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Petition ID',
|
||||
name: 'petitionId',
|
||||
description: 'ID of the petition to update.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('petition', 'update'),
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'petition',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: petitionAdditionalFieldsOptions,
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,376 @@
|
|||
import {
|
||||
Operation,
|
||||
Resource,
|
||||
} from '../types';
|
||||
|
||||
export const languageOptions = [
|
||||
{
|
||||
name: 'Danish',
|
||||
value: 'da',
|
||||
},
|
||||
{
|
||||
name: 'Dutch',
|
||||
value: 'nl',
|
||||
},
|
||||
{
|
||||
name: 'English',
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
name: 'Finnish',
|
||||
value: 'fi',
|
||||
},
|
||||
{
|
||||
name: 'French',
|
||||
value: 'fr',
|
||||
},
|
||||
{
|
||||
name: 'German',
|
||||
value: 'de',
|
||||
},
|
||||
{
|
||||
name: 'Hungarian',
|
||||
value: 'hu',
|
||||
},
|
||||
{
|
||||
name: 'Indonesian',
|
||||
value: 'id',
|
||||
},
|
||||
{
|
||||
name: 'Japanese',
|
||||
value: 'ja',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese - Portugal',
|
||||
value: 'pt',
|
||||
},
|
||||
{
|
||||
name: 'Portuguese - Brazil',
|
||||
value: 'br',
|
||||
},
|
||||
{
|
||||
name: 'Rumanian',
|
||||
value: 'ru',
|
||||
},
|
||||
{
|
||||
name: 'Spanish',
|
||||
value: 'es',
|
||||
},
|
||||
{
|
||||
name: 'Swedish',
|
||||
value: 'sv',
|
||||
},
|
||||
{
|
||||
name: 'Turkish',
|
||||
value: 'tr',
|
||||
},
|
||||
{
|
||||
name: 'Welsh',
|
||||
value: 'cy',
|
||||
},
|
||||
] as const;
|
||||
|
||||
const postalAddressesFields = [
|
||||
{
|
||||
displayName: 'Primary',
|
||||
name: 'primary',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether this is the person\'s primary address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Address Line',
|
||||
name: 'address_lines',
|
||||
type: 'string', // The Action Network API expects a string array but ignores any string beyond the first, so this input field is simplified to string.
|
||||
default: '',
|
||||
description: 'Line for a person\'s address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Locality',
|
||||
name: 'locality',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'City or other local administrative area. If blank, this will be filled in based on Action Network\'s geocoding.',
|
||||
},
|
||||
{
|
||||
displayName: 'Region',
|
||||
name: 'region',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'State or subdivision code per ISO 3166-2.',
|
||||
},
|
||||
{
|
||||
displayName: 'Postal Code',
|
||||
name: 'postal_code',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Region specific postal code, such as ZIP code.',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Country code according to ISO 3166-1 Alpha-2. Defaults to US.',
|
||||
},
|
||||
{
|
||||
displayName: 'Language',
|
||||
name: 'language',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Language in which the address is recorded, per ISO 639.',
|
||||
},
|
||||
{
|
||||
displayName: 'Location',
|
||||
name: 'location',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Location Fields',
|
||||
name: 'location_fields',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Latitude',
|
||||
name: 'latitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Latitude of the location of the address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Longitude',
|
||||
name: 'longitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Longitude of the location of the address.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const eventAdditionalFieldsOptions = [
|
||||
{
|
||||
displayName: 'Browser URL',
|
||||
name: 'browser_url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL to this event’s page on the Action Network or a third party.',
|
||||
},
|
||||
{
|
||||
displayName: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Description of the event. HTML supported.',
|
||||
},
|
||||
{
|
||||
displayName: 'End Date',
|
||||
name: 'end_date',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'End date and time of the event.',
|
||||
},
|
||||
{
|
||||
displayName: 'Featured Image URL',
|
||||
name: 'featured_image_url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL to this event’s featured image on the Action Network.',
|
||||
},
|
||||
{
|
||||
displayName: 'Instructions',
|
||||
name: 'instructions',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Event\'s instructions for activists, visible after they RSVP. HTML supported.',
|
||||
},
|
||||
{
|
||||
displayName: 'Location',
|
||||
name: 'location',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
placeholder: 'Add Location Field',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
options: [
|
||||
// different name, identical structure
|
||||
{
|
||||
displayName: 'Postal Addresses Fields',
|
||||
name: 'postal_addresses_fields',
|
||||
placeholder: 'Add Postal Address Field',
|
||||
values: postalAddressesFields,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Internal (not public) title of the event.',
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date',
|
||||
name: 'start_date',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Start date and time of the event.',
|
||||
},
|
||||
];
|
||||
|
||||
export const personAdditionalFieldsOptions = [
|
||||
{
|
||||
displayName: 'Family Name',
|
||||
name: 'family_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Person’s last name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Given Name',
|
||||
name: 'given_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Person’s first name.',
|
||||
},
|
||||
{
|
||||
displayName: 'Language Spoken',
|
||||
name: 'languages_spoken',
|
||||
type: 'options', // Action Network accepts a `string[]` of language codes, but supports only one language per person - sending an array of 2+ languages will result in the first valid language being set as the preferred language for the person. Therefore, the user may select only one option in the n8n UI.
|
||||
default: [],
|
||||
description: 'Language spoken by the person',
|
||||
options: languageOptions,
|
||||
},
|
||||
{
|
||||
displayName: 'Phone Number', // on create, only _one_ must be passed in
|
||||
name: 'phone_numbers',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
placeholder: 'Add Phone Numbers Field',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Phone Numbers Fields',
|
||||
name: 'phone_numbers_fields',
|
||||
placeholder: 'Add Phone Number Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Number',
|
||||
name: 'number',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Person\'s mobile number, in international format without the plus sign.',
|
||||
},
|
||||
{
|
||||
displayName: 'Primary',
|
||||
name: 'primary',
|
||||
type: 'hidden',
|
||||
default: true,
|
||||
description: 'Whether this is the person\'s primary phone number.',
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
default: 'subscribed',
|
||||
description: 'Subscription status of this number.',
|
||||
options: [
|
||||
{
|
||||
name: 'Bouncing',
|
||||
value: 'bouncing',
|
||||
},
|
||||
{
|
||||
name: 'Previous Bounce',
|
||||
value: 'previous bounce',
|
||||
},
|
||||
{
|
||||
name: 'Subscribed',
|
||||
value: 'subscribed',
|
||||
},
|
||||
{
|
||||
name: 'Unsubscribed',
|
||||
value: 'unsubscribed',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Postal Addresses',
|
||||
name: 'postal_addresses',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
placeholder: 'Add Postal Addresses Field',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Postal Addresses Fields',
|
||||
name: 'postal_addresses_fields',
|
||||
placeholder: 'Add Postal Address Field',
|
||||
values: postalAddressesFields,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const petitionAdditionalFieldsOptions = [
|
||||
{
|
||||
displayName: 'Browser URL',
|
||||
name: 'browser_url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL to this petition’s page on the Action Network or a third party.',
|
||||
},
|
||||
{
|
||||
displayName: 'Featured Image URL',
|
||||
name: 'featured_image_url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL to this action’s featured image on the Action Network.',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Internal (not public) title of the petition.',
|
||||
},
|
||||
{
|
||||
displayName: 'Petition Text',
|
||||
name: 'petition_text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Text of the letter to the petition’s target.',
|
||||
},
|
||||
{
|
||||
displayName: 'Targets',
|
||||
name: 'target',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Comma-separated names of targets for this petition.',
|
||||
},
|
||||
];
|
||||
|
||||
export const makeSimpleField = (resource: Resource, operation: Operation) => ({
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource,
|
||||
],
|
||||
operation: [
|
||||
operation,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'Return a simplified version of the response instead of the raw data.',
|
||||
});
|
|
@ -0,0 +1,282 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
makeSimpleField,
|
||||
} from './SharedFields';
|
||||
|
||||
export const signatureOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const signatureFields = [
|
||||
// ----------------------------------------
|
||||
// signature: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Petition ID',
|
||||
name: 'petitionId',
|
||||
description: 'ID of the petition to sign.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'personId',
|
||||
description: 'ID of the person whose signature to create.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('signature', 'create'),
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Comments',
|
||||
name: 'comments',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Comments to leave when signing this petition.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// signature: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Petition ID',
|
||||
name: 'petitionId',
|
||||
description: 'ID of the petition whose signature to retrieve.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Signature ID',
|
||||
name: 'signatureId',
|
||||
description: 'ID of the signature to retrieve.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('signature', 'get'),
|
||||
|
||||
// ----------------------------------------
|
||||
// signature: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Petition ID',
|
||||
name: 'petitionId',
|
||||
description: 'ID of the petition whose signatures to retrieve.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('signature', 'getAll'),
|
||||
|
||||
// ----------------------------------------
|
||||
// signature: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Petition ID',
|
||||
name: 'petitionId',
|
||||
description: 'ID of the petition whose signature to update.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Signature ID',
|
||||
name: 'signatureId',
|
||||
description: 'ID of the signature to update.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('signature', 'update'),
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'signature',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Comments',
|
||||
name: 'comments',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Comments to leave when signing this petition.',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,131 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
makeSimpleField,
|
||||
} from './SharedFields';
|
||||
|
||||
export const tagOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const tagFields = [
|
||||
// ----------------------------------------
|
||||
// tag: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
description: 'Name of the tag to create.',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('tag', 'create'),
|
||||
|
||||
// ----------------------------------------
|
||||
// tag: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Tag ID',
|
||||
name: 'tagId',
|
||||
description: 'ID of the tag to retrieve.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('tag', 'get'),
|
||||
|
||||
// ----------------------------------------
|
||||
// tag: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'tag',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
makeSimpleField('tag', 'getAll'),
|
||||
] as INodeProperties[];
|
|
@ -0,0 +1,7 @@
|
|||
export * from './AttendanceDescription';
|
||||
export * from './EventDescription';
|
||||
export * from './PersonDescription';
|
||||
export * from './PersonTagDescription';
|
||||
export * from './PetitionDescription';
|
||||
export * from './SignatureDescription';
|
||||
export * from './TagDescription';
|
99
packages/nodes-base/nodes/ActionNetwork/types.d.ts
vendored
Normal file
99
packages/nodes-base/nodes/ActionNetwork/types.d.ts
vendored
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { languageOptions } from './descriptions/SharedFields';
|
||||
|
||||
export type Resource = 'attendance' | 'event' | 'person' | 'personTag' | 'petition' | 'signature' | 'tag';
|
||||
|
||||
export type Operation = 'create' | 'delete' | 'get' | 'getAll' | 'update' | 'add' | 'remove';
|
||||
|
||||
export type LanguageCodes = typeof languageOptions[number]['value']
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// UI fields
|
||||
// ----------------------------------------
|
||||
|
||||
export type AllFieldsUi = {
|
||||
email_addresses: EmailAddressUi;
|
||||
postal_addresses: PostalAddressesUi;
|
||||
phone_numbers: PhoneNumberUi;
|
||||
languages_spoken: LanguageCodes;
|
||||
target: string;
|
||||
location: LocationUi;
|
||||
}
|
||||
|
||||
export type EmailAddressUi = {
|
||||
email_addresses_fields: EmailAddressField,
|
||||
}
|
||||
|
||||
export type EmailAddressField = {
|
||||
primary: boolean;
|
||||
address: string;
|
||||
status: EmailStatus;
|
||||
}
|
||||
|
||||
type BaseStatus = 'subscribed' | 'unsubscribed' | 'bouncing' | 'previous bounce';
|
||||
|
||||
type EmailStatus = BaseStatus | 'spam complaint' | 'previous spam complaint';
|
||||
|
||||
type PhoneNumberUi = {
|
||||
phone_numbers_fields: PhoneNumberField[],
|
||||
}
|
||||
|
||||
export type PhoneNumberField = {
|
||||
primary: boolean;
|
||||
number: string;
|
||||
status: BaseStatus;
|
||||
};
|
||||
|
||||
type PostalAddressesUi = {
|
||||
postal_addresses_fields: PostalAddressField[],
|
||||
}
|
||||
|
||||
type LocationUi = {
|
||||
postal_addresses_fields: PostalAddressField,
|
||||
}
|
||||
|
||||
export type PostalAddressField = {
|
||||
primary: boolean;
|
||||
address_lines: string;
|
||||
locality: string;
|
||||
region: string;
|
||||
postal_code: string;
|
||||
country: string;
|
||||
language: LanguageCodes;
|
||||
location: { location_fields: LatitudeLongitude }
|
||||
}
|
||||
|
||||
type LatitudeLongitude = {
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
}
|
||||
|
||||
export type FieldWithPrimaryField = EmailAddressField | PhoneNumberField | PostalAddressField;
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// responses
|
||||
// ----------------------------------------
|
||||
|
||||
export type LinksFieldContainer = { _links: { self: { href: string } } };
|
||||
|
||||
export type Response = JsonObject & LinksFieldContainer;
|
||||
|
||||
export type PersonResponse = Response & {
|
||||
identifiers: string[];
|
||||
email_addresses: EmailAddressField[];
|
||||
phone_numbers: PhoneNumberField[];
|
||||
postal_addresses: PostalAddressField[];
|
||||
languages_spoken: LanguageCodes[];
|
||||
};
|
||||
|
||||
export type PetitionResponse = Response & { _embedded: { 'osdi:creator': PersonResponse } };
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// utils
|
||||
// ----------------------------------------
|
||||
|
||||
export type JsonValue = string | number | boolean | null | JsonObject | JsonValue[];
|
||||
|
||||
export type JsonObject = { [key: string]: JsonValue };
|
|
@ -27,6 +27,11 @@
|
|||
"icon": "☀️",
|
||||
"url": "https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
|
||||
"icon": "🔗",
|
||||
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
|
||||
},
|
||||
{
|
||||
"label": "15 Google apps you can combine and automate to increase productivity",
|
||||
"icon": "💡",
|
||||
|
|
|
@ -163,6 +163,15 @@ export class AwsS3 implements INodeType {
|
|||
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
|
||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html
|
||||
if (operation === 'delete') {
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
|
||||
responseData = await awsApiRequestSOAP.call(this, `${name}.s3`, 'DELETE', '', '', {}, headers);
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
|
|
|
@ -20,6 +20,11 @@ export const bucketOperations = [
|
|||
value: 'create',
|
||||
description: 'Create a bucket',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a bucket',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
|
@ -152,6 +157,29 @@ export const bucketFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'bucket',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Name of the AWS S3 bucket to delete.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* bucket:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
|
|
@ -16,6 +16,13 @@
|
|||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.calendlyTrigger/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -58,6 +58,11 @@
|
|||
"icon": "⚙️",
|
||||
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "Celebrating World Poetry Day with a daily poem in Telegram",
|
||||
"icon": "📜",
|
||||
|
@ -123,7 +128,7 @@
|
|||
"alias": [
|
||||
"Time",
|
||||
"Scheduler",
|
||||
"Poll"
|
||||
"Polling"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
|
|
|
@ -12,6 +12,13 @@
|
|||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.crypto/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
|
||||
"icon": "🔗",
|
||||
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
|
|
|
@ -21,6 +21,12 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Shell",
|
||||
"Command",
|
||||
"OS",
|
||||
"Bash"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Helpers"
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"n8n"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Helpers"
|
||||
|
|
|
@ -64,6 +64,11 @@
|
|||
"icon": "⏲",
|
||||
"url": "https://n8n.io/blog/creating-triggers-for-n8n-workflows-using-polling/"
|
||||
},
|
||||
{
|
||||
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
|
||||
"icon": "🔗",
|
||||
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
|
||||
},
|
||||
{
|
||||
"label": "Build your own virtual assistant with n8n: A step by step guide",
|
||||
"icon": "👦",
|
||||
|
@ -101,6 +106,13 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Code",
|
||||
"Javascript",
|
||||
"Custom Code",
|
||||
"Script",
|
||||
"cpde"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Data Transformation"
|
||||
|
|
|
@ -70,16 +70,15 @@ export async function ghostApiRequestAllItems(this: IHookFunctions | IExecuteFun
|
|||
|
||||
let responseData;
|
||||
|
||||
query.limit = 20;
|
||||
|
||||
let uri: string | undefined;
|
||||
query.limit = 50;
|
||||
query.page = 1;
|
||||
|
||||
do {
|
||||
responseData = await ghostApiRequest.call(this, method, endpoint, body, query, uri);
|
||||
uri = responseData.meta.pagination.next;
|
||||
responseData = await ghostApiRequest.call(this, method, endpoint, body, query);
|
||||
query.page = responseData.meta.pagination.next;
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
} while (
|
||||
responseData.meta.pagination.next !== null
|
||||
query.page !== null
|
||||
);
|
||||
return returnData;
|
||||
}
|
||||
|
|
|
@ -70,6 +70,10 @@ export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOp
|
|||
export function simplify(responseData: any | [any]) { // tslint:disable-line:no-any
|
||||
const response = [];
|
||||
for (const { columnHeader: { dimensions }, data: { rows } } of responseData) {
|
||||
if (rows === undefined) {
|
||||
// Do not error if there is no data
|
||||
continue;
|
||||
}
|
||||
for (const row of rows) {
|
||||
const data: IDataObject = {};
|
||||
if (dimensions) {
|
||||
|
|
|
@ -112,6 +112,15 @@ export class GoogleAnalytics implements INodeType {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
returnData.sort((a, b) => {
|
||||
const aName= a.name.toLowerCase();
|
||||
const bName= b.name.toLowerCase();
|
||||
if (aName < bName) { return -1; }
|
||||
if (aName > bName) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
return returnData;
|
||||
},
|
||||
// Get all the views to display them to user so that he can
|
||||
|
@ -203,6 +212,14 @@ export class GoogleAnalytics implements INodeType {
|
|||
body.dimensions = dimensions;
|
||||
}
|
||||
}
|
||||
if (additionalFields.dimensionFiltersUi) {
|
||||
const dimensionFilters = (additionalFields.dimensionFiltersUi as IDataObject).filterValues as IDataObject[];
|
||||
if (dimensionFilters) {
|
||||
dimensionFilters.forEach(filter => filter.expressions = [filter.expressions]);
|
||||
body.dimensionFilterClauses = { filters: dimensionFilters };
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFields.includeEmptyRows) {
|
||||
Object.assign(body, { includeEmptyRows: additionalFields.includeEmptyRows });
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
export interface IData {
|
||||
viewId: string;
|
||||
dimensions?: IDimension[];
|
||||
dimensionFilterClauses?: {
|
||||
filters: IDimensionFilter[];
|
||||
};
|
||||
pageSize?: number;
|
||||
metrics?: IMetric[];
|
||||
}
|
||||
|
@ -10,6 +13,11 @@ export interface IDimension {
|
|||
histogramBuckets?: string[];
|
||||
}
|
||||
|
||||
export interface IDimensionFilter {
|
||||
dimensionName?: string;
|
||||
operator?: string;
|
||||
expressions?: string[];
|
||||
}
|
||||
export interface IMetric {
|
||||
expression?: string;
|
||||
alias?: string;
|
||||
|
|
|
@ -183,6 +183,85 @@ export const reportFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Dimension Filters',
|
||||
name: 'dimensionFiltersUi',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Dimension Filter',
|
||||
description: 'Dimension Filters in the request',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filterValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Dimension Name',
|
||||
name: 'dimensionName',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDimensions',
|
||||
},
|
||||
default: '',
|
||||
description: 'Name of the dimension to filter by.',
|
||||
},
|
||||
// https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet#Operator
|
||||
{
|
||||
displayName: 'Operator',
|
||||
name: 'operator',
|
||||
type: 'options',
|
||||
default: 'EXACT',
|
||||
description: 'Operator to use in combination with value.',
|
||||
options: [
|
||||
{
|
||||
name: 'Begins With',
|
||||
value: 'BEGINS_WITH',
|
||||
},
|
||||
{
|
||||
name: 'Ends With',
|
||||
value: 'ENDS_WITH',
|
||||
},
|
||||
{
|
||||
name: 'Exact',
|
||||
value: 'EXACT',
|
||||
},
|
||||
{
|
||||
name: 'Greater Than (number)',
|
||||
value: 'NUMERIC_GREATER_THAN',
|
||||
},
|
||||
{
|
||||
name: 'Partial',
|
||||
value: 'PARTIAL',
|
||||
},
|
||||
{
|
||||
name: 'Regular Expression',
|
||||
value: 'REGEXP',
|
||||
},
|
||||
{
|
||||
name: 'Equal (number)',
|
||||
value: 'NUMERIC_EQUAL',
|
||||
},
|
||||
{
|
||||
name: 'Less Than (number)',
|
||||
value: 'NUMERIC_LESS_THAN',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'expressions',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'ga:newUsers',
|
||||
description: `String or <a href="https://support.google.com/analytics/answer/1034324?hl=en" target="_blank">regular expression</a> to match against.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Hide Totals',
|
||||
name: 'hideTotals',
|
||||
|
|
|
@ -53,6 +53,10 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||
return await this.helpers.requestOAuth2.call(this, 'googleBooksOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
|
||||
error.statusCode = '401';
|
||||
}
|
||||
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
"icon": "⚙️",
|
||||
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "15 Google apps you can combine and automate to increase productivity",
|
||||
"icon": "💡",
|
||||
|
|
1212
packages/nodes-base/nodes/Google/Docs/DocumentDescription.ts
Normal file
1212
packages/nodes-base/nodes/Google/Docs/DocumentDescription.ts
Normal file
File diff suppressed because it is too large
Load diff
142
packages/nodes-base/nodes/Google/Docs/GenericFunctions.ts
Normal file
142
packages/nodes-base/nodes/Google/Docs/GenericFunctions.ts
Normal file
|
@ -0,0 +1,142 @@
|
|||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as moment from 'moment-timezone';
|
||||
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
|
||||
export async function googleApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs?: IDataObject,
|
||||
uri?: string,
|
||||
) {
|
||||
const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string;
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: uri || `https://docs.googleapis.com/v1${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
try {
|
||||
|
||||
if (authenticationMethod === 'serviceAccount') {
|
||||
const credentials = this.getCredentials('googleApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
|
||||
}
|
||||
|
||||
const { access_token } = await getAccessToken.call(this, credentials as IDataObject);
|
||||
|
||||
options.headers!.Authorization = `Bearer ${access_token}`;
|
||||
return await this.helpers.request!(options);
|
||||
} else {
|
||||
//@ts-ignore
|
||||
return await this.helpers.requestOAuth2.call(this, 'googleDocsOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: IDataObject = {}, qs?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
const query: IDataObject = { ...qs };
|
||||
query.maxResults = 100;
|
||||
query.pageSize = 100;
|
||||
|
||||
do {
|
||||
responseData = await googleApiRequest.call(this, method, endpoint, body, query, uri);
|
||||
query.pageToken = responseData['nextPageToken'];
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
} while (
|
||||
responseData['nextPageToken'] !== undefined &&
|
||||
responseData['nextPageToken'] !== ''
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
function getAccessToken(this: IExecuteFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise<IDataObject> {
|
||||
//https://developers.google.com/identity/protocols/oauth2/service-account#httprest
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/documents',
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
'https://www.googleapis.com/auth/drive.file',
|
||||
];
|
||||
|
||||
const now = moment().unix();
|
||||
|
||||
const signature = jwt.sign(
|
||||
{
|
||||
'iss': credentials.email as string,
|
||||
'sub': credentials.delegatedEmail || credentials.email as string,
|
||||
'scope': scopes.join(' '),
|
||||
'aud': `https://oauth2.googleapis.com/token`,
|
||||
'iat': now,
|
||||
'exp': now + 3600,
|
||||
},
|
||||
credentials.privateKey as string,
|
||||
{
|
||||
algorithm: 'RS256',
|
||||
header: {
|
||||
'kid': credentials.privateKey as string,
|
||||
'typ': 'JWT',
|
||||
'alg': 'RS256',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
method: 'POST',
|
||||
form: {
|
||||
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||
assertion: signature,
|
||||
},
|
||||
uri: 'https://oauth2.googleapis.com/token',
|
||||
json: true,
|
||||
};
|
||||
|
||||
return this.helpers.request!(options);
|
||||
}
|
||||
|
||||
export const hasKeys = (obj = {}) => Object.keys(obj).length > 0;
|
||||
export const extractID = (url: string) => {
|
||||
const regex = new RegExp('https://docs.google.com/document/d/([a-zA-Z0-9-_]+)/');
|
||||
const results = regex.exec(url);
|
||||
return results ? results[1] : undefined;
|
||||
};
|
||||
export const upperFirst = (str: string) => {
|
||||
return str[0].toUpperCase() + str.substr(1);
|
||||
};
|
20
packages/nodes-base/nodes/Google/Docs/GoogleDocs.node.json
Normal file
20
packages/nodes-base/nodes/Google/Docs/GoogleDocs.node.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"node": "n8n-nodes-base.googleDocs",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Miscellaneous"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/google"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.googleDocs/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
461
packages/nodes-base/nodes/Google/Docs/GoogleDocs.node.ts
Normal file
461
packages/nodes-base/nodes/Google/Docs/GoogleDocs.node.ts
Normal file
|
@ -0,0 +1,461 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
extractID,
|
||||
googleApiRequest,
|
||||
googleApiRequestAllItems,
|
||||
hasKeys,
|
||||
upperFirst,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
documentFields,
|
||||
documentOperations,
|
||||
} from './DocumentDescription';
|
||||
|
||||
import {
|
||||
IUpdateBody,
|
||||
IUpdateFields,
|
||||
} from './interfaces';
|
||||
|
||||
export class GoogleDocs implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Google Docs',
|
||||
name: 'googleDocs',
|
||||
icon: 'file:googleDocs.svg',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Google Docs API.',
|
||||
defaults: {
|
||||
name: 'Google Docs',
|
||||
color: '#1a73e8',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'googleApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
'serviceAccount',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'googleDocsOAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
'oAuth2',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Service Account',
|
||||
value: 'serviceAccount',
|
||||
},
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'oAuth2',
|
||||
},
|
||||
],
|
||||
default: 'serviceAccount',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Document',
|
||||
value: 'document',
|
||||
},
|
||||
],
|
||||
default: 'document',
|
||||
description: 'The resource to operate on.',
|
||||
},
|
||||
...documentOperations,
|
||||
...documentFields,
|
||||
],
|
||||
};
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the drives to display them to user so that he can
|
||||
// select them easily
|
||||
async getDrives(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [
|
||||
{
|
||||
name: 'My Drive',
|
||||
value: 'myDrive',
|
||||
},
|
||||
{
|
||||
name: 'Shared with me',
|
||||
value: 'sharedWithMe',
|
||||
},
|
||||
];
|
||||
let drives;
|
||||
try {
|
||||
drives = await googleApiRequestAllItems.call(this, 'drives', 'GET', '', {}, {}, 'https://www.googleapis.com/drive/v3/drives');
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error, { message: 'Error in loading Drives' });
|
||||
}
|
||||
|
||||
for (const drive of drives) {
|
||||
returnData.push({
|
||||
name: drive.name as string,
|
||||
value: drive.id as string,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
async getFolders(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [
|
||||
{
|
||||
name: '/',
|
||||
value: 'default',
|
||||
},
|
||||
];
|
||||
const driveId = this.getNodeParameter('driveId');
|
||||
|
||||
const qs = {
|
||||
q: `mimeType = \'application/vnd.google-apps.folder\' ${driveId === 'sharedWithMe' ? 'and sharedWithMe = true' : ' and \'root\' in parents'}`,
|
||||
...(driveId && driveId !== 'myDrive' && driveId !== 'sharedWithMe') ? { driveId } : {},
|
||||
};
|
||||
let folders;
|
||||
|
||||
try {
|
||||
folders = await googleApiRequestAllItems.call(this, 'files', 'GET', '', {}, qs, 'https://www.googleapis.com/drive/v3/files');
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error, { message: 'Error in loading Folders' });
|
||||
}
|
||||
|
||||
for (const folder of folders) {
|
||||
returnData.push({
|
||||
name: folder.name as string,
|
||||
value: folder.id as string,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length;
|
||||
|
||||
let responseData;
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
|
||||
try {
|
||||
|
||||
if (resource === 'document') {
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// https://developers.google.com/docs/api/reference/rest/v1/documents/create
|
||||
|
||||
const folderId = this.getNodeParameter('folderId', i) as string;
|
||||
|
||||
const body: IDataObject = {
|
||||
name: this.getNodeParameter('title', i) as string,
|
||||
mimeType: 'application/vnd.google-apps.document',
|
||||
...(folderId && folderId !== 'default') ? { parents: [folderId] } : {},
|
||||
};
|
||||
|
||||
responseData = await googleApiRequest.call(this, 'POST', '', body, {}, 'https://www.googleapis.com/drive/v3/files');
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// https://developers.google.com/docs/api/reference/rest/v1/documents/get
|
||||
|
||||
const documentURL = this.getNodeParameter('documentURL', i) as string;
|
||||
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||
let documentId = extractID(documentURL);
|
||||
|
||||
if (!documentId) {
|
||||
documentId = documentURL;
|
||||
}
|
||||
responseData = await googleApiRequest.call(this, 'GET', `/documents/${documentId}`);
|
||||
if (simple) {
|
||||
|
||||
const content = (responseData.body.content as IDataObject[])
|
||||
.reduce((arr: string[], contentItem) => {
|
||||
if (contentItem && contentItem.paragraph) {
|
||||
const texts = ((contentItem.paragraph as IDataObject).elements as IDataObject[])
|
||||
.map(element => {
|
||||
if (element && element.textRun) {
|
||||
return (element.textRun as IDataObject).content as string;
|
||||
}
|
||||
}) as string[];
|
||||
arr = [...arr, ...texts];
|
||||
}
|
||||
return arr;
|
||||
}, [])
|
||||
.join('');
|
||||
|
||||
responseData = {
|
||||
documentId,
|
||||
content,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// https://developers.google.com/docs/api/reference/rest/v1/documents/batchUpdate
|
||||
|
||||
const documentURL = this.getNodeParameter('documentURL', i) as string;
|
||||
let documentId = extractID(documentURL);
|
||||
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||
const actionsUi = this.getNodeParameter('actionsUi', i) as {
|
||||
actionFields: IDataObject[]
|
||||
};
|
||||
const { writeControlObject } = this.getNodeParameter('updateFields', i) as IUpdateFields;
|
||||
|
||||
if (!documentId) {
|
||||
documentId = documentURL;
|
||||
}
|
||||
|
||||
const body = {
|
||||
requests: [],
|
||||
} as IUpdateBody;
|
||||
|
||||
if (hasKeys(writeControlObject)) {
|
||||
const { control, value } = writeControlObject;
|
||||
body.writeControl = {
|
||||
[control]: value,
|
||||
};
|
||||
}
|
||||
|
||||
if (actionsUi) {
|
||||
|
||||
let requestBody: IDataObject;
|
||||
actionsUi.actionFields.forEach(actionField => {
|
||||
const { action, object } = actionField;
|
||||
if (object === 'positionedObject') {
|
||||
if (action === 'delete') {
|
||||
requestBody = {
|
||||
objectId: actionField.objectId,
|
||||
};
|
||||
}
|
||||
|
||||
} else if (object === 'pageBreak') {
|
||||
|
||||
if (action === 'insert') {
|
||||
const { insertSegment, segmentId, locationChoice, index } = actionField;
|
||||
requestBody = {
|
||||
[locationChoice as string]: {
|
||||
segmentId: (insertSegment !== 'body') ? segmentId : '',
|
||||
...(locationChoice === 'location') ? { index } : {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} else if (object === 'table') {
|
||||
|
||||
if (action === 'insert') {
|
||||
const { rows, columns, insertSegment, locationChoice, segmentId, index } = actionField;
|
||||
requestBody = {
|
||||
rows,
|
||||
columns,
|
||||
[locationChoice as string]: {
|
||||
segmentId: (insertSegment !== 'body') ? segmentId : '',
|
||||
...(locationChoice === 'location') ? { index } : {},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} else if (object === 'footer') {
|
||||
|
||||
if (action === 'create') {
|
||||
const { insertSegment, locationChoice, segmentId, index } = actionField;
|
||||
requestBody = {
|
||||
type: 'DEFAULT',
|
||||
sectionBreakLocation: {
|
||||
segmentId: (insertSegment !== 'body') ? segmentId : '',
|
||||
...(locationChoice === 'location') ? { index } : {},
|
||||
},
|
||||
};
|
||||
} else if (action === 'delete') {
|
||||
requestBody = {
|
||||
footerId: actionField.footerId,
|
||||
};
|
||||
}
|
||||
|
||||
} else if (object === 'header') {
|
||||
|
||||
if (action === 'create') {
|
||||
const { insertSegment, locationChoice, segmentId, index } = actionField;
|
||||
requestBody = {
|
||||
type: 'DEFAULT',
|
||||
sectionBreakLocation: {
|
||||
segmentId: (insertSegment !== 'body') ? segmentId : '',
|
||||
...(locationChoice === 'location') ? { index } : {},
|
||||
},
|
||||
};
|
||||
} else if (action === 'delete') {
|
||||
requestBody = {
|
||||
headerId: actionField.headerId,
|
||||
};
|
||||
}
|
||||
|
||||
} else if (object === 'tableColumn') {
|
||||
|
||||
if (action === 'insert') {
|
||||
const { insertPosition, rowIndex, columnIndex, insertSegment, segmentId, index } = actionField;
|
||||
requestBody = {
|
||||
insertRight: insertPosition,
|
||||
tableCellLocation: {
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
tableStartLocation: { segmentId: (insertSegment !== 'body') ? segmentId : '', index, },
|
||||
},
|
||||
};
|
||||
} else if (action === 'delete') {
|
||||
const { rowIndex, columnIndex, insertSegment, segmentId, index } = actionField;
|
||||
requestBody = {
|
||||
tableCellLocation: {
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
tableStartLocation: { segmentId: (insertSegment !== 'body') ? segmentId : '', index, },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} else if (object === 'tableRow') {
|
||||
|
||||
if (action === 'insert') {
|
||||
const { insertPosition, rowIndex, columnIndex, insertSegment, segmentId, index } = actionField;
|
||||
requestBody = {
|
||||
insertBelow: insertPosition,
|
||||
tableCellLocation: {
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
tableStartLocation: { segmentId: (insertSegment !== 'body') ? segmentId : '', index, },
|
||||
},
|
||||
};
|
||||
} else if (action === 'delete') {
|
||||
const { rowIndex, columnIndex, insertSegment, segmentId, index } = actionField;
|
||||
requestBody = {
|
||||
tableCellLocation: {
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
tableStartLocation: { segmentId: (insertSegment !== 'body') ? segmentId : '', index, },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
} else if (object === 'text') {
|
||||
|
||||
if (action === 'insert') {
|
||||
const { text, locationChoice, insertSegment, segmentId, index } = actionField;
|
||||
requestBody = {
|
||||
text,
|
||||
[locationChoice as string]: {
|
||||
segmentId: (insertSegment !== 'body') ? segmentId : '',
|
||||
...(locationChoice === 'location') ? { index } : {},
|
||||
},
|
||||
};
|
||||
} else if (action === 'replaceAll') {
|
||||
const { text, replaceText, matchCase } = actionField;
|
||||
requestBody = {
|
||||
replaceText,
|
||||
containsText: { text, matchCase },
|
||||
};
|
||||
}
|
||||
|
||||
} else if (object === 'paragraphBullets') {
|
||||
if (action === 'create') {
|
||||
const { bulletPreset, startIndex, insertSegment, segmentId, endIndex } = actionField;
|
||||
requestBody = {
|
||||
bulletPreset,
|
||||
range: { segmentId: (insertSegment !== 'body') ? segmentId : '', startIndex, endIndex },
|
||||
};
|
||||
} else if (action === 'delete') {
|
||||
const { startIndex, insertSegment, segmentId, endIndex } = actionField;
|
||||
requestBody = {
|
||||
range: { segmentId: (insertSegment !== 'body') ? segmentId : '', startIndex, endIndex },
|
||||
};
|
||||
}
|
||||
} else if (object === 'namedRange') {
|
||||
if (action === 'create') {
|
||||
const { name, insertSegment, segmentId, startIndex, endIndex } = actionField;
|
||||
requestBody = {
|
||||
name,
|
||||
range: { segmentId: (insertSegment !== 'body') ? segmentId : '', startIndex, endIndex },
|
||||
};
|
||||
} else if (action === 'delete') {
|
||||
const { namedRangeReference, value } = actionField;
|
||||
requestBody = {
|
||||
[namedRangeReference as string]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
body.requests.push({
|
||||
[`${action}${upperFirst(object as string)}`]: requestBody,
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
responseData = await googleApiRequest.call(this, 'POST', `/documents/${documentId}:batchUpdate`, body);
|
||||
|
||||
if (simple === true) {
|
||||
if (Object.keys(responseData.replies[0]).length !== 0) {
|
||||
const key = Object.keys(responseData.replies[0])[0];
|
||||
responseData = responseData.replies[0][key];
|
||||
} else {
|
||||
responseData = {};
|
||||
}
|
||||
}
|
||||
responseData.documentId = documentId;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.message });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
Array.isArray(responseData)
|
||||
? returnData.push(...responseData)
|
||||
: returnData.push(responseData);
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
1
packages/nodes-base/nodes/Google/Docs/googleDocs.svg
Normal file
1
packages/nodes-base/nodes/Google/Docs/googleDocs.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-18 0 90 80" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#A" x=".5" y=".5"/><symbol id="A" overflow="visible"><g stroke="none"><path fill="#548df6" d="M36 0l22 22v53a4.99 4.99 0 0 1-5 5H5a4.99 4.99 0 0 1-5-5V5a4.99 4.99 0 0 1 5-5z"/><path d="M14 40h30v3H14zm0 7h30v3H14zm0 8h30v3H14zm0 7h21v3H14z"/><path fill="#abd0fb" d="M36 0l22 22H41c-2.77 0-5-2.48-5-5.25z"/><path fill="#3e5bb9" d="M40.75 22L58 29.125V22z"/></g></symbol></svg>
|
After Width: | Height: | Size: 591 B |
13
packages/nodes-base/nodes/Google/Docs/interfaces.d.ts
vendored
Normal file
13
packages/nodes-base/nodes/Google/Docs/interfaces.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
export interface IUpdateBody extends IDataObject {
|
||||
requests: IDataObject[];
|
||||
writeControl?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface IUpdateFields {
|
||||
writeControlObject: {
|
||||
control: string,
|
||||
value: string,
|
||||
};
|
||||
}
|
|
@ -51,6 +51,10 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||
return await this.helpers.requestOAuth2.call(this, 'googleDriveOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
|
||||
error.statusCode = '401';
|
||||
}
|
||||
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,5 +23,8 @@
|
|||
"url": "https://n8n.io/blog/automate-google-apps-for-productivity/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"alias": [
|
||||
"Workspaces"
|
||||
]
|
||||
}
|
|
@ -74,6 +74,10 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||
}
|
||||
|
||||
} catch (error) {
|
||||
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
|
||||
error.statusCode = '401';
|
||||
}
|
||||
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
|
|||
return await this.helpers.requestOAuth2.call(this, 'googleSheetsOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
|
||||
error.statusCode = '401';
|
||||
}
|
||||
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
@ -138,4 +142,4 @@ export function hexToRgb(hex: string) {
|
|||
green: parseInt(result[2], 16),
|
||||
blue: parseInt(result[3], 16),
|
||||
} : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,5 +79,10 @@
|
|||
"url": "https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"alias": [
|
||||
"CSV",
|
||||
"Spreadsheet",
|
||||
"GS"
|
||||
]
|
||||
}
|
|
@ -54,6 +54,10 @@ export async function googleApiRequest(
|
|||
return await this.helpers.requestOAuth2!.call(this, 'googleSlidesOAuth2Api', options);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
|
||||
error.statusCode = '401';
|
||||
}
|
||||
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "What are APIs and how to use them with no code",
|
||||
"icon": " 🪢",
|
||||
"url": "https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/"
|
||||
},
|
||||
{
|
||||
"label": "How to automatically give kudos to contributors with GitHub, Slack, and n8n",
|
||||
"icon": "👏",
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
"icon": "🧬",
|
||||
"url": "https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "How to use the HTTP Request Node - The Swiss Army Knife for Workflow Automation",
|
||||
"icon": "🧰",
|
||||
|
|
|
@ -43,6 +43,16 @@
|
|||
"icon": "🛳",
|
||||
"url": "https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/"
|
||||
},
|
||||
{
|
||||
"label": "What are APIs and how to use them with no code",
|
||||
"icon": " 🪢",
|
||||
"url": "https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "Celebrating World Poetry Day with a daily poem in Telegram",
|
||||
"icon": "📜",
|
||||
|
@ -83,6 +93,11 @@
|
|||
"icon": "📈",
|
||||
"url": "https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/"
|
||||
},
|
||||
{
|
||||
"label": "How Common Knowledge use workflow automation for activism",
|
||||
"icon": "✨",
|
||||
"url": "https://n8n.io/blog/automations-for-activists/"
|
||||
},
|
||||
{
|
||||
"label": "Creating scheduled text affirmations with n8n",
|
||||
"icon": "🤟",
|
||||
|
@ -95,6 +110,12 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"API",
|
||||
"Request",
|
||||
"URL",
|
||||
"Build"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Helpers"
|
||||
|
|
|
@ -347,6 +347,20 @@ export class HttpRequest implements INodeType {
|
|||
placeholder: 'http://myproxy:3128',
|
||||
description: 'HTTP proxy to use.',
|
||||
},
|
||||
{
|
||||
displayName: 'Split Into Items',
|
||||
name: 'splitIntoItems',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Outputs each element of an array as own item.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/responseFormat': [
|
||||
'json',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Timeout',
|
||||
name: 'timeout',
|
||||
|
@ -1005,7 +1019,11 @@ export class HttpRequest implements INodeType {
|
|||
}
|
||||
}
|
||||
|
||||
returnItems.push({ json: response });
|
||||
if (options.splitIntoItems === true && Array.isArray(response)) {
|
||||
response.forEach(item => returnItems.push({ json: item }));
|
||||
} else {
|
||||
returnItems.push({ json: response });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.humanticAi/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -28,11 +28,21 @@
|
|||
"icon": "🧬",
|
||||
"url": "https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/"
|
||||
},
|
||||
{
|
||||
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
|
||||
"icon": "🔗",
|
||||
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
|
||||
},
|
||||
{
|
||||
"label": "Automate your data processing pipeline in 9 steps",
|
||||
"icon": "⚙️",
|
||||
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "15 Google apps you can combine and automate to increase productivity",
|
||||
"icon": "💡",
|
||||
|
@ -60,6 +70,14 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Router",
|
||||
"Filter",
|
||||
"Condition",
|
||||
"Logic",
|
||||
"Boolean",
|
||||
"Branch"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Flow"
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Time",
|
||||
"Scheduler",
|
||||
"Polling"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Flow"
|
||||
|
|
|
@ -17,5 +17,8 @@
|
|||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.keap/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"alias": [
|
||||
"Infusionsoft"
|
||||
]
|
||||
}
|
|
@ -17,5 +17,8 @@
|
|||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.keapTrigger/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"alias": [
|
||||
"Infusionsoft"
|
||||
]
|
||||
}
|
|
@ -32,6 +32,11 @@
|
|||
"icon": "⏲",
|
||||
"url": "https://n8n.io/blog/creating-triggers-for-n8n-workflows-using-polling/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "15 Google apps you can combine and automate to increase productivity",
|
||||
"icon": "💡",
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Join",
|
||||
"Concatenate"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Flow"
|
||||
|
|
|
@ -95,7 +95,16 @@ export function executeQueryQueue(
|
|||
*/
|
||||
export function extractValues(item: IDataObject): string {
|
||||
return `(${Object.values(item as any) // tslint:disable-line:no-any
|
||||
.map(val => (typeof val === 'string' ? `'${val}'` : val)) // maybe other types such as dates have to be handled as well
|
||||
.map(val => {
|
||||
//the column cannot be found in the input
|
||||
//so, set it to null in the sql query
|
||||
if (val === null) {
|
||||
return 'NULL';
|
||||
} else if (typeof val === 'string') {
|
||||
return `'${val.replace(/'/g, '\'\'')}'`;
|
||||
}
|
||||
return val;
|
||||
}) // maybe other types such as dates have to be handled as well
|
||||
.join(',')})`;
|
||||
}
|
||||
|
||||
|
|
|
@ -278,7 +278,6 @@ export class MicrosoftSql implements INodeType {
|
|||
const values = insertValues
|
||||
.map((item: IDataObject) => extractValues(item))
|
||||
.join(',');
|
||||
|
||||
return pool
|
||||
.request()
|
||||
.query(
|
||||
|
|
|
@ -15,11 +15,6 @@
|
|||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.microsoftToDo/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Todo"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
162
packages/nodes-base/nodes/N8nTrainingCustomerDatastore.node.ts
Normal file
162
packages/nodes-base/nodes/N8nTrainingCustomerDatastore.node.ts
Normal file
|
@ -0,0 +1,162 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: '23423532',
|
||||
name: 'Jay Gatsby',
|
||||
email: 'gatsby@west-egg.com',
|
||||
notes: 'Keeps asking about a green light??',
|
||||
country: 'US',
|
||||
created: '1925-04-10',
|
||||
},
|
||||
{
|
||||
id: '23423533',
|
||||
name: 'José Arcadio Buendía',
|
||||
email: 'jab@macondo.co',
|
||||
notes: 'Lots of people named after him. Very confusing',
|
||||
country: 'CO',
|
||||
created: '1967-05-05',
|
||||
},
|
||||
{
|
||||
id: '23423534',
|
||||
name: 'Max Sendak',
|
||||
email: 'info@in-and-out-of-weeks.org',
|
||||
notes: 'Keeps rolling his terrible eyes',
|
||||
country: 'US',
|
||||
created: '1963-04-09',
|
||||
},
|
||||
{
|
||||
id: '23423535',
|
||||
name: 'Zaphod Beeblebrox',
|
||||
email: 'captain@heartofgold.com',
|
||||
notes: 'Felt like I was talking to more than one person',
|
||||
country: null,
|
||||
created: '1979-10-12',
|
||||
},
|
||||
{
|
||||
id: '23423536',
|
||||
name: 'Edmund Pevensie',
|
||||
email: 'edmund@narnia.gov',
|
||||
notes: 'Passionate sailor',
|
||||
country: 'UK',
|
||||
created: '1950-10-16',
|
||||
},
|
||||
];
|
||||
|
||||
export class N8nTrainingCustomerDatastore implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Customer Datastore (n8n training)',
|
||||
name: 'n8nTrainingCustomerDatastore',
|
||||
icon: 'file:n8nTrainingCustomerDatastore.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"]}}',
|
||||
description: 'Dummy node used for n8n training',
|
||||
defaults: {
|
||||
name: 'Customer Datastore',
|
||||
color: '#ff6d5a',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Get One Person',
|
||||
value: 'getOnePerson',
|
||||
description: 'Get one person',
|
||||
},
|
||||
{
|
||||
name: 'Get All People',
|
||||
value: 'getAllPeople',
|
||||
description: 'Get all people',
|
||||
},
|
||||
],
|
||||
default: 'getOnePerson',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAllPeople',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAllPeople',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 10,
|
||||
},
|
||||
default: 5,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = (items.length as unknown) as number;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
let responseData;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
|
||||
if (operation === 'getOnePerson') {
|
||||
|
||||
responseData = data[0];
|
||||
}
|
||||
|
||||
if (operation === 'getAllPeople') {
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (returnAll === true) {
|
||||
responseData = data;
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = data.splice(0, limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else if (responseData !== undefined) {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class N8nTrainingCustomerMessenger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Customer Messenger (n8n training)',
|
||||
name: 'n8nTrainingCustomeMessenger',
|
||||
icon: 'file:n8nTrainingCustomerMessenger.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
description: 'Dummy node used for n8n training',
|
||||
defaults: {
|
||||
name: 'Customer Messenger',
|
||||
color: '#ff6d5a',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Customer ID',
|
||||
name: 'customerId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Message',
|
||||
name: 'message',
|
||||
type: 'string',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
rows: 4,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = (items.length as unknown) as number;
|
||||
let responseData;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
|
||||
const customerId = this.getNodeParameter('customerId', i) as string;
|
||||
|
||||
const message = this.getNodeParameter('message', i) as string;
|
||||
|
||||
responseData = { output: `Sent message to customer ${customerId}: ${message}` };
|
||||
|
||||
returnData.push(responseData);
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
|
@ -27,6 +27,11 @@
|
|||
"icon": "⚙️",
|
||||
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "5 workflow automations for Mattermost that we love at n8n",
|
||||
"icon": "🤖",
|
||||
|
@ -44,6 +49,9 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"nothing"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Helpers"
|
||||
|
|
|
@ -52,6 +52,8 @@ export async function notionApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
|
||||
export async function notionApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
@ -59,8 +61,11 @@ export async function notionApiRequestAllItems(this: IExecuteFunctions | ILoadOp
|
|||
do {
|
||||
responseData = await notionApiRequest.call(this, method, endpoint, body, query);
|
||||
const { next_cursor } = responseData;
|
||||
query['start_cursor'] = next_cursor;
|
||||
body['start_cursor'] = next_cursor;
|
||||
if (resource === 'block' || resource === 'user') {
|
||||
query['start_cursor'] = next_cursor;
|
||||
} else {
|
||||
body['start_cursor'] = next_cursor;
|
||||
}
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
if (query.limit && query.limit <= returnData.length) {
|
||||
return returnData;
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.notion/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -47,7 +47,6 @@ import {
|
|||
databasePageFields,
|
||||
databasePageOperations,
|
||||
} from './DatabasePageDescription';
|
||||
import { getServers } from 'dns';
|
||||
|
||||
export class Notion implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -323,7 +322,6 @@ export class Notion implements INodeType {
|
|||
if (operation === 'getAll') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const body: IDataObject = {
|
||||
page_size: 100,
|
||||
filter: { property: 'object', value: 'database' },
|
||||
};
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.notionTrigger/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -29,6 +29,10 @@ import {
|
|||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
QuickBooksOAuth2Credentials,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* Make an authenticated API request to QuickBooks.
|
||||
*/
|
||||
|
@ -53,7 +57,7 @@ export async function quickBooksApiRequest(
|
|||
const productionUrl = 'https://quickbooks.api.intuit.com';
|
||||
const sandboxUrl = 'https://sandbox-quickbooks.api.intuit.com';
|
||||
|
||||
const credentials = this.getCredentials('quickBooksOAuth2Api') as IDataObject;
|
||||
const credentials = this.getCredentials('quickBooksOAuth2Api') as QuickBooksOAuth2Credentials;
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
|
|
|
@ -26,6 +26,8 @@ import {
|
|||
itemOperations,
|
||||
paymentFields,
|
||||
paymentOperations,
|
||||
purchaseFields,
|
||||
purchaseOperations,
|
||||
vendorFields,
|
||||
vendorOperations,
|
||||
} from './descriptions';
|
||||
|
@ -49,17 +51,21 @@ import {
|
|||
isEmpty,
|
||||
} from 'lodash';
|
||||
|
||||
import {
|
||||
QuickBooksOAuth2Credentials,
|
||||
} from './types';
|
||||
|
||||
export class QuickBooks implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'QuickBooks',
|
||||
displayName: 'QuickBooks Online',
|
||||
name: 'quickbooks',
|
||||
icon: 'file:quickbooks.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume the QuickBooks API',
|
||||
description: 'Consume the QuickBooks Online API',
|
||||
defaults: {
|
||||
name: 'QuickBooks',
|
||||
name: 'QuickBooks Online',
|
||||
color: '#2CA01C',
|
||||
},
|
||||
inputs: ['main'],
|
||||
|
@ -104,6 +110,10 @@ export class QuickBooks implements INodeType {
|
|||
name: 'Payment',
|
||||
value: 'payment',
|
||||
},
|
||||
{
|
||||
name: 'Purchase',
|
||||
value: 'purchase',
|
||||
},
|
||||
{
|
||||
name: 'Vendor',
|
||||
value: 'vendor',
|
||||
|
@ -126,6 +136,8 @@ export class QuickBooks implements INodeType {
|
|||
...itemFields,
|
||||
...paymentOperations,
|
||||
...paymentFields,
|
||||
...purchaseOperations,
|
||||
...purchaseFields,
|
||||
...vendorOperations,
|
||||
...vendorFields,
|
||||
],
|
||||
|
@ -145,6 +157,10 @@ export class QuickBooks implements INodeType {
|
|||
return await loadResource.call(this, 'item');
|
||||
},
|
||||
|
||||
async getPurchases(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'purchase');
|
||||
},
|
||||
|
||||
async getVendors(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'vendor');
|
||||
},
|
||||
|
@ -160,8 +176,8 @@ export class QuickBooks implements INodeType {
|
|||
let responseData;
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
const { oauthTokenData } = this.getCredentials('quickBooksOAuth2Api') as IDataObject;
|
||||
// @ts-ignore
|
||||
const { oauthTokenData } = this.getCredentials('quickBooksOAuth2Api') as QuickBooksOAuth2Credentials;
|
||||
|
||||
const companyId = oauthTokenData.callbackQueryString.realmId;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
@ -169,10 +185,10 @@ export class QuickBooks implements INodeType {
|
|||
if (resource === 'bill') {
|
||||
|
||||
// *********************************************************************
|
||||
// bill
|
||||
// bill
|
||||
// *********************************************************************
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/estimate
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/bill
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
|
@ -288,7 +304,7 @@ export class QuickBooks implements INodeType {
|
|||
} else if (resource === 'customer') {
|
||||
|
||||
// *********************************************************************
|
||||
// customer
|
||||
// customer
|
||||
// *********************************************************************
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/customer
|
||||
|
@ -360,7 +376,7 @@ export class QuickBooks implements INodeType {
|
|||
} else if (resource === 'employee') {
|
||||
|
||||
// *********************************************************************
|
||||
// employee
|
||||
// employee
|
||||
// *********************************************************************
|
||||
|
||||
if (operation === 'create') {
|
||||
|
@ -431,7 +447,7 @@ export class QuickBooks implements INodeType {
|
|||
} else if (resource === 'estimate') {
|
||||
|
||||
// *********************************************************************
|
||||
// estimate
|
||||
// estimate
|
||||
// *********************************************************************
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/estimate
|
||||
|
@ -574,7 +590,7 @@ export class QuickBooks implements INodeType {
|
|||
} else if (resource === 'invoice') {
|
||||
|
||||
// *********************************************************************
|
||||
// invoice
|
||||
// invoice
|
||||
// *********************************************************************
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/invoice
|
||||
|
@ -733,7 +749,7 @@ export class QuickBooks implements INodeType {
|
|||
} else if (resource === 'item') {
|
||||
|
||||
// *********************************************************************
|
||||
// item
|
||||
// item
|
||||
// *********************************************************************
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/item
|
||||
|
@ -763,7 +779,7 @@ export class QuickBooks implements INodeType {
|
|||
} else if (resource === 'payment') {
|
||||
|
||||
// *********************************************************************
|
||||
// payment
|
||||
// payment
|
||||
// *********************************************************************
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/payment
|
||||
|
@ -902,10 +918,40 @@ export class QuickBooks implements INodeType {
|
|||
|
||||
}
|
||||
|
||||
} else if (resource === 'purchase') {
|
||||
|
||||
// *********************************************************************
|
||||
// purchase
|
||||
// *********************************************************************
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/purchase
|
||||
|
||||
if (operation === 'get') {
|
||||
|
||||
// ----------------------------------
|
||||
// purchase: get
|
||||
// ----------------------------------
|
||||
|
||||
const purchaseId = this.getNodeParameter('purchaseId', i);
|
||||
const endpoint = `/v3/company/${companyId}/${resource}/${purchaseId}`;
|
||||
responseData = await quickBooksApiRequest.call(this, 'GET', endpoint, {}, {});
|
||||
responseData = responseData[capitalCase(resource)];
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------
|
||||
// purchase: getAll
|
||||
// ----------------------------------
|
||||
|
||||
const endpoint = `/v3/company/${companyId}/query`;
|
||||
responseData = await handleListing.call(this, i, endpoint, resource);
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'vendor') {
|
||||
|
||||
// *********************************************************************
|
||||
// vendor
|
||||
// vendor
|
||||
// *********************************************************************
|
||||
|
||||
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/vendor
|
||||
|
@ -975,6 +1021,7 @@ export class QuickBooks implements INodeType {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
Array.isArray(responseData)
|
||||
? returnData.push(...responseData)
|
||||
: returnData.push(responseData);
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const purchaseOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
default: 'get',
|
||||
description: 'Operation to perform',
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'purchase',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const purchaseFields = [
|
||||
// ----------------------------------
|
||||
// purchase: get
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Purchase ID',
|
||||
name: 'purchaseId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The ID of the purchase to retrieve.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'purchase',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// purchase: getAll
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'purchase',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 5,
|
||||
description: 'The number of results to return.',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 1000,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'purchase',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'WHERE Metadata.LastUpdatedTime > \'2021-01-01\'',
|
||||
description: 'The condition for selecting purchases. See the <a href="https://developer.intuit.com/app/developer/qbo/docs/develop/explore-the-quickbooks-online-api/data-queries" target="_blank">guide</a> for supported syntax.',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'purchase',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -6,3 +6,4 @@ export * from './Invoice/InvoiceDescription';
|
|||
export * from './Item/ItemDescription';
|
||||
export * from './Payment/PaymentDescription';
|
||||
export * from './Vendor/VendorDescription';
|
||||
export * from './Purchase/PurchaseDescription';
|
||||
|
|
8
packages/nodes-base/nodes/QuickBooks/types.d.ts
vendored
Normal file
8
packages/nodes-base/nodes/QuickBooks/types.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
export type QuickBooksOAuth2Credentials = {
|
||||
environment: 'production' | 'sandbox';
|
||||
oauthTokenData: {
|
||||
callbackQueryString: {
|
||||
realmId: string;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -24,6 +24,11 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Text",
|
||||
"Open",
|
||||
"Import"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Files"
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Text",
|
||||
"Open",
|
||||
"Import"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Files"
|
||||
|
|
|
@ -37,11 +37,21 @@
|
|||
"icon": "🧾",
|
||||
"url": "https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
|
||||
"icon": "🔗",
|
||||
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
|
||||
},
|
||||
{
|
||||
"label": "Automate your data processing pipeline in 9 steps",
|
||||
"icon": "⚙️",
|
||||
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "15 Google apps you can combine and automate to increase productivity",
|
||||
"icon": "💡",
|
||||
|
@ -84,6 +94,12 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"JSON",
|
||||
"Filter",
|
||||
"Transform",
|
||||
"Map"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Data Transformation"
|
||||
|
|
|
@ -22,6 +22,11 @@
|
|||
"icon": "⚙️",
|
||||
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "Build your own virtual assistant with n8n: A step by step guide",
|
||||
"icon": "👦",
|
||||
|
@ -31,6 +36,11 @@
|
|||
"label": "How to automatically give kudos to contributors with GitHub, Slack, and n8n",
|
||||
"icon": "👏",
|
||||
"url": "https://n8n.io/blog/how-to-automatically-give-kudos-to-contributors-with-github-slack-and-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "How Common Knowledge use workflow automation for activism",
|
||||
"icon": "✨",
|
||||
"url": "https://n8n.io/blog/automations-for-activists/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Loop"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Flow"
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"CSV",
|
||||
"Spreadsheet"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Files"
|
||||
|
|
|
@ -11,6 +11,13 @@
|
|||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.sseTrigger/"
|
||||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "What are APIs and how to use them with no code",
|
||||
"icon": " 🪢",
|
||||
"url": "https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subcategories": {
|
||||
|
|
|
@ -17,5 +17,8 @@
|
|||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.surveyMonkeyTrigger/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"alias": [
|
||||
"Form"
|
||||
]
|
||||
}
|
|
@ -24,6 +24,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Router",
|
||||
"If",
|
||||
"Path",
|
||||
"Filter",
|
||||
"Condition",
|
||||
"Logic",
|
||||
"Branch"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Flow"
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
}
|
||||
],
|
||||
"generic": [
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "Hey founders! Your business doesn't need you to operate",
|
||||
"icon": " 🖥️",
|
||||
|
|
|
@ -27,6 +27,11 @@
|
|||
"icon": "🎫",
|
||||
"url": "https://n8n.io/blog/supercharging-your-conference-registration-process-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "15 Google apps you can combine and automate to increase productivity",
|
||||
"icon": "💡",
|
||||
|
@ -46,7 +51,15 @@
|
|||
"label": "Automating Conference Organization Processes with n8n",
|
||||
"icon": "🙋♀️",
|
||||
"url": "https://n8n.io/blog/automating-conference-organization-processes-with-n8n/"
|
||||
},
|
||||
{
|
||||
"label": "How Common Knowledge use workflow automation for activism",
|
||||
"icon": "✨",
|
||||
"url": "https://n8n.io/blog/automations-for-activists/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"alias": [
|
||||
"Form"
|
||||
]
|
||||
}
|
|
@ -3,12 +3,12 @@
|
|||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Monitoring"
|
||||
"Development"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/uptimeRobot"
|
||||
"url": "https://docs.n8n.io/credentials/uptimeRobot/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
|
|
|
@ -23,6 +23,21 @@
|
|||
"icon": "🛳",
|
||||
"url": "https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/"
|
||||
},
|
||||
{
|
||||
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
|
||||
"icon": "🔗",
|
||||
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
|
||||
},
|
||||
{
|
||||
"label": "What are APIs and how to use them with no code",
|
||||
"icon": " 🪢",
|
||||
"url": "https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/"
|
||||
},
|
||||
{
|
||||
"label": "5 tasks you can automate with the new Notion API ",
|
||||
"icon": "⚡️",
|
||||
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
|
||||
},
|
||||
{
|
||||
"label": "How a digital strategist uses n8n for online marketing",
|
||||
"icon": "💻",
|
||||
|
@ -70,6 +85,12 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"HTTP",
|
||||
"API",
|
||||
"Build",
|
||||
"WH"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Flow"
|
||||
|
|
|
@ -12,6 +12,11 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": [
|
||||
"Text",
|
||||
"Save",
|
||||
"Export"
|
||||
],
|
||||
"subcategories": {
|
||||
"Core Nodes": [
|
||||
"Files"
|
||||
|
|
|
@ -16,5 +16,8 @@
|
|||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.wufooTrigger/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"alias": [
|
||||
"Form"
|
||||
]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue