Merge branch 'n8n-io:master' into Add-schema-registry-into-kafka
This commit is contained in:
@@ -142,6 +142,9 @@ export class Start extends Command {
|
||||
LoggerProxy.init(logger);
|
||||
logger.info('Initializing n8n process');
|
||||
|
||||
// todo remove a few versions after release
|
||||
logger.info('\nn8n now checks for new versions and security updates. You can turn this off using the environment variable N8N_VERSION_NOTIFICATIONS_ENABLED to "false"\nFor more information, please refer to https://docs.n8n.io/getting-started/installation/advanced/configuration.html\n');
|
||||
|
||||
// Start directly with the init of the database to improve startup time
|
||||
const startDbInitPromise = Db.init().catch((error: Error) => {
|
||||
logger.error(`There was an error initializing DB: "${error.message}"`);
|
||||
|
||||
@@ -618,6 +618,27 @@ const config = convict({
|
||||
},
|
||||
},
|
||||
|
||||
versionNotifications: {
|
||||
enabled: {
|
||||
doc: 'Whether feature is enabled to request notifications about new versions and security updates.',
|
||||
format: Boolean,
|
||||
default: true,
|
||||
env: 'N8N_VERSION_NOTIFICATIONS_ENABLED',
|
||||
},
|
||||
endpoint: {
|
||||
doc: 'Endpoint to retrieve version information from.',
|
||||
format: String,
|
||||
default: 'https://api.n8n.io/versions/',
|
||||
env: 'N8N_VERSION_NOTIFICATIONS_ENDPOINT',
|
||||
},
|
||||
infoUrl: {
|
||||
doc: `Url in New Versions Panel with more information on updating one's instance.`,
|
||||
format: String,
|
||||
default: 'https://docs.n8n.io/getting-started/installation/updating.html',
|
||||
env: 'N8N_VERSION_NOTIFICATIONS_INFO_URL',
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
// Overwrite default configuration with settings which got defined in
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n",
|
||||
"version": "0.130.0",
|
||||
"version": "0.132.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -108,10 +108,10 @@
|
||||
"localtunnel": "^2.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mysql2": "~2.2.0",
|
||||
"n8n-core": "~0.77.0",
|
||||
"n8n-editor-ui": "~0.99.0",
|
||||
"n8n-nodes-base": "~0.127.0",
|
||||
"n8n-workflow": "~0.63.0",
|
||||
"n8n-core": "~0.78.0",
|
||||
"n8n-editor-ui": "~0.100.0",
|
||||
"n8n-nodes-base": "~0.129.0",
|
||||
"n8n-workflow": "~0.64.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^8.3.0",
|
||||
|
||||
@@ -39,6 +39,8 @@ import {
|
||||
LoggerProxy as Logger,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const WEBHOOK_PROD_UNREGISTERED_HINT = `The workflow must be active for a production URL to run successfully. You can activate the workflow using the toggle in the top-right of the editor. Note that unlike test URL calls, production URL calls aren't shown on the canvas (only in the executions list)`;
|
||||
|
||||
export class ActiveWorkflowRunner {
|
||||
private activeWorkflows: ActiveWorkflows | null = null;
|
||||
|
||||
@@ -148,7 +150,7 @@ export class ActiveWorkflowRunner {
|
||||
const dynamicWebhooks = await Db.collections.Webhook?.find({ webhookId, method: httpMethod, pathLength: pathElements.length });
|
||||
if (dynamicWebhooks === undefined || dynamicWebhooks.length === 0) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404);
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404, WEBHOOK_PROD_UNREGISTERED_HINT);
|
||||
}
|
||||
|
||||
let maxMatches = 0;
|
||||
@@ -169,7 +171,7 @@ export class ActiveWorkflowRunner {
|
||||
}
|
||||
});
|
||||
if (webhook === undefined) {
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404);
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404, WEBHOOK_PROD_UNREGISTERED_HINT);
|
||||
}
|
||||
|
||||
path = webhook!.webhookPath;
|
||||
|
||||
@@ -89,6 +89,7 @@ export async function init(): Promise<IDatabaseCollections> {
|
||||
migrations: mysqlMigrations,
|
||||
migrationsRun: true,
|
||||
migrationsTableName: `${entityPrefix}migrations`,
|
||||
timezone: 'Z', // set UTC as default
|
||||
};
|
||||
break;
|
||||
|
||||
|
||||
@@ -312,6 +312,11 @@ export interface IN8nConfigNodes {
|
||||
exclude: string[];
|
||||
}
|
||||
|
||||
export interface IVersionNotificationSettings {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
infoUrl: string;
|
||||
}
|
||||
|
||||
export interface IN8nUISettings {
|
||||
endpointWebhook: string;
|
||||
@@ -331,6 +336,8 @@ export interface IN8nUISettings {
|
||||
n8nMetadata?: {
|
||||
[key: string]: string | number | undefined;
|
||||
};
|
||||
versionNotifications: IVersionNotificationSettings;
|
||||
instanceId: string;
|
||||
}
|
||||
|
||||
export interface IPackageVersions {
|
||||
|
||||
@@ -21,17 +21,21 @@ export class ResponseError extends Error {
|
||||
// The HTTP status code of response
|
||||
httpStatusCode?: number;
|
||||
|
||||
// The error code in the resonse
|
||||
// The error code in the response
|
||||
errorCode?: number;
|
||||
|
||||
// The error hint the response
|
||||
hint?: string;
|
||||
|
||||
/**
|
||||
* Creates an instance of ResponseError.
|
||||
* @param {string} message The error message
|
||||
* @param {number} [errorCode] The error code which can be used by frontend to identify the actual error
|
||||
* @param {number} [httpStatusCode] The HTTP status code the response should have
|
||||
* @param {string} [hint] The error hint to provide a context (webhook related)
|
||||
* @memberof ResponseError
|
||||
*/
|
||||
constructor(message: string, errorCode?: number, httpStatusCode?: number) {
|
||||
constructor(message: string, errorCode?: number, httpStatusCode?: number, hint?:string) {
|
||||
super(message);
|
||||
this.name = 'ResponseError';
|
||||
|
||||
@@ -41,6 +45,9 @@ export class ResponseError extends Error {
|
||||
if (httpStatusCode) {
|
||||
this.httpStatusCode = httpStatusCode;
|
||||
}
|
||||
if (hint) {
|
||||
this.hint = hint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +98,7 @@ export function sendErrorResponse(res: Response, error: ResponseError) {
|
||||
const response = {
|
||||
code: 0,
|
||||
message: 'Unknown error',
|
||||
hint: '',
|
||||
};
|
||||
|
||||
if (error.name === 'NodeApiError') {
|
||||
@@ -103,6 +111,9 @@ export function sendErrorResponse(res: Response, error: ResponseError) {
|
||||
if (error.message) {
|
||||
response.message = error.message;
|
||||
}
|
||||
if (error.hint) {
|
||||
response.hint = error.hint;
|
||||
}
|
||||
if (error.stack && process.env.NODE_ENV !== 'production') {
|
||||
// @ts-ignore
|
||||
response.stack = error.stack;
|
||||
|
||||
@@ -21,7 +21,7 @@ import * as clientOAuth1 from 'oauth-1.0a';
|
||||
import { RequestOptions } from 'oauth-1.0a';
|
||||
import * as csrf from 'csrf';
|
||||
import * as requestPromise from 'request-promise-native';
|
||||
import { createHmac } from 'crypto';
|
||||
import { createHash, createHmac } from 'crypto';
|
||||
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
||||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
||||
import { compare } from 'bcryptjs';
|
||||
@@ -196,6 +196,12 @@ class App {
|
||||
'oauth1': urlBaseWebhook + `${this.restEndpoint}/oauth1-credential/callback`,
|
||||
'oauth2': urlBaseWebhook + `${this.restEndpoint}/oauth2-credential/callback`,
|
||||
},
|
||||
versionNotifications: {
|
||||
enabled: config.get('versionNotifications.enabled'),
|
||||
endpoint: config.get('versionNotifications.endpoint'),
|
||||
infoUrl: config.get('versionNotifications.infoUrl'),
|
||||
},
|
||||
instanceId: '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -225,6 +231,7 @@ class App {
|
||||
|
||||
this.versions = await GenericHelpers.getVersions();
|
||||
this.frontendSettings.versionCli = this.versions.cli;
|
||||
this.frontendSettings.instanceId = await generateInstanceId() as string;
|
||||
|
||||
await this.externalHooks.run('frontend.settings', [this.frontendSettings]);
|
||||
|
||||
@@ -2210,3 +2217,10 @@ async function getExecutionsCount(countFilter: IDataObject): Promise<{ count: nu
|
||||
const count = await Db.collections.Execution!.count(countFilter);
|
||||
return { count, estimate: false };
|
||||
}
|
||||
|
||||
async function generateInstanceId() {
|
||||
const encryptionKey = await UserSettings.getEncryptionKey();
|
||||
const hash = encryptionKey ? createHash('sha256').update(encryptionKey.slice(Math.round(encryptionKey.length / 2))).digest('hex') : undefined;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
WorkflowExecuteMode,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
const WEBHOOK_TEST_UNREGISTERED_HINT = `Click the 'Execute workflow' button on the canvas, then try again. (In test mode, the webhook only works for one call after you click this button)`;
|
||||
|
||||
export class TestWebhooks {
|
||||
|
||||
@@ -72,7 +72,7 @@ export class TestWebhooks {
|
||||
webhookData = this.activeWebhooks!.get(httpMethod, pathElements.join('/'), webhookId);
|
||||
if (webhookData === undefined) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404);
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404, WEBHOOK_TEST_UNREGISTERED_HINT);
|
||||
}
|
||||
|
||||
path = webhookData.path;
|
||||
@@ -90,7 +90,7 @@ export class TestWebhooks {
|
||||
// TODO: Clean that duplication up one day and improve code generally
|
||||
if (this.testWebhookData[webhookKey] === undefined) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404);
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404, WEBHOOK_TEST_UNREGISTERED_HINT);
|
||||
}
|
||||
|
||||
const workflow = this.testWebhookData[webhookKey].workflow;
|
||||
@@ -145,7 +145,7 @@ export class TestWebhooks {
|
||||
|
||||
if (webhookMethods === undefined) {
|
||||
// The requested webhook is not registered
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${path}" is not registered.`, 404, 404);
|
||||
throw new ResponseHelper.ResponseError(`The requested webhook "${path}" is not registered.`, 404, 404, WEBHOOK_TEST_UNREGISTERED_HINT);
|
||||
}
|
||||
|
||||
return webhookMethods;
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
import * as config from '../config';
|
||||
|
||||
import { LessThanOrEqual } from 'typeorm';
|
||||
import { DateUtils } from 'typeorm/util/DateUtils';
|
||||
|
||||
const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
||||
|
||||
@@ -102,7 +103,7 @@ function executeErrorWorkflow(workflowData: IWorkflowBase, fullRunData: IRun, mo
|
||||
*
|
||||
*/
|
||||
let throttling = false;
|
||||
function pruneExecutionData(): void {
|
||||
function pruneExecutionData(this: WorkflowHooks): void {
|
||||
if (!throttling) {
|
||||
Logger.verbose('Pruning execution data from database');
|
||||
|
||||
@@ -112,13 +113,20 @@ function pruneExecutionData(): void {
|
||||
const date = new Date(); // today
|
||||
date.setHours(date.getHours() - maxAge);
|
||||
|
||||
// date reformatting needed - see https://github.com/typeorm/typeorm/issues/2286
|
||||
const utcDate = DateUtils.mixedDateToUtcDatetimeString(date);
|
||||
|
||||
// throttle just on success to allow for self healing on failure
|
||||
Db.collections.Execution!.delete({ stoppedAt: LessThanOrEqual(date.toISOString()) })
|
||||
Db.collections.Execution!.delete({ stoppedAt: LessThanOrEqual(utcDate) })
|
||||
.then(data =>
|
||||
setTimeout(() => {
|
||||
throttling = false;
|
||||
}, timeout * 1000)
|
||||
).catch(err => throttling = false);
|
||||
).catch(error => {
|
||||
throttling = false;
|
||||
|
||||
Logger.error(`Failed pruning execution data from database for execution ID ${this.executionId} (hookFunctionsSave)`, { ...error, executionId: this.executionId, sessionId: this.sessionId, workflowId: this.workflowData.id });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +330,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||
|
||||
// Prune old execution data
|
||||
if (config.get('executions.pruneData')) {
|
||||
pruneExecutionData();
|
||||
pruneExecutionData.call(this);
|
||||
}
|
||||
|
||||
const isManualMode = [this.mode, parentProcessMode].includes('manual');
|
||||
|
||||
@@ -18,7 +18,7 @@ export function resolveDataType(dataType: string) {
|
||||
json: 'simple-json',
|
||||
},
|
||||
postgresdb: {
|
||||
datetime: 'timestamp',
|
||||
datetime: 'timestamptz',
|
||||
},
|
||||
mysqldb: {},
|
||||
mariadb: {},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.77.0",
|
||||
"version": "0.78.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -47,7 +47,7 @@
|
||||
"file-type": "^14.6.2",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.63.0",
|
||||
"n8n-workflow": "~0.64.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.99.0",
|
||||
"version": "0.100.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -25,7 +25,9 @@
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"v-click-outside": "^3.1.2"
|
||||
"timeago.js": "^4.0.2",
|
||||
"v-click-outside": "^3.1.2",
|
||||
"vue-fragment": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@beyonk/google-fonts-webpack-plugin": "^1.5.0",
|
||||
@@ -68,7 +70,7 @@
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.63.0",
|
||||
"n8n-workflow": "~0.64.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
|
||||
@@ -445,6 +445,12 @@ export interface IPushDataConsoleMessage {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface IVersionNotificationSettings {
|
||||
enabled: boolean;
|
||||
endpoint: string;
|
||||
infoUrl: string;
|
||||
}
|
||||
|
||||
export interface IN8nUISettings {
|
||||
endpointWebhook: string;
|
||||
endpointWebhookTest: string;
|
||||
@@ -463,6 +469,8 @@ export interface IN8nUISettings {
|
||||
n8nMetadata?: {
|
||||
[key: string]: string | number | undefined;
|
||||
};
|
||||
versionNotifications: IVersionNotificationSettings;
|
||||
instanceId: string;
|
||||
}
|
||||
|
||||
export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||
@@ -547,6 +555,29 @@ export interface ITagRow {
|
||||
delete?: boolean;
|
||||
}
|
||||
|
||||
export interface IVersion {
|
||||
name: string;
|
||||
nodes: IVersionNode[];
|
||||
createdAt: string;
|
||||
description: string;
|
||||
documentationUrl: string;
|
||||
hasBreakingChange: boolean;
|
||||
hasSecurityFix: boolean;
|
||||
hasSecurityIssue: boolean;
|
||||
securityIssueFixVersion: string;
|
||||
}
|
||||
|
||||
export interface IVersionNode {
|
||||
name: string;
|
||||
displayName: string;
|
||||
icon: string;
|
||||
defaults: INodeParameters;
|
||||
iconData: {
|
||||
type: string;
|
||||
icon?: string;
|
||||
fileBuffer?: string;
|
||||
};
|
||||
}
|
||||
export interface IRootState {
|
||||
activeExecutions: IExecutionsCurrentSummaryExtended[];
|
||||
activeWorkflows: string[];
|
||||
@@ -583,6 +614,7 @@ export interface IRootState {
|
||||
urlBaseWebhook: string;
|
||||
workflow: IWorkflowDb;
|
||||
sidebarMenuItems: IMenuItem[];
|
||||
instanceId: string;
|
||||
}
|
||||
|
||||
export interface ITagsState {
|
||||
@@ -605,6 +637,12 @@ export interface IUiState {
|
||||
isPageLoading: boolean;
|
||||
}
|
||||
|
||||
export interface IVersionsState {
|
||||
versionNotificationSettings: IVersionNotificationSettings;
|
||||
nextVersions: IVersion[];
|
||||
currentVersion: IVersion | undefined;
|
||||
}
|
||||
|
||||
export interface IWorkflowsState {
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
IRestApiContext,
|
||||
} from '../Interface';
|
||||
|
||||
|
||||
class ResponseError extends Error {
|
||||
// The HTTP status code of response
|
||||
httpStatusCode?: number;
|
||||
@@ -91,6 +90,6 @@ export async function makeRestApiRequest(context: IRestApiContext, method: Metho
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function get(baseURL: string, endpoint: string, params?: IDataObject) {
|
||||
return await request({method: 'GET', baseURL, endpoint, data: params});
|
||||
export async function get(baseURL: string, endpoint: string, params?: IDataObject, headers?: IDataObject) {
|
||||
return await request({method: 'GET', baseURL, endpoint, headers, data: params});
|
||||
}
|
||||
|
||||
9
packages/editor-ui/src/api/versions.ts
Normal file
9
packages/editor-ui/src/api/versions.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IVersion } from '@/Interface';
|
||||
import { INSTANCE_ID_HEADER } from '@/constants';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
import { get } from './helpers';
|
||||
|
||||
export async function getNextVersions(endpoint: string, version: string, instanceId: string): Promise<IVersion[]> {
|
||||
const headers = {[INSTANCE_ID_HEADER as string] : instanceId};
|
||||
return await get(endpoint, version, {}, headers);
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.7 KiB |
@@ -38,8 +38,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
|
||||
|
||||
51
packages/editor-ui/src/components/Badge.vue
Normal file
51
packages/editor-ui/src/components/Badge.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template functional>
|
||||
<fragment>
|
||||
<el-tag
|
||||
v-if="props.type === 'danger'"
|
||||
type="danger"
|
||||
size="small"
|
||||
:class="$style['danger']"
|
||||
>
|
||||
{{ props.text }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else-if="props.type === 'warning'"
|
||||
size="small"
|
||||
:class="$style['warning']"
|
||||
>
|
||||
{{ props.text }}
|
||||
</el-tag>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
props: ["text", "type"],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.badge {
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
max-height: 18px;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.danger {
|
||||
composes: badge;
|
||||
color: $--badge-danger-color;
|
||||
background-color: $--badge-danger-background-color;
|
||||
border-color: $--badge-danger-border-color;
|
||||
}
|
||||
|
||||
.warning {
|
||||
composes: badge;
|
||||
background-color: $--badge-warning-background-color;
|
||||
color: $--badge-warning-color;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
@@ -37,11 +37,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
IRunData,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
|
||||
@@ -28,8 +28,6 @@ export default mixins(genericHelpers).extend({
|
||||
return this.$store.getters.activeNode;
|
||||
},
|
||||
currentValue (): string {
|
||||
const parameterNameParts = this.keyName.split('.');
|
||||
|
||||
const getDescendantProp = (obj: object, path: string): string => {
|
||||
// @ts-ignore
|
||||
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
@@ -167,7 +167,6 @@ import {
|
||||
IExecutionDeleteFilter,
|
||||
IExecutionsListResponse,
|
||||
IExecutionShortResponse,
|
||||
IExecutionsStopData,
|
||||
IExecutionsSummary,
|
||||
IWorkflowShortResponse,
|
||||
} from '@/Interface';
|
||||
@@ -632,7 +631,7 @@ export default mixins(
|
||||
// can show the user in the UI that it is in progress
|
||||
this.stoppingExecutions.push(activeExecutionId);
|
||||
|
||||
const stopData: IExecutionsStopData = await this.restApi().stopCurrentExecution(activeExecutionId);
|
||||
await this.restApi().stopCurrentExecution(activeExecutionId);
|
||||
|
||||
// Remove it from the list of currently stopping executions
|
||||
const index = this.stoppingExecutions.indexOf(activeExecutionId);
|
||||
|
||||
@@ -262,9 +262,7 @@ export default mixins(
|
||||
// Convert the expression string into a Quill Operations
|
||||
const editorOperations: DeltaOperation[] = [];
|
||||
currentValue.replace(/\{\{(.*?)\}\}/ig, '*%%#_@^$1*%%#_@').split('*%%#_@').forEach((value: string) => {
|
||||
if (!value) {
|
||||
|
||||
} else if (value.charAt(0) === '^') {
|
||||
if (value && value.charAt(0) === '^') {
|
||||
// Is variable
|
||||
let displayValue = `{{${value.slice(1)}}}` as string | number | boolean | null | undefined;
|
||||
if (this.resolvedValue) {
|
||||
|
||||
@@ -47,8 +47,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
IUpdateInformation,
|
||||
} from '@/Interface';
|
||||
|
||||
36
packages/editor-ui/src/components/GiftNotificationIcon.vue
Normal file
36
packages/editor-ui/src/components/GiftNotificationIcon.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div :class="$style['gift-icon']">
|
||||
<font-awesome-icon icon="gift" />
|
||||
<div :class="$style['notification']">
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.gift-icon {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
.notification {
|
||||
height: .47em;
|
||||
width: .47em;
|
||||
border-radius: 50%;
|
||||
color: $--gift-notification-active-color;
|
||||
position: absolute;
|
||||
background-color: $--gift-notification-outer-color;
|
||||
right: -.3em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: -.148em;
|
||||
|
||||
div {
|
||||
height: .36em;
|
||||
width: .36em;
|
||||
background-color: $--gift-notification-inner-color;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -127,6 +127,14 @@
|
||||
|
||||
<MenuItemsIterator :items="sidebarMenuBottomItems" :root="true"/>
|
||||
|
||||
<div class="footer-menu-items">
|
||||
<el-menu-item index="updates" class="updates" v-if="hasVersionUpdates" @click="openUpdatesPanel">
|
||||
<div class="gift-container">
|
||||
<GiftNotificationIcon />
|
||||
</div>
|
||||
<span slot="title" class="item-title-root">{{nextVersions.length > 99 ? '99+' : nextVersions.length}} update{{nextVersions.length > 1 ? 's' : ''}} available</span>
|
||||
</el-menu-item>
|
||||
</div>
|
||||
</el-menu>
|
||||
|
||||
</div>
|
||||
@@ -140,7 +148,6 @@ import { MessageBoxInputData } from 'element-ui/types/message-box';
|
||||
|
||||
import {
|
||||
IExecutionResponse,
|
||||
IExecutionsStopData,
|
||||
IWorkflowDataUpdate,
|
||||
IMenuItem,
|
||||
} from '../Interface';
|
||||
@@ -149,6 +156,7 @@ import About from '@/components/About.vue';
|
||||
import CredentialsEdit from '@/components/CredentialsEdit.vue';
|
||||
import CredentialsList from '@/components/CredentialsList.vue';
|
||||
import ExecutionsList from '@/components/ExecutionsList.vue';
|
||||
import GiftNotificationIcon from './GiftNotificationIcon.vue';
|
||||
import WorkflowSettings from '@/components/WorkflowSettings.vue';
|
||||
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
@@ -212,6 +220,7 @@ export default mixins(
|
||||
CredentialsEdit,
|
||||
CredentialsList,
|
||||
ExecutionsList,
|
||||
GiftNotificationIcon,
|
||||
WorkflowSettings,
|
||||
MenuItemsIterator,
|
||||
},
|
||||
@@ -232,6 +241,10 @@ export default mixins(
|
||||
...mapGetters('ui', {
|
||||
isCollapsed: 'sidebarMenuCollapsed',
|
||||
}),
|
||||
...mapGetters('versions', [
|
||||
'hasVersionUpdates',
|
||||
'nextVersions',
|
||||
]),
|
||||
exeuctionId (): string | undefined {
|
||||
return this.$route.params.id;
|
||||
},
|
||||
@@ -311,6 +324,9 @@ export default mixins(
|
||||
openTagManager() {
|
||||
this.$store.dispatch('ui/openTagsManagerModal');
|
||||
},
|
||||
openUpdatesPanel() {
|
||||
this.$store.dispatch('ui/openUpdatesPanel');
|
||||
},
|
||||
async stopExecution () {
|
||||
const executionId = this.$store.getters.activeExecutionId;
|
||||
if (executionId === null) {
|
||||
@@ -319,7 +335,7 @@ export default mixins(
|
||||
|
||||
try {
|
||||
this.stopExecutionInProgress = true;
|
||||
const stopData: IExecutionsStopData = await this.restApi().stopCurrentExecution(executionId);
|
||||
await this.restApi().stopCurrentExecution(executionId);
|
||||
this.$showMessage({
|
||||
title: 'Execution stopped',
|
||||
message: `The execution with the id "${executionId}" got stopped!`,
|
||||
@@ -388,9 +404,8 @@ export default mixins(
|
||||
return;
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await this.restApi().deleteWorkflow(this.currentWorkflow);
|
||||
await this.restApi().deleteWorkflow(this.currentWorkflow);
|
||||
} catch (error) {
|
||||
this.$showError(error, 'Problem deleting the workflow', 'There was a problem deleting the workflow:');
|
||||
return;
|
||||
@@ -574,6 +589,39 @@ a.logo {
|
||||
&.expanded {
|
||||
width: $--sidebar-expanded-width;
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-menu-items {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
.el-menu-item.updates {
|
||||
color: $--sidebar-inactive-color;
|
||||
.item-title-root {
|
||||
font-size: 13px;
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $--sidebar-active-color;
|
||||
}
|
||||
|
||||
.gift-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
<template>
|
||||
<div v-if="dialogVisible">
|
||||
<div>
|
||||
<el-drawer
|
||||
v-if="drawer"
|
||||
:direction="drawerDirection"
|
||||
:visible="visible && visibleDrawer"
|
||||
:size="drawerWidth"
|
||||
:before-close="closeDrawer"
|
||||
>
|
||||
<template v-slot:title>
|
||||
<slot name="header" />
|
||||
</template>
|
||||
<template>
|
||||
<slot name="content"/>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-dialog
|
||||
v-else
|
||||
:visible="dialogVisible"
|
||||
:before-close="closeDialog"
|
||||
:title="title"
|
||||
@@ -32,7 +47,12 @@ const sizeMap: {[size: string]: string} = {
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Modal",
|
||||
props: ['name', 'title', 'eventBus', 'size'],
|
||||
props: ['name', 'title', 'eventBus', 'size', 'drawer', 'drawerDirection', 'drawerWidth', 'visible'],
|
||||
data() {
|
||||
return {
|
||||
visibleDrawer: this.drawer,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('keydown', this.onWindowKeydown);
|
||||
|
||||
@@ -65,8 +85,18 @@ export default Vue.extend({
|
||||
this.$emit('enter');
|
||||
}
|
||||
},
|
||||
closeDialog() {
|
||||
closeDialog(callback?: () => void) {
|
||||
this.$store.commit('ui/closeTopModal');
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
closeDrawer() {
|
||||
this.visibleDrawer = false;
|
||||
setTimeout(() =>{
|
||||
this.$store.commit('ui/closeTopModal');
|
||||
this.visibleDrawer = true;
|
||||
}, 300); // delayed for closing animation to take effect
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
@@ -84,6 +114,15 @@ export default Vue.extend({
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.el-drawer__header {
|
||||
margin: 0;
|
||||
padding: 30px 30px 0 30px;
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-wrapper {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="isOpen(name)"
|
||||
v-if="isOpen(name) || keepAlive"
|
||||
>
|
||||
<slot :modalName="name" :active="isActive(name)"></slot>
|
||||
<slot :modalName="name" :active="isActive(name)" :open="isOpen(name)"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,7 +11,7 @@ import Vue from "vue";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "ModalRoot",
|
||||
props: ["name"],
|
||||
props: ["name", "keepAlive"],
|
||||
methods: {
|
||||
isActive(name: string) {
|
||||
return this.$store.getters['ui/isModalActive'](name);
|
||||
|
||||
@@ -24,17 +24,26 @@
|
||||
/>
|
||||
</template>
|
||||
</ModalRoot>
|
||||
<ModalRoot :name="VERSIONS_MODAL_KEY" :keepAlive="true">
|
||||
<template v-slot="{ modalName, open }">
|
||||
<UpdatesPanel
|
||||
:modalName="modalName"
|
||||
:visible="open"
|
||||
/>
|
||||
</template>
|
||||
</ModalRoot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY, VERSIONS_MODAL_KEY } from '@/constants';
|
||||
|
||||
import TagsManager from "@/components/TagsManager/TagsManager.vue";
|
||||
import DuplicateWorkflowDialog from "@/components/DuplicateWorkflowDialog.vue";
|
||||
import WorkflowOpen from "@/components/WorkflowOpen.vue";
|
||||
import ModalRoot from "./ModalRoot.vue";
|
||||
import UpdatesPanel from "./UpdatesPanel.vue";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Modals",
|
||||
@@ -43,11 +52,13 @@ export default Vue.extend({
|
||||
DuplicateWorkflowDialog,
|
||||
WorkflowOpen,
|
||||
ModalRoot,
|
||||
UpdatesPanel,
|
||||
},
|
||||
data: () => ({
|
||||
DUPLICATE_MODAL_KEY,
|
||||
TAGS_MANAGER_MODAL_KEY,
|
||||
WORKLOW_OPEN_MODAL_KEY,
|
||||
VERSIONS_MODAL_KEY,
|
||||
}),
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
IUpdateInformation,
|
||||
} from '@/Interface';
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NodeIcon class="node-icon" :nodeType="nodeType" size="60" :style="nodeIconStyle" :shrink="true"/>
|
||||
<NodeIcon class="node-icon" :nodeType="nodeType" size="60" :shrink="true" :disabled="this.data.disabled"/>
|
||||
</div>
|
||||
<div class="node-description">
|
||||
<div class="node-name" :title="data.name">
|
||||
@@ -77,11 +77,6 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
||||
isExecuting (): boolean {
|
||||
return this.$store.getters.executingNode === this.data.name;
|
||||
},
|
||||
nodeIconStyle (): object {
|
||||
return {
|
||||
color: this.data.disabled ? '#ccc' : this.data.color,
|
||||
};
|
||||
},
|
||||
nodeType (): INodeTypeDescription | null {
|
||||
return this.$store.getters.nodeType(this.data.type);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template functional>
|
||||
<div :class="{[$style['node-item']]: true, [$style.bordered]: props.bordered}">
|
||||
<NodeIcon :class="$style['node-icon']" :nodeType="props.nodeType" :style="{color: props.nodeType.defaults.color}" />
|
||||
<NodeIcon :class="$style['node-icon']" :nodeType="props.nodeType" />
|
||||
<div>
|
||||
<div :class="$style.details">
|
||||
<span :class="$style.name">{{props.nodeType.displayName}}</span>
|
||||
|
||||
@@ -49,7 +49,6 @@ import {
|
||||
ICredentialsResponse,
|
||||
INodeUi,
|
||||
INodeUpdatePropertiesInformation,
|
||||
IUpdateInformation,
|
||||
} from '@/Interface';
|
||||
import {
|
||||
ICredentialType,
|
||||
@@ -212,8 +211,6 @@ export default mixins(
|
||||
return node.issues.credentials[credentialTypeName];
|
||||
},
|
||||
updateCredentials (credentialType: string): void {
|
||||
const credentials = this.credentials[credentialType];
|
||||
|
||||
const name = this.credentials[credentialType];
|
||||
const credentialData = this.credentialOptions[credentialType].find((optionData: ICredentialsResponse) => optionData.name === name);
|
||||
if (credentialData === undefined) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="node-icon-wrapper" :style="iconStyleData" :class="{shrink: isSvgIcon && shrink, full: !shrink}">
|
||||
<div v-if="nodeIconData !== null" class="icon">
|
||||
<img :src="nodeIconData.path" style="max-width: 100%; max-height: 100%;" v-if="nodeIconData.type === 'file'"/>
|
||||
<font-awesome-icon :icon="nodeIconData.path" v-else-if="nodeIconData.type === 'fa'" />
|
||||
<img v-if="nodeIconData.type === 'file'" :src="nodeIconData.fileBuffer || nodeIconData.path" style="max-width: 100%; max-height: 100%;" />
|
||||
<font-awesome-icon v-else :icon="nodeIconData.icon || nodeIconData.path" />
|
||||
</div>
|
||||
<div v-else class="node-icon-placeholder">
|
||||
{{nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
|
||||
@@ -26,16 +26,19 @@ export default Vue.extend({
|
||||
'nodeType',
|
||||
'size',
|
||||
'shrink',
|
||||
'disabled',
|
||||
],
|
||||
computed: {
|
||||
iconStyleData (): object {
|
||||
const color = this.disabled ? '#ccc' : this.nodeType.defaults && this.nodeType.defaults.color;
|
||||
if (!this.size) {
|
||||
return {};
|
||||
return {color};
|
||||
}
|
||||
|
||||
const size = parseInt(this.size, 10);
|
||||
|
||||
return {
|
||||
color,
|
||||
width: size + 'px',
|
||||
height: size + 'px',
|
||||
'font-size': Math.floor(parseInt(this.size, 10) * 0.6) + 'px',
|
||||
@@ -54,6 +57,10 @@ export default Vue.extend({
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.nodeType.iconData) {
|
||||
return this.nodeType.iconData;
|
||||
}
|
||||
|
||||
const restUrl = this.$store.getters.getRestUrl;
|
||||
|
||||
if (this.nodeType.icon) {
|
||||
@@ -94,6 +101,10 @@ export default Vue.extend({
|
||||
&.full .icon {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.shrink .icon {
|
||||
|
||||
@@ -79,8 +79,6 @@ export default mixins(
|
||||
},
|
||||
computed: {
|
||||
nodeType (): INodeTypeDescription | null {
|
||||
const activeNode = this.node;
|
||||
|
||||
if (this.node) {
|
||||
return this.$store.getters.nodeType(this.node.type);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
<template>
|
||||
<div v-if="webhooksNode.length" class="webhoooks">
|
||||
<div class="clickable headline" :class="{expanded: !isMinimized}" @click="isMinimized=!isMinimized" :title="isMinimized ? 'Click to display Webhook URLs' : 'Click to hide Webhook URLs'">
|
||||
<font-awesome-icon icon="angle-up" class="minimize-button minimize-icon" />
|
||||
<font-awesome-icon icon="angle-down" class="minimize-button minimize-icon" />
|
||||
Webhook URLs
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div class="node-webhooks" v-if="!isMinimized">
|
||||
<div class="url-selection">
|
||||
<el-row>
|
||||
<el-col :span="10" class="mode-selection-headline">
|
||||
Display URL for:
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<el-col :span="24">
|
||||
<el-radio-group v-model="showUrlFor" size="mini">
|
||||
<el-radio-button label="Production"></el-radio-button>
|
||||
<el-radio-button label="Test"></el-radio-button>
|
||||
<el-radio-button label="test">Test URL</el-radio-button>
|
||||
<el-radio-button label="production">Production URL</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -41,14 +38,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
IWebhookDescription,
|
||||
NodeHelpers,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { WEBHOOK_NODE_NAME } from '@/constants';
|
||||
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
@@ -68,8 +63,8 @@ export default mixins(
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
isMinimized: true,
|
||||
showUrlFor: 'Production',
|
||||
isMinimized: this.nodeType.name !== WEBHOOK_NODE_NAME,
|
||||
showUrlFor: 'test',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -104,7 +99,7 @@ export default mixins(
|
||||
},
|
||||
getWebhookUrl (webhookData: IWebhookDescription): string {
|
||||
let baseUrl = this.$store.getters.getWebhookUrl;
|
||||
if (this.showUrlFor === 'Test') {
|
||||
if (this.showUrlFor === 'test') {
|
||||
baseUrl = this.$store.getters.getWebhookTestUrl;
|
||||
}
|
||||
|
||||
@@ -117,7 +112,7 @@ export default mixins(
|
||||
},
|
||||
watch: {
|
||||
node () {
|
||||
this.isMinimized = true;
|
||||
this.isMinimized = this.nodeType.name !== WEBHOOK_NODE_NAME;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="addExpression" v-if="parameter.noDataExpression !== true && !isValueExpression">Add Expression</el-dropdown-item>
|
||||
<el-dropdown-item command="removeExpression" v-if="parameter.noDataExpression !== true && isValueExpression">Remove Expression</el-dropdown-item>
|
||||
<el-dropdown-item command="refreshOptions" v-if="Boolean(remoteMethod)">Refresh List</el-dropdown-item>
|
||||
<el-dropdown-item command="resetValue" :disabled="isDefault" divided>Reset Value</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
@@ -115,13 +116,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import {
|
||||
INodeUi,
|
||||
IVariableItemSelected,
|
||||
IVariableSelectorOption,
|
||||
} from '@/Interface';
|
||||
import {
|
||||
NodeHelpers,
|
||||
@@ -576,6 +574,8 @@ export default mixins(
|
||||
this.expressionEditDialogVisible = true;
|
||||
} else if (command === 'removeExpression') {
|
||||
this.valueChanged(this.expressionValueComputed || null);
|
||||
} else if (command === 'refreshOptions') {
|
||||
this.loadRemoteParameterOptions();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -196,20 +196,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
//@ts-ignore
|
||||
import VueJsonPretty from 'vue-json-pretty';
|
||||
import {
|
||||
GenericValue,
|
||||
IBinaryData,
|
||||
IBinaryKeyData,
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
IRun,
|
||||
IRunData,
|
||||
IRunExecutionData,
|
||||
ITaskData,
|
||||
ITaskDataConnections,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export default Vue.extend({
|
||||
|
||||
name: 'TextEdit',
|
||||
|
||||
15
packages/editor-ui/src/components/TimeAgo.vue
Normal file
15
packages/editor-ui/src/components/TimeAgo.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template functional>
|
||||
<span>
|
||||
{{$options.format(props.date)}}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { format } from 'timeago.js';
|
||||
|
||||
export default {
|
||||
name: 'UpdatesPanel',
|
||||
props: ['date'],
|
||||
format,
|
||||
};
|
||||
</script>
|
||||
122
packages/editor-ui/src/components/UpdatesPanel.vue
Normal file
122
packages/editor-ui/src/components/UpdatesPanel.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<Modal
|
||||
:name="modalName"
|
||||
:drawer="true"
|
||||
:visible="visible"
|
||||
drawerDirection="ltr"
|
||||
drawerWidth="520px"
|
||||
>
|
||||
<template slot="header">
|
||||
<span :class="$style.title">We’ve been busy ✨</span>
|
||||
</template>
|
||||
<template slot="content">
|
||||
<section :class="$style['description']">
|
||||
|
||||
<p v-if="currentVersion">
|
||||
You’re on {{ currentVersion.name }}, which was released
|
||||
<strong><TimeAgo :date="currentVersion.createdAt" /></strong> and is
|
||||
<strong>{{ nextVersions.length }} version{{nextVersions.length > 1 ? "s" : ""}}</strong>
|
||||
behind the latest and greatest n8n
|
||||
</p>
|
||||
|
||||
<a
|
||||
:class="$style['info-url']"
|
||||
:href="infoUrl"
|
||||
v-if="infoUrl"
|
||||
target="_blank"
|
||||
>
|
||||
<font-awesome-icon icon="info-circle"></font-awesome-icon>
|
||||
<span>How to update your n8n version</span>
|
||||
</a>
|
||||
|
||||
</section>
|
||||
<section :class="$style.versions">
|
||||
<div
|
||||
v-for="version in nextVersions"
|
||||
:key="version.name"
|
||||
:class="$style['versions-card']"
|
||||
>
|
||||
<VersionCard :version="version" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import Modal from './Modal.vue';
|
||||
import TimeAgo from './TimeAgo.vue';
|
||||
import VersionCard from './VersionCard.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'UpdatesPanel',
|
||||
components: {
|
||||
Modal,
|
||||
VersionCard,
|
||||
TimeAgo,
|
||||
},
|
||||
props: ['modalName', 'visible'],
|
||||
computed: {
|
||||
...mapGetters('versions', ['nextVersions', 'currentVersion', 'infoUrl']),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
color: $--updates-panel-text-color;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 0px 30px;
|
||||
margin-block-start: 16px;
|
||||
margin-block-end: 30px;
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: $--updates-panel-description-text-color;
|
||||
font-weight: 400;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
div {
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.versions {
|
||||
background-color: $--updates-panel-dark-background-color;
|
||||
border-top: $--updates-panel-border;
|
||||
height: 100%;
|
||||
padding: 30px;
|
||||
overflow-y: scroll;
|
||||
padding-bottom: 220px;
|
||||
}
|
||||
|
||||
.versions-card {
|
||||
margin-block-end: 15px;
|
||||
}
|
||||
|
||||
.info-url {
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
|
||||
svg {
|
||||
color: $--updates-panel-info-icon-color;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: $--updates-panel-info-url-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -12,13 +12,10 @@
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import Vue from 'vue';
|
||||
|
||||
import {
|
||||
GenericValue,
|
||||
IContextObject,
|
||||
IDataObject,
|
||||
IRun,
|
||||
IRunData,
|
||||
IRunExecutionData,
|
||||
Workflow,
|
||||
|
||||
132
packages/editor-ui/src/components/VersionCard.vue
Normal file
132
packages/editor-ui/src/components/VersionCard.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template functional>
|
||||
<a v-if="props.version" :set="version = props.version" :href="version.documentationUrl" target="_blank" :class="$style.card">
|
||||
<div :class="$style.header">
|
||||
<div>
|
||||
<div :class="$style.name">
|
||||
Version {{version.name}}
|
||||
</div>
|
||||
<WarningTooltip v-if="version.hasSecurityIssue">
|
||||
<template>
|
||||
This version has a security issue.<br/>It is listed here for completeness.
|
||||
</template>
|
||||
</WarningTooltip>
|
||||
<Badge
|
||||
v-if="version.hasSecurityFix"
|
||||
text="Security update"
|
||||
type="danger"
|
||||
/>
|
||||
<Badge
|
||||
v-if="version.hasBreakingChange"
|
||||
text="Breaking changes"
|
||||
type="warning"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style['release-date']">
|
||||
Released <TimeAgo :date="version.createdAt" />
|
||||
</div>
|
||||
</div>
|
||||
<div :class="$style.divider" v-if="version.description || (version.nodes && version.nodes.length)"></div>
|
||||
<div>
|
||||
<div v-if="version.description" v-html="version.description" :class="$style.description"></div>
|
||||
<div v-if="version.nodes && version.nodes.length > 0" :class="$style.nodes">
|
||||
<NodeIcon
|
||||
v-for="node in version.nodes"
|
||||
:key="node.name"
|
||||
:nodeType="node"
|
||||
:title="$options.nodeName(node)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import NodeIcon from './NodeIcon.vue';
|
||||
import TimeAgo from './TimeAgo.vue';
|
||||
import Badge from './Badge.vue';
|
||||
import WarningTooltip from './WarningTooltip.vue';
|
||||
import { IVersionNode } from '@/Interface';
|
||||
|
||||
Vue.component('NodeIcon', NodeIcon);
|
||||
Vue.component('TimeAgo', TimeAgo);
|
||||
Vue.component('Badge', Badge);
|
||||
Vue.component('WarningTooltip', WarningTooltip);
|
||||
|
||||
export default Vue.extend({
|
||||
components: { NodeIcon, TimeAgo, Badge, WarningTooltip },
|
||||
name: 'UpdatesPanel',
|
||||
props: ['version'],
|
||||
// @ts-ignore
|
||||
nodeName (node: IVersionNode): string {
|
||||
return node !== null ? node.displayName : 'unknown';
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.card {
|
||||
background-color: $--version-card-background-color;
|
||||
border: $--version-card-border;
|
||||
border-radius: 8px;
|
||||
display: block;
|
||||
padding: 16px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 2px 10px $--version-card-box-shadow-color;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
> div:first-child {
|
||||
flex-grow: 1;
|
||||
|
||||
> * {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
color: $--version-card-name-text-color;
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-bottom: $--version-card-border;
|
||||
width: 100%;
|
||||
margin: 10px 0 15px 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 19px;
|
||||
color: $--version-card-description-text-color;
|
||||
}
|
||||
|
||||
.release-date {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
font-weight: 400;
|
||||
color: $--version-card-release-date-text-color;
|
||||
}
|
||||
|
||||
.nodes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, 1fr);
|
||||
grid-row-gap: 12px;
|
||||
margin-block-start: 24px;
|
||||
}
|
||||
</style>
|
||||
15
packages/editor-ui/src/components/WarningTooltip.vue
Normal file
15
packages/editor-ui/src/components/WarningTooltip.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template functional>
|
||||
<el-tooltip effect="light" content=" " placement="top" >
|
||||
<div slot="content"><slot /></div>
|
||||
<font-awesome-icon :class="$style['icon']" icon="exclamation-triangle"></font-awesome-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="scss" module>
|
||||
.icon {
|
||||
font-size: 14px;
|
||||
height: 18px;
|
||||
color: $--warning-tooltip-color;
|
||||
}
|
||||
</style>
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from '../Interface';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
export default mixins(
|
||||
externalHooks,
|
||||
@@ -54,6 +55,9 @@ export default mixins(
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
dirtyState: "getStateIsDirty",
|
||||
}),
|
||||
nodesIssuesExist (): boolean {
|
||||
return this.$store.getters.nodesIssuesExist;
|
||||
},
|
||||
@@ -100,9 +104,11 @@ export default mixins(
|
||||
// workflow. If that would not happen then it could be quite confusing
|
||||
// for people because it would activate a different version of the workflow
|
||||
// than the one they can currently see.
|
||||
const importConfirm = await this.confirmMessage(`When you activate the workflow all currently unsaved changes of the workflow will be saved.`, 'Activate and save?', 'warning', 'Yes, activate and save!');
|
||||
if (importConfirm === false) {
|
||||
return;
|
||||
if (this.dirtyState) {
|
||||
const importConfirm = await this.confirmMessage(`When you activate the workflow all currently unsaved changes of the workflow will be saved.`, 'Activate and save?', 'warning', 'Yes, activate and save!');
|
||||
if (importConfirm === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current workflow data that it gets saved together with the activation
|
||||
|
||||
@@ -355,7 +355,6 @@ export default mixins(
|
||||
Vue.set(this, 'workflows', workflows);
|
||||
},
|
||||
async openDialog () {
|
||||
const workflowId = this.$route.params.name;
|
||||
if (this.$route.params.name === undefined) {
|
||||
this.$showMessage({
|
||||
title: 'No workflow active',
|
||||
|
||||
40
packages/editor-ui/src/components/mixins/newVersions.ts
Normal file
40
packages/editor-ui/src/components/mixins/newVersions.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { showMessage } from './showMessage';
|
||||
import {
|
||||
IVersion,
|
||||
} from '../../Interface';
|
||||
|
||||
export const newVersions = mixins(
|
||||
showMessage,
|
||||
).extend({
|
||||
methods: {
|
||||
async checkForNewVersions() {
|
||||
const enabled = this.$store.getters['versions/areNotificationsEnabled'];
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('versions/fetchVersions');
|
||||
|
||||
const currentVersion: IVersion | undefined = this.$store.getters['versions/currentVersion'];
|
||||
const nextVersions: IVersion[] = this.$store.getters['versions/nextVersions'];
|
||||
if (currentVersion && currentVersion.hasSecurityIssue && nextVersions.length) {
|
||||
const fixVersion = currentVersion.securityIssueFixVersion;
|
||||
let message = `Please update to latest version.`;
|
||||
if (fixVersion) {
|
||||
message = `Please update to version ${fixVersion} or higher.`;
|
||||
}
|
||||
|
||||
message = `${message} <a class="primary-color">More info</a>`;
|
||||
this.$showWarning('Critical update available', message, {
|
||||
onClick: () => {
|
||||
this.$store.dispatch('ui/openUpdatesPanel');
|
||||
},
|
||||
closeOnClick: true,
|
||||
customClass: 'clickable',
|
||||
duration: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Notification } from 'element-ui';
|
||||
import { ElNotificationOptions } from 'element-ui/types/notification';
|
||||
import { ElNotificationComponent, ElNotificationOptions } from 'element-ui/types/notification';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
@@ -16,6 +16,30 @@ export const showMessage = mixins(externalHooks).extend({
|
||||
return Notification(messageData);
|
||||
},
|
||||
|
||||
$showWarning(title: string, message: string, config?: {onClick?: () => void, duration?: number, customClass?: string, closeOnClick?: boolean}) {
|
||||
let notification: ElNotificationComponent;
|
||||
if (config && config.closeOnClick) {
|
||||
const cb = config.onClick;
|
||||
config.onClick = () => {
|
||||
if (notification) {
|
||||
notification.close();
|
||||
}
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
notification = this.$showMessage({
|
||||
title,
|
||||
message,
|
||||
type: 'warning',
|
||||
...(config || {}),
|
||||
});
|
||||
|
||||
return notification;
|
||||
},
|
||||
|
||||
$getExecutionError(error?: ExecutionError) {
|
||||
// There was a problem with executing the workflow
|
||||
let errorMessage = 'There was a problem executing the workflow!';
|
||||
|
||||
@@ -14,6 +14,7 @@ export const MAX_TAG_NAME_LENGTH = 24;
|
||||
export const DUPLICATE_MODAL_KEY = 'duplicate';
|
||||
export const TAGS_MANAGER_MODAL_KEY = 'tagsManager';
|
||||
export const WORKLOW_OPEN_MODAL_KEY = 'workflowOpen';
|
||||
export const VERSIONS_MODAL_KEY = 'versions';
|
||||
|
||||
// breakpoints
|
||||
export const BREAKPOINT_SM = 768;
|
||||
@@ -48,3 +49,6 @@ export const HIDDEN_NODES = ['n8n-nodes-base.start'];
|
||||
export const WEBHOOK_NODE_NAME = 'n8n-nodes-base.webhook';
|
||||
export const HTTP_REQUEST_NODE_NAME = 'n8n-nodes-base.httpRequest';
|
||||
export const REQUEST_NODE_FORM_URL = 'https://n8n-community.typeform.com/to/K1fBVTZ3';
|
||||
|
||||
// General
|
||||
export const INSTANCE_ID_HEADER = 'n8n-instance-id';
|
||||
@@ -21,6 +21,7 @@ import { runExternalHook } from './components/mixins/externalHooks';
|
||||
|
||||
// @ts-ignore
|
||||
import vClickOutside from 'v-click-outside';
|
||||
import Fragment from 'vue-fragment';
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import {
|
||||
@@ -62,6 +63,7 @@ import {
|
||||
faFileImport,
|
||||
faFilePdf,
|
||||
faFolderOpen,
|
||||
faGift,
|
||||
faHdd,
|
||||
faHome,
|
||||
faHourglass,
|
||||
@@ -151,6 +153,7 @@ library.add(faFileExport);
|
||||
library.add(faFileImport);
|
||||
library.add(faFilePdf);
|
||||
library.add(faFolderOpen);
|
||||
library.add(faGift);
|
||||
library.add(faHdd);
|
||||
library.add(faHome);
|
||||
library.add(faHourglass);
|
||||
@@ -194,6 +197,7 @@ library.add(faUsers);
|
||||
library.add(faClock);
|
||||
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||
Vue.use(Fragment.Plugin);
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
router.afterEach((to, from) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import { DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, VERSIONS_MODAL_KEY, WORKLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import Vue from 'vue';
|
||||
import { ActionContext, Module } from 'vuex';
|
||||
import {
|
||||
@@ -19,12 +19,18 @@ const module: Module<IUiState, IRootState> = {
|
||||
[WORKLOW_OPEN_MODAL_KEY]: {
|
||||
open: false,
|
||||
},
|
||||
[VERSIONS_MODAL_KEY]: {
|
||||
open: false,
|
||||
},
|
||||
},
|
||||
modalStack: [],
|
||||
sidebarMenuCollapsed: true,
|
||||
isPageLoading: true,
|
||||
},
|
||||
getters: {
|
||||
isVersionsOpen: (state: IUiState) => {
|
||||
return state.modals[VERSIONS_MODAL_KEY].open;
|
||||
},
|
||||
isModalOpen: (state: IUiState) => {
|
||||
return (name: string) => state.modals[name].open;
|
||||
},
|
||||
@@ -58,6 +64,9 @@ const module: Module<IUiState, IRootState> = {
|
||||
openDuplicateModal: async (context: ActionContext<IUiState, IRootState>) => {
|
||||
context.commit('openModal', DUPLICATE_MODAL_KEY);
|
||||
},
|
||||
openUpdatesPanel: async (context: ActionContext<IUiState, IRootState>) => {
|
||||
context.commit('openModal', VERSIONS_MODAL_KEY);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
62
packages/editor-ui/src/modules/versions.ts
Normal file
62
packages/editor-ui/src/modules/versions.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { getNextVersions } from '@/api/versions';
|
||||
import { ActionContext, Module } from 'vuex';
|
||||
import {
|
||||
IRootState,
|
||||
IVersion,
|
||||
IVersionsState,
|
||||
} from '../Interface';
|
||||
|
||||
const module: Module<IVersionsState, IRootState> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
versionNotificationSettings: {
|
||||
enabled: false,
|
||||
endpoint: '',
|
||||
infoUrl: '',
|
||||
},
|
||||
nextVersions: [],
|
||||
currentVersion: undefined,
|
||||
},
|
||||
getters: {
|
||||
hasVersionUpdates(state: IVersionsState) {
|
||||
return state.nextVersions.length > 0;
|
||||
},
|
||||
nextVersions(state: IVersionsState) {
|
||||
return state.nextVersions;
|
||||
},
|
||||
currentVersion(state: IVersionsState) {
|
||||
return state.currentVersion;
|
||||
},
|
||||
areNotificationsEnabled(state: IVersionsState) {
|
||||
return state.versionNotificationSettings.enabled;
|
||||
},
|
||||
infoUrl(state: IVersionsState) {
|
||||
return state.versionNotificationSettings.infoUrl;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setVersions(state: IVersionsState, {versions, currentVersion}: {versions: IVersion[], currentVersion: string}) {
|
||||
state.nextVersions = versions.filter((version) => version.name !== currentVersion);
|
||||
state.currentVersion = versions.find((version) => version.name === currentVersion);
|
||||
},
|
||||
setVersionNotificationSettings(state: IVersionsState, settings: {enabled: true, endpoint: string, infoUrl: string}) {
|
||||
state.versionNotificationSettings = settings;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async fetchVersions(context: ActionContext<IVersionsState, IRootState>) {
|
||||
try {
|
||||
const { enabled, endpoint } = context.state.versionNotificationSettings;
|
||||
if (enabled && endpoint) {
|
||||
const currentVersion = context.rootState.versionCli;
|
||||
const instanceId = context.rootState.instanceId;
|
||||
const versions = await getNextVersions(endpoint, currentVersion, instanceId);
|
||||
context.commit('setVersions', {versions, currentVersion});
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default module;
|
||||
@@ -28,6 +28,16 @@ $--custom-success-text : #40c351;
|
||||
$--custom-warning-background : #ffffe5;
|
||||
$--custom-warning-text : #eb9422;
|
||||
|
||||
// Badge
|
||||
$--badge-danger-color: #f45959;
|
||||
$--badge-danger-background-color: #fef0f0;
|
||||
$--badge-danger-border-color: #fde2e2;
|
||||
$--badge-warning-background-color: rgba(255, 229, 100, 0.3);
|
||||
$--badge-warning-color: #6b5900;
|
||||
|
||||
// Warning tooltip
|
||||
$--warning-tooltip-color: #ff8080;
|
||||
|
||||
$--custom-node-view-background : #faf9fe;
|
||||
|
||||
// Table
|
||||
@@ -44,8 +54,18 @@ $--custom-input-border-shadow: 1px solid $--custom-input-border-color;
|
||||
|
||||
$--header-height: 65px;
|
||||
|
||||
// sidebar
|
||||
$--sidebar-width: 65px;
|
||||
$--sidebar-expanded-width: 200px;
|
||||
$--sidebar-inactive-color: #909399;
|
||||
$--sidebar-active-color: $--color-primary;
|
||||
|
||||
// gifts notification
|
||||
$--gift-notification-active-color: $--color-primary;
|
||||
$--gift-notification-inner-color: $--color-primary;
|
||||
$--gift-notification-outer-color: #fff;
|
||||
|
||||
// tags manager
|
||||
$--tags-manager-min-height: 300px;
|
||||
|
||||
// based on element.io breakpoints
|
||||
@@ -83,3 +103,23 @@ $--node-creator-description-color: #7d7d87;
|
||||
// trigger icon
|
||||
$--trigger-icon-border-color: #dcdfe6;
|
||||
$--trigger-icon-background-color: #fff;
|
||||
|
||||
// drawer
|
||||
$--drawer-background-color: #fff;
|
||||
|
||||
// updates-panel
|
||||
$--updates-panel-info-icon-color: #909399;
|
||||
$--updates-panel-info-url-color: $--color-primary;
|
||||
$--updates-panel-border: 1px #dbdfe7 solid;
|
||||
$--updates-panel-dark-background-color: #f8f9fb;
|
||||
$--updates-panel-description-text-color: #7d7d87;
|
||||
$--updates-panel-text-color: #555;
|
||||
|
||||
// versions card
|
||||
$--version-card-name-text-color: #666;
|
||||
$--version-card-background-color: #fff;
|
||||
$--version-card-border: 1px #dbdfe7 solid;
|
||||
$--version-card-description-text-color: #7d7d87;
|
||||
$--version-card-release-date-text-color: #909399;
|
||||
$--version-card-box-shadow-color: rgba(109, 48, 40, 0.07);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
import tags from './modules/tags';
|
||||
import ui from './modules/ui';
|
||||
import workflows from './modules/workflows';
|
||||
import versions from './modules/versions';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@@ -86,12 +87,14 @@ const state: IRootState = {
|
||||
tags: [],
|
||||
},
|
||||
sidebarMenuItems: [],
|
||||
instanceId: '',
|
||||
};
|
||||
|
||||
const modules = {
|
||||
tags,
|
||||
ui,
|
||||
workflows,
|
||||
versions,
|
||||
};
|
||||
|
||||
export const store = new Vuex.Store({
|
||||
@@ -543,9 +546,12 @@ export const store = new Vuex.Store({
|
||||
setMaxExecutionTimeout (state, maxExecutionTimeout: number) {
|
||||
Vue.set(state, 'maxExecutionTimeout', maxExecutionTimeout);
|
||||
},
|
||||
setVersionCli (state, version: string) {
|
||||
setVersionCli(state, version: string) {
|
||||
Vue.set(state, 'versionCli', version);
|
||||
},
|
||||
setInstanceId(state, instanceId: string) {
|
||||
Vue.set(state, 'instanceId', instanceId);
|
||||
},
|
||||
setOauthCallbackUrls(state, urls: IDataObject) {
|
||||
Vue.set(state, 'oauthCallbackUrls', urls);
|
||||
},
|
||||
|
||||
@@ -125,6 +125,7 @@ import { moveNodeWorkflow } from '@/components/mixins/moveNodeWorkflow';
|
||||
import { restApi } from '@/components/mixins/restApi';
|
||||
import { showMessage } from '@/components/mixins/showMessage';
|
||||
import { titleChange } from '@/components/mixins/titleChange';
|
||||
import { newVersions } from '@/components/mixins/newVersions';
|
||||
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
import { workflowRun } from '@/components/mixins/workflowRun';
|
||||
@@ -148,7 +149,6 @@ import {
|
||||
INodeConnections,
|
||||
INodeIssues,
|
||||
INodeTypeDescription,
|
||||
NodeInputConnections,
|
||||
NodeHelpers,
|
||||
Workflow,
|
||||
IRun,
|
||||
@@ -156,7 +156,6 @@ import {
|
||||
import {
|
||||
IConnectionsUi,
|
||||
IExecutionResponse,
|
||||
IExecutionsStopData,
|
||||
IN8nUISettings,
|
||||
IWorkflowDb,
|
||||
IWorkflowData,
|
||||
@@ -198,6 +197,7 @@ export default mixins(
|
||||
titleChange,
|
||||
workflowHelpers,
|
||||
workflowRun,
|
||||
newVersions,
|
||||
)
|
||||
.extend({
|
||||
name: 'NodeView',
|
||||
@@ -908,7 +908,7 @@ export default mixins(
|
||||
|
||||
try {
|
||||
this.stopExecutionInProgress = true;
|
||||
const stopData: IExecutionsStopData = await this.restApi().stopCurrentExecution(executionId);
|
||||
await this.restApi().stopCurrentExecution(executionId);
|
||||
this.$showMessage({
|
||||
title: 'Execution stopped',
|
||||
message: `The execution with the id "${executionId}" got stopped!`,
|
||||
@@ -948,9 +948,8 @@ export default mixins(
|
||||
},
|
||||
|
||||
async stopWaitingForWebhook () {
|
||||
let result;
|
||||
try {
|
||||
result = await this.restApi().removeTestWebhook(this.$store.getters.workflowId);
|
||||
await this.restApi().removeTestWebhook(this.$store.getters.workflowId);
|
||||
} catch (error) {
|
||||
this.$showError(error, 'Problem deleting the test-webhook', 'There was a problem deleting webhook:');
|
||||
return;
|
||||
@@ -2035,7 +2034,6 @@ export default mixins(
|
||||
const nodeSourceConnections = [];
|
||||
if (currentConnections[sourceNode][type][sourceIndex]) {
|
||||
for (connectionIndex = 0; connectionIndex < currentConnections[sourceNode][type][sourceIndex].length; connectionIndex++) {
|
||||
const nodeConnection: NodeInputConnections = [];
|
||||
connectionData = currentConnections[sourceNode][type][sourceIndex][connectionIndex];
|
||||
if (!createNodeNames.includes(connectionData.node)) {
|
||||
// Node does not get created so skip input connection
|
||||
@@ -2200,8 +2198,10 @@ export default mixins(
|
||||
this.$store.commit('setExecutionTimeout', settings.executionTimeout);
|
||||
this.$store.commit('setMaxExecutionTimeout', settings.maxExecutionTimeout);
|
||||
this.$store.commit('setVersionCli', settings.versionCli);
|
||||
this.$store.commit('setInstanceId', settings.instanceId);
|
||||
this.$store.commit('setOauthCallbackUrls', settings.oauthCallbackUrls);
|
||||
this.$store.commit('setN8nMetadata', settings.n8nMetadata || {});
|
||||
this.$store.commit('versions/setVersionNotificationSettings', settings.versionNotifications);
|
||||
},
|
||||
async loadNodeTypes (): Promise<void> {
|
||||
const nodeTypes = await this.restApi().getNodeTypes();
|
||||
@@ -2228,9 +2228,10 @@ export default mixins(
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
async mounted () {
|
||||
this.$root.$on('importWorkflowData', async (data: IDataObject) => {
|
||||
const resData = await this.importWorkflowData(data.data as IWorkflowDataUpdate);
|
||||
await this.importWorkflowData(data.data as IWorkflowDataUpdate);
|
||||
});
|
||||
|
||||
this.$root.$on('newWorkflow', this.newWorkflow);
|
||||
@@ -2238,7 +2239,7 @@ export default mixins(
|
||||
this.$root.$on('importWorkflowUrl', async (data: IDataObject) => {
|
||||
const workflowData = await this.getWorkflowDataFromUrl(data.url as string);
|
||||
if (workflowData !== undefined) {
|
||||
const resData = await this.importWorkflowData(workflowData);
|
||||
await this.importWorkflowData(workflowData);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2267,6 +2268,10 @@ export default mixins(
|
||||
this.$showError(error, 'Init Problem', 'There was a problem initializing the workflow:');
|
||||
}
|
||||
this.stopLoading();
|
||||
|
||||
setTimeout(() => {
|
||||
this.checkForNewVersions();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
this.$externalHooks().run('nodeView.mount');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.17.0",
|
||||
"version": "0.18.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
@@ -59,8 +59,8 @@
|
||||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "~0.77.0",
|
||||
"n8n-workflow": "~0.63.0",
|
||||
"n8n-core": "~0.78.0",
|
||||
"n8n-workflow": "~0.64.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class CiscoWebexOAuth2Api implements ICredentialType {
|
||||
name = 'ciscoWebexOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'Cisco Webex OAuth2 API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://webexapis.com/v1/authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://webexapis.com/v1/access_token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'spark:memberships_read meeting:recordings_read spark:kms meeting:schedules_read spark:rooms_read spark:messages_write spark:memberships_write meeting:recordings_write meeting:preferences_read spark:messages_read meeting:schedules_write',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class FreshworksCrmApi implements ICredentialType {
|
||||
name = 'freshworksCrmApi';
|
||||
displayName = 'Freshworks CRM API';
|
||||
documentationUrl = 'freshdesk';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'BDsTn15vHezBlt_XGp3Tig',
|
||||
},
|
||||
{
|
||||
displayName: 'Domain',
|
||||
name: 'domain',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'n8n-org',
|
||||
description: 'Domain in the Freshworks CRM org URL. For example, in <code>https://n8n-org.myfreshworks.com</code>, the domain is <code>n8n-org</code>.',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const scopes = [
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
];
|
||||
|
||||
export class GooglePerspectiveOAuth2Api implements ICredentialType {
|
||||
name = 'googlePerspectiveOAuth2Api';
|
||||
extends = [
|
||||
'googleOAuth2Api',
|
||||
];
|
||||
displayName = 'Google Perspective OAuth2 API';
|
||||
documentationUrl = 'google';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: scopes.join(' '),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class MarketstackApi implements ICredentialType {
|
||||
name = 'marketstackApi';
|
||||
displayName = 'Marketstack API';
|
||||
documentationUrl = 'marketstack';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Use HTTPS',
|
||||
name: 'useHttps',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
default: false,
|
||||
description: 'Use HTTPS (paid plans only).',
|
||||
},
|
||||
];
|
||||
}
|
||||
26
packages/nodes-base/credentials/NocoDb.credentials.ts
Normal file
26
packages/nodes-base/credentials/NocoDb.credentials.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class NocoDb implements ICredentialType {
|
||||
name = 'nocoDb';
|
||||
displayName = 'NocoDB';
|
||||
documentationUrl = 'nocoDb';
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'API Token',
|
||||
name: 'apiToken',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Host',
|
||||
name: 'host',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'http(s)://localhost:8080',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.actionNetwork",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Sales",
|
||||
"Marketing & Content"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/actionNetwork"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.actionNetwork/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
21
packages/nodes-base/nodes/Aws/DynamoDB/AwsDynamoDB.node.json
Normal file
21
packages/nodes-base/nodes/Aws/DynamoDB/AwsDynamoDB.node.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.awsDynamoDb",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Data & Storage",
|
||||
"Development"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/aws"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.awsDynamoDb/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
20
packages/nodes-base/nodes/Cisco/Webex/CiscoWebex.node.json
Normal file
20
packages/nodes-base/nodes/Cisco/Webex/CiscoWebex.node.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.ciscoWebex",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Communication"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/ciscoWebex"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.ciscoWebex/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
499
packages/nodes-base/nodes/Cisco/Webex/CiscoWebex.node.ts
Normal file
499
packages/nodes-base/nodes/Cisco/Webex/CiscoWebex.node.ts
Normal file
@@ -0,0 +1,499 @@
|
||||
import {
|
||||
BINARY_ENCODING,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IBinaryData,
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
getAttachemnts,
|
||||
webexApiRequest,
|
||||
webexApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
meetingFields,
|
||||
meetingOperations,
|
||||
// meetingTranscriptFields,
|
||||
// meetingTranscriptOperations,
|
||||
messageFields,
|
||||
messageOperations,
|
||||
} from './descriptions';
|
||||
|
||||
import * as moment from 'moment-timezone';
|
||||
|
||||
export class CiscoWebex implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Cisco Webex',
|
||||
name: 'ciscoWebex',
|
||||
icon: 'file:ciscoWebex.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume the Cisco Webex API',
|
||||
defaults: {
|
||||
name: 'Cisco Webex',
|
||||
color: '#29b6f6',
|
||||
},
|
||||
credentials: [
|
||||
{
|
||||
name: 'ciscoWebexOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Meeting',
|
||||
value: 'meeting',
|
||||
},
|
||||
// {
|
||||
// name: 'Meeeting Transcript',
|
||||
// value: 'meetingTranscript',
|
||||
// },
|
||||
{
|
||||
name: 'Message',
|
||||
value: 'message',
|
||||
},
|
||||
],
|
||||
default: 'message',
|
||||
description: 'Resource to consume',
|
||||
},
|
||||
...meetingOperations,
|
||||
...meetingFields,
|
||||
// ...meetingTranscriptOperations,
|
||||
// ...meetingTranscriptFields,
|
||||
...messageOperations,
|
||||
...messageFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getRooms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const rooms = await webexApiRequestAllItems.call(this, 'items', 'GET', '/rooms');
|
||||
for (const room of rooms) {
|
||||
returnData.push({
|
||||
name: room.title,
|
||||
value: room.id,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
async getSites(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const sites = await webexApiRequestAllItems.call(this, 'sites', 'GET', '/meetingPreferences/sites');
|
||||
for (const site of sites) {
|
||||
returnData.push({
|
||||
name: site.siteUrl,
|
||||
value: site.siteUrl,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const timezone = this.getTimezone();
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
let responseData;
|
||||
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
try {
|
||||
if (resource === 'message') {
|
||||
|
||||
// **********************************************************************
|
||||
// message
|
||||
// **********************************************************************
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// message: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.webex.com/docs/api/v1/messages/create-a-message
|
||||
const destination = this.getNodeParameter('destination', i);
|
||||
const file = this.getNodeParameter('additionalFields.fileUi.fileValue', i, {}) as IDataObject;
|
||||
const markdown = this.getNodeParameter('additionalFields.markdown', i, '') as boolean;
|
||||
const body = {} as IDataObject;
|
||||
if (destination === 'room') {
|
||||
body['roomId'] = this.getNodeParameter('roomId', i);
|
||||
}
|
||||
|
||||
if (destination === 'person') {
|
||||
const specifyPersonBy = this.getNodeParameter('specifyPersonBy', 0) as string;
|
||||
if (specifyPersonBy === 'id') {
|
||||
body['toPersonId'] = this.getNodeParameter('toPersonId', i);
|
||||
} else {
|
||||
body['toPersonEmail'] = this.getNodeParameter('toPersonEmail', i);
|
||||
}
|
||||
}
|
||||
|
||||
if (markdown) {
|
||||
body['markdown'] = markdown;
|
||||
}
|
||||
|
||||
body['text'] = this.getNodeParameter('text', i);
|
||||
|
||||
body.attachments = getAttachemnts(this.getNodeParameter('additionalFields.attachmentsUi.attachmentValues', i, []) as IDataObject[]);
|
||||
|
||||
if (Object.keys(file).length) {
|
||||
|
||||
const isBinaryData = file.fileLocation === 'binaryData' ? true : false;
|
||||
|
||||
if (isBinaryData) {
|
||||
|
||||
if (!items[i].binary) {
|
||||
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
|
||||
}
|
||||
|
||||
const binaryPropertyName = file.binaryPropertyName as string;
|
||||
|
||||
const binaryData = items[i].binary![binaryPropertyName] as IBinaryData;
|
||||
|
||||
const formData = {
|
||||
files: {
|
||||
value: Buffer.from(binaryData.data, BINARY_ENCODING),
|
||||
options: {
|
||||
filename: binaryData.fileName,
|
||||
contentType: binaryData.mimeType,
|
||||
},
|
||||
},
|
||||
};
|
||||
Object.assign(body, formData);
|
||||
} else {
|
||||
const url = file.url as string;
|
||||
Object.assign(body, { files: url });
|
||||
}
|
||||
}
|
||||
|
||||
if (file.fileLocation === 'binaryData') {
|
||||
responseData = await webexApiRequest.call(this, 'POST', '/messages', {}, {}, undefined, { formData: body });
|
||||
} else {
|
||||
responseData = await webexApiRequest.call(this, 'POST', '/messages', body);
|
||||
}
|
||||
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// message: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.webex.com/docs/api/v1/messages/delete-a-message
|
||||
const messageId = this.getNodeParameter('messageId', i);
|
||||
|
||||
const endpoint = `/messages/${messageId}`;
|
||||
responseData = await webexApiRequest.call(this, 'DELETE', endpoint);
|
||||
responseData = { success: true };
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// message: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.webex.com/docs/api/v1/messages/get-message-details
|
||||
const messageId = this.getNodeParameter('messageId', i);
|
||||
|
||||
const endpoint = `/messages/${messageId}`;
|
||||
responseData = await webexApiRequest.call(this, 'GET', endpoint);
|
||||
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// message: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.webex.com/docs/api/v1/messages/list-messages
|
||||
const qs: IDataObject = {
|
||||
roomId: this.getNodeParameter('roomId', i),
|
||||
};
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
|
||||
if (Object.keys(filters).length) {
|
||||
Object.assign(qs, filters);
|
||||
}
|
||||
|
||||
if (returnAll === true) {
|
||||
responseData = await webexApiRequestAllItems.call(this, 'items', 'GET', '/messages', {}, qs);
|
||||
} else {
|
||||
qs.max = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await webexApiRequest.call(this, 'GET', '/messages', {}, qs);
|
||||
responseData = responseData.items;
|
||||
}
|
||||
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// message: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developer.webex.com/docs/api/v1/messages/edit-a-message
|
||||
const messageId = this.getNodeParameter('messageId', i) as string;
|
||||
const markdown = this.getNodeParameter('markdown', i) as boolean;
|
||||
|
||||
const endpoint = `/messages/${messageId}`;
|
||||
|
||||
responseData = await webexApiRequest.call(this, 'GET', endpoint);
|
||||
|
||||
const body = {
|
||||
roomId: responseData.roomId,
|
||||
} as IDataObject;
|
||||
|
||||
if (markdown === true) {
|
||||
body['markdown'] = this.getNodeParameter('markdownText', i);
|
||||
} else {
|
||||
body['text'] = this.getNodeParameter('text', i);
|
||||
}
|
||||
|
||||
responseData = await webexApiRequest.call(this, 'PUT', endpoint, body);
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'meeting') {
|
||||
if (operation === 'create') {
|
||||
const title = this.getNodeParameter('title', i) as string;
|
||||
const start = this.getNodeParameter('start', i) as string;
|
||||
const end = this.getNodeParameter('end', i) as string;
|
||||
const invitees = this.getNodeParameter('additionalFields.inviteesUi.inviteeValues', i, []) as IDataObject[];
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
const body: IDataObject = {
|
||||
title,
|
||||
start: moment.tz(start, timezone).format(),
|
||||
end: moment.tz(end, timezone).format(),
|
||||
...additionalFields,
|
||||
};
|
||||
|
||||
if (body.requireRegistrationInfo) {
|
||||
body['registration'] = (body.requireRegistrationInfo as string[])
|
||||
.reduce((obj, value) => Object.assign(obj, { [`${value}`]: true }), {});
|
||||
delete body.requireRegistrationInfo;
|
||||
}
|
||||
|
||||
if (invitees) {
|
||||
body['invitees'] = invitees;
|
||||
delete body.inviteesUi;
|
||||
}
|
||||
|
||||
responseData = await webexApiRequest.call(this, 'POST', '/meetings', body);
|
||||
|
||||
}
|
||||
|
||||
if (operation === 'delete') {
|
||||
const meetingId = this.getNodeParameter('meetingId', i) as string;
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
|
||||
const qs: IDataObject = {
|
||||
...options,
|
||||
};
|
||||
|
||||
responseData = await webexApiRequest.call(this, 'DELETE', `/meetings/${meetingId}`, {}, qs);
|
||||
responseData = { success: true };
|
||||
}
|
||||
|
||||
if (operation === 'get') {
|
||||
const meetingId = this.getNodeParameter('meetingId', i) as string;
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
let headers = {};
|
||||
|
||||
const qs: IDataObject = {
|
||||
...options,
|
||||
};
|
||||
|
||||
if (options.passsword) {
|
||||
headers = {
|
||||
passsword: options.passsword,
|
||||
};
|
||||
}
|
||||
|
||||
responseData = await webexApiRequest.call(this, 'GET', `/meetings/${meetingId}`, {}, qs, undefined, { headers });
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
const qs: IDataObject = {
|
||||
...filters,
|
||||
};
|
||||
|
||||
if (qs.from) {
|
||||
qs.from = moment(qs.from as string).utc(true).format();
|
||||
}
|
||||
|
||||
if (qs.to) {
|
||||
qs.to = moment(qs.to as string).utc(true).format();
|
||||
}
|
||||
|
||||
if (returnAll === true) {
|
||||
responseData = await webexApiRequestAllItems.call(this, 'items', 'GET', '/meetings', {}, qs);
|
||||
returnData.push(...responseData);
|
||||
} else {
|
||||
qs.max = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await webexApiRequest.call(this, 'GET', '/meetings', {}, qs);
|
||||
responseData = responseData.items;
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'update') {
|
||||
const meetingId = this.getNodeParameter('meetingId', i) as string;
|
||||
const invitees = this.getNodeParameter('updateFields.inviteesUi.inviteeValues', i, []) as IDataObject[];
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
const {
|
||||
title,
|
||||
password,
|
||||
start,
|
||||
end,
|
||||
} = await webexApiRequest.call(this, 'GET', `/meetings/${meetingId}`);
|
||||
|
||||
const body: IDataObject = {
|
||||
...updateFields,
|
||||
};
|
||||
|
||||
if (body.requireRegistrationInfo) {
|
||||
body['registration'] = (body.requireRegistrationInfo as string[])
|
||||
.reduce((obj, value) => Object.assign(obj, { [`${value}`]: true }), {});
|
||||
delete body.requireRegistrationInfo;
|
||||
}
|
||||
|
||||
if (invitees.length) {
|
||||
body['invitees'] = invitees;
|
||||
}
|
||||
|
||||
if (body.start) {
|
||||
body.start = moment.tz(updateFields.start, timezone).format();
|
||||
} else {
|
||||
body.start = start;
|
||||
}
|
||||
|
||||
if (body.end) {
|
||||
body.end = moment.tz(updateFields.end, timezone).format();
|
||||
} else {
|
||||
body.end = end;
|
||||
}
|
||||
|
||||
if (!body.title) {
|
||||
body.title = title;
|
||||
}
|
||||
|
||||
if (!body.password) {
|
||||
body.password = password;
|
||||
}
|
||||
|
||||
responseData = await webexApiRequest.call(this, 'PUT', `/meetings/${meetingId}`, body);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else if (responseData !== undefined) {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.toString() });
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if (resource === 'meetingTranscript') {
|
||||
|
||||
// if (operation === 'download') {
|
||||
// for (let i = 0; i < items.length; i++) {
|
||||
// const transcriptId = this.getNodeParameter('transcriptId', i) as string;
|
||||
// const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||
// const meetingId = this.getNodeParameter('meetingId', i) as string;
|
||||
// const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
|
||||
// const qs: IDataObject = {
|
||||
// meetingId,
|
||||
// ...options,
|
||||
// };
|
||||
// const transcription = await webexApiRequest.call(this, 'GET', `/meetingTranscripts/${transcriptId}/download`, {}, qs);
|
||||
|
||||
// responseData = {
|
||||
// json: {},
|
||||
// binary: {
|
||||
// [binaryPropertyName]: {
|
||||
// data: Buffer.from(transcription, BINARY_ENCODING),
|
||||
// //contentType:
|
||||
// //FILE
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (operation === 'getAll') {
|
||||
// for (let i = 0; i < items.length; i++) {
|
||||
// try {
|
||||
// const meetingId = this.getNodeParameter('meetingId', i) as string;
|
||||
// const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
// const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
// const qs: IDataObject = {
|
||||
// meetingId,
|
||||
// ...filters,
|
||||
// };
|
||||
|
||||
// if (returnAll === true) {
|
||||
// responseData = await webexApiRequestAllItems.call(this, 'items', 'GET', '/meetingTranscripts', {}, qs);
|
||||
// returnData.push(...responseData);
|
||||
// } else {
|
||||
// qs.max = this.getNodeParameter('limit', i) as number;
|
||||
// responseData = await webexApiRequest.call(this, 'GET', '/meetingTranscripts', {}, qs);
|
||||
// returnData.push(...responseData.items);
|
||||
// }
|
||||
// } catch (error) {
|
||||
// if (this.continueOnFail()) {
|
||||
// returnData.push({
|
||||
// error: error.message,
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.ciscoWebexTrigger",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Communication"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/ciscoWebex"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.ciscoWebexTrigger/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
683
packages/nodes-base/nodes/Cisco/Webex/CiscoWebexTrigger.node.ts
Normal file
683
packages/nodes-base/nodes/Cisco/Webex/CiscoWebexTrigger.node.ts
Normal file
@@ -0,0 +1,683 @@
|
||||
import {
|
||||
IHookFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
getAutomaticSecret,
|
||||
getEvents,
|
||||
mapResource,
|
||||
webexApiRequest,
|
||||
webexApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
createHmac,
|
||||
} from 'crypto';
|
||||
|
||||
export class CiscoWebexTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Cisco Webex Trigger',
|
||||
name: 'ciscoWebexTrigger',
|
||||
icon: 'file:ciscoWebex.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["resource"] + ":" + $parameter["event"]}}',
|
||||
description: 'Starts the workflow when Cisco Webex events occur.',
|
||||
defaults: {
|
||||
name: 'Cisco Webex Trigger',
|
||||
color: '#29b6f6',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'ciscoWebexOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
name: 'default',
|
||||
httpMethod: 'POST',
|
||||
responseMode: 'onReceived',
|
||||
path: 'webhook',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Attachment Action',
|
||||
value: 'attachmentAction',
|
||||
},
|
||||
{
|
||||
name: 'Meeting',
|
||||
value: 'meeting',
|
||||
},
|
||||
{
|
||||
name: 'Membership',
|
||||
value: 'membership',
|
||||
},
|
||||
{
|
||||
name: 'Message',
|
||||
value: 'message',
|
||||
},
|
||||
// {
|
||||
// name: 'Telephony Call',
|
||||
// value: 'telephonyCall',
|
||||
// },
|
||||
{
|
||||
name: 'Recording',
|
||||
value: 'recording',
|
||||
},
|
||||
{
|
||||
name: 'Room',
|
||||
value: 'room',
|
||||
},
|
||||
{
|
||||
name: '*',
|
||||
value: 'all',
|
||||
},
|
||||
],
|
||||
default: 'meeting',
|
||||
required: true,
|
||||
},
|
||||
...getEvents(),
|
||||
{
|
||||
displayName: 'Resolve Data',
|
||||
name: 'resolveData',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'attachmentAction',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'By default the response only contain a reference to the data the user inputed<br />If this option gets activated it will resolve the data automatically.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Has Files',
|
||||
name: 'hasFiles',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'message',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Limit to messages which contain file content attachments',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Locked',
|
||||
name: 'isLocked',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'room',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'updated',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Limit to rooms that are locked',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Moderator',
|
||||
name: 'isModerator',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'membership',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'updated',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Limit to moderators of a room',
|
||||
},
|
||||
{
|
||||
displayName: 'Mentioned People',
|
||||
name: 'mentionedPeople',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'message',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Limit to messages which contain these mentioned people, by person ID; accepts me as a shorthand for your own person ID; separate multiple values with commas`,
|
||||
},
|
||||
{
|
||||
displayName: 'Message ID',
|
||||
name: 'messageId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'attachmentAction',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular message, by ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Owned By',
|
||||
name: 'ownedBy',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'meeting',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Person Email',
|
||||
name: 'personEmail',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'membership',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'updated',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular person, by email',
|
||||
},
|
||||
{
|
||||
displayName: 'Person Email',
|
||||
name: 'personEmail',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'message',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular person, by email',
|
||||
},
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'personId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'attachmentAction',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular person, by ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'personId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'membership',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'updated',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular person, by ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'personId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'message',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular person, by ID',
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Room ID',
|
||||
name: 'roomId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'attachmentAction',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular room, by ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Room ID',
|
||||
name: 'roomId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'membership',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'updated',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular room, by ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Room ID',
|
||||
name: 'roomId',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'message',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'updated',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Limit to a particular room, by ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Room Type',
|
||||
name: 'roomType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Direct',
|
||||
value: 'direct',
|
||||
},
|
||||
{
|
||||
name: 'Group',
|
||||
value: 'group',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'message',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'deleted',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Limit to a particular room type`,
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Direct',
|
||||
value: 'direct',
|
||||
},
|
||||
{
|
||||
name: 'Group',
|
||||
value: 'group',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/resource': [
|
||||
'room',
|
||||
],
|
||||
'/event': [
|
||||
'created',
|
||||
'updated',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Limit to a particular room type`,
|
||||
},
|
||||
// {
|
||||
// displayName: 'Call Type',
|
||||
// name: 'callType',
|
||||
// type: 'options',
|
||||
// options: [
|
||||
// {
|
||||
// name: 'Emergency',
|
||||
// value: 'emergency',
|
||||
// },
|
||||
// {
|
||||
// name: 'External',
|
||||
// value: 'external',
|
||||
// },
|
||||
// {
|
||||
// name: 'Location',
|
||||
// value: 'location',
|
||||
// },
|
||||
// {
|
||||
// name: 'Disconnected',
|
||||
// value: 'disconnected',
|
||||
// },
|
||||
// {
|
||||
// name: 'Organization',
|
||||
// value: 'organization',
|
||||
// },
|
||||
// {
|
||||
// name: 'Other',
|
||||
// value: 'other',
|
||||
// },
|
||||
// {
|
||||
// name: 'Repair',
|
||||
// value: 'repair',
|
||||
// },
|
||||
// ],
|
||||
// displayOptions: {
|
||||
// show: {
|
||||
// '/resource': [
|
||||
// 'telephonyCall',
|
||||
// ],
|
||||
// '/event': [
|
||||
// 'created',
|
||||
// 'deleted',
|
||||
// 'updated',
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// default: '',
|
||||
// description: `Limit to a particular call type`,
|
||||
// },
|
||||
// {
|
||||
// displayName: 'Person ID',
|
||||
// name: 'personId',
|
||||
// type: 'string',
|
||||
// displayOptions: {
|
||||
// show: {
|
||||
// '/resource': [
|
||||
// 'telephonyCall',
|
||||
// ],
|
||||
// '/event': [
|
||||
// 'created',
|
||||
// 'deleted',
|
||||
// 'updated',
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// default: '',
|
||||
// description: 'Limit to a particular person, by ID',
|
||||
// },
|
||||
// {
|
||||
// displayName: 'Personality',
|
||||
// name: 'personality',
|
||||
// type: 'options',
|
||||
// options: [
|
||||
// {
|
||||
// name: 'Click To Dial',
|
||||
// value: 'clickToDial',
|
||||
// },
|
||||
// {
|
||||
// name: 'Originator',
|
||||
// value: 'originator',
|
||||
// },
|
||||
// {
|
||||
// name: 'Terminator',
|
||||
// value: 'terminator',
|
||||
// },
|
||||
// ],
|
||||
// displayOptions: {
|
||||
// show: {
|
||||
// '/resource': [
|
||||
// 'telephonyCall',
|
||||
// ],
|
||||
// '/event': [
|
||||
// 'created',
|
||||
// 'deleted',
|
||||
// 'updated',
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// default: '',
|
||||
// description: `Limit to a particular call personality`,
|
||||
// },
|
||||
// {
|
||||
// displayName: 'State',
|
||||
// name: 'state',
|
||||
// type: 'options',
|
||||
// options: [
|
||||
// {
|
||||
// name: 'Alerting',
|
||||
// value: 'alerting',
|
||||
// },
|
||||
// {
|
||||
// name: 'Connected',
|
||||
// value: 'connected',
|
||||
// },
|
||||
// {
|
||||
// name: 'Connecting',
|
||||
// value: 'connecting',
|
||||
// },
|
||||
// {
|
||||
// name: 'Disconnected',
|
||||
// value: 'disconnected',
|
||||
// },
|
||||
// {
|
||||
// name: 'Held',
|
||||
// value: 'held',
|
||||
// },
|
||||
// {
|
||||
// name: 'Remote Held',
|
||||
// value: 'remoteHeld',
|
||||
// },
|
||||
// ],
|
||||
// displayOptions: {
|
||||
// show: {
|
||||
// '/resource': [
|
||||
// 'telephonyCall',
|
||||
// ],
|
||||
// '/event': [
|
||||
// 'created',
|
||||
// 'deleted',
|
||||
// 'updated',
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// default: '',
|
||||
// description: `Limit to a particular call state`,
|
||||
// },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// @ts-ignore (because of request)
|
||||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const resource = this.getNodeParameter('resource') as string;
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
|
||||
// Check all the webhooks which exist already if it is identical to the
|
||||
// one that is supposed to get created.
|
||||
const data = await webexApiRequestAllItems.call(this, 'items', 'GET', '/webhooks');
|
||||
for (const webhook of data) {
|
||||
if (webhook.url === webhookUrl
|
||||
&& webhook.resource === mapResource(resource)
|
||||
&& webhook.event === event
|
||||
&& webhook.status === 'active') {
|
||||
webhookData.webhookId = webhook.id as string;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
const resource = this.getNodeParameter('resource') as string;
|
||||
const filters = this.getNodeParameter('filters', {}) as IDataObject;
|
||||
const secret = getAutomaticSecret(this.getCredentials('ciscoWebexOAuth2Api')!);
|
||||
const filter = [];
|
||||
for (const key of Object.keys(filters)) {
|
||||
if (key !== 'ownedBy') {
|
||||
filter.push(`${key}=${filters[key]}`);
|
||||
}
|
||||
}
|
||||
const endpoint = '/webhooks';
|
||||
|
||||
const body: IDataObject = {
|
||||
name: `n8n-webhook:${webhookUrl}`,
|
||||
targetUrl: webhookUrl,
|
||||
event,
|
||||
resource: mapResource(resource),
|
||||
};
|
||||
|
||||
if (filters.ownedBy) {
|
||||
body['ownedBy'] = filters.ownedBy as string;
|
||||
}
|
||||
|
||||
body['secret'] = secret;
|
||||
|
||||
if (filter.length) {
|
||||
body['filter'] = filter.join('&');
|
||||
}
|
||||
|
||||
const responseData = await webexApiRequest.call(this, 'POST', endpoint, body);
|
||||
if (responseData.id === undefined) {
|
||||
// Required data is missing so was not successful
|
||||
return false;
|
||||
}
|
||||
|
||||
webhookData.webhookId = responseData.id as string;
|
||||
webhookData.secret = secret;
|
||||
return true;
|
||||
},
|
||||
async delete(this: IHookFunctions): Promise<boolean> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
if (webhookData.webhookId !== undefined) {
|
||||
|
||||
const endpoint = `/webhooks/${webhookData.webhookId}`;
|
||||
try {
|
||||
await webexApiRequest.call(this, 'DELETE', endpoint);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove from the static workflow data so that it is clear
|
||||
// that no webhooks are registred anymore
|
||||
delete webhookData.webhookId;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
let bodyData = this.getBodyData();
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const headers = this.getHeaderData() as IDataObject;
|
||||
const req = this.getRequestObject();
|
||||
const resolveData = this.getNodeParameter('resolveData', false) as boolean;
|
||||
|
||||
//@ts-ignore
|
||||
const computedSignature = createHmac('sha1', webhookData.secret).update(req.rawBody).digest('hex');
|
||||
if (headers['x-spark-signature'] !== computedSignature) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (resolveData) {
|
||||
const { data: { id } } = bodyData as { data: { id: string } };
|
||||
bodyData = await webexApiRequest.call(this, 'GET', `/attachment/actions/${id}`);
|
||||
}
|
||||
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray(bodyData),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
657
packages/nodes-base/nodes/Cisco/Webex/GenericFunctions.ts
Normal file
657
packages/nodes-base/nodes/Cisco/Webex/GenericFunctions.ts
Normal file
@@ -0,0 +1,657 @@
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialDataDecryptedObject,
|
||||
IDataObject,
|
||||
INodeProperties,
|
||||
IWebhookFunctions,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
upperFirst,
|
||||
} from 'lodash';
|
||||
|
||||
import {
|
||||
createHash,
|
||||
} from 'crypto';
|
||||
|
||||
export async function webexApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
let options: OptionsWithUri = {
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: uri || `https://webexapis.com/v1${resource}`,
|
||||
json: true,
|
||||
};
|
||||
try {
|
||||
if (Object.keys(option).length !== 0) {
|
||||
options = Object.assign({}, options, option);
|
||||
}
|
||||
if (Object.keys(body).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
if (Object.keys(qs).length === 0) {
|
||||
delete options.qs;
|
||||
}
|
||||
//@ts-ignore
|
||||
return await this.helpers.requestOAuth2.call(this, 'ciscoWebexOAuth2Api', options, { tokenType: 'Bearer' });
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function webexApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}, options: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
let uri: string | undefined;
|
||||
query.max = 100;
|
||||
do {
|
||||
responseData = await webexApiRequest.call(this, method, endpoint, body, query, uri, { resolveWithFullResponse: true, ...options });
|
||||
if (responseData.headers.link) {
|
||||
uri = responseData.headers['link'].split(';')[0].replace('<', '').replace('>', '');
|
||||
}
|
||||
returnData.push.apply(returnData, responseData.body[propertyName]);
|
||||
} while (
|
||||
responseData.headers['link'] !== undefined &&
|
||||
responseData.headers['link'].includes('rel="next"')
|
||||
);
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function getEvents() {
|
||||
const resourceEvents: { [key: string]: string[] } = {
|
||||
'attachmentAction': ['created', 'deleted', 'updated', '*'],
|
||||
'membership': ['created', 'deleted', 'updated', '*'],
|
||||
'message': ['created', 'deleted', 'updated', '*'],
|
||||
'room': ['created', 'deleted', 'updated', '*'],
|
||||
'meeting': ['created', 'deleted', 'updated', 'started', 'ended', '*'],
|
||||
'recording': ['created', 'deleted', 'updated', '*'],
|
||||
'telephonyCall': ['created', 'deleted', 'updated'],
|
||||
'*': ['created', 'updated', 'deleted', '*'],
|
||||
};
|
||||
|
||||
const elements: INodeProperties[] = [];
|
||||
|
||||
for (const resource of Object.keys(resourceEvents)) {
|
||||
elements.push({
|
||||
displayName: 'Event',
|
||||
name: 'event',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
(resource === '*') ? 'all' : resource,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: resourceEvents[resource].map((event) => ({ value: (event === '*' ? 'all' : event), name: upperFirst(event) })),
|
||||
default: '',
|
||||
required: true,
|
||||
});
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
export function mapResource(event: string) {
|
||||
return ({
|
||||
'attachmentAction': 'attachmentActions',
|
||||
'membership': 'memberships',
|
||||
'message': 'messages',
|
||||
'room': 'rooms',
|
||||
'meeting': 'meetings',
|
||||
'recording': 'recordings',
|
||||
'telephonyCall': 'telephony_calls',
|
||||
'all': 'all',
|
||||
} as { [key: string]: string })[event];
|
||||
}
|
||||
|
||||
export function getAttachemnts(attachements: IDataObject[]) {
|
||||
const _attachments: IDataObject[] = [];
|
||||
for (const attachment of attachements) {
|
||||
const body: IDataObject[] = [];
|
||||
const actions: IDataObject[] = [];
|
||||
for (const element of (attachment?.elementsUi as IDataObject).elementValues as IDataObject[] || []) {
|
||||
// tslint:disable-next-line: no-any
|
||||
const { type, ...rest } = element as { type: string, [key: string]: any };
|
||||
if (type.startsWith('input')) {
|
||||
body.push({ type: `Input.${upperFirst(type.replace('input', ''))}`, ...removeEmptyProperties(rest) });
|
||||
} else {
|
||||
body.push({ type: upperFirst(type), ...removeEmptyProperties(rest) });
|
||||
}
|
||||
}
|
||||
for (const action of (attachment?.actionsUi as IDataObject).actionValues as IDataObject[] || []) {
|
||||
// tslint:disable-next-line: no-any
|
||||
const { type, ...rest } = action as { type: string, [key: string]: any };
|
||||
actions.push({ type: `Action.${upperFirst(type)}`, ...removeEmptyProperties(rest) });
|
||||
}
|
||||
_attachments.push({
|
||||
contentType: 'application/vnd.microsoft.card.adaptive',
|
||||
content: {
|
||||
$schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
|
||||
type: 'AdaptiveCard',
|
||||
version: '1.2',
|
||||
body,
|
||||
actions,
|
||||
},
|
||||
});
|
||||
}
|
||||
return _attachments;
|
||||
}
|
||||
|
||||
export function getActionInheritedProperties() {
|
||||
return [
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Label for button or link that represents this action.',
|
||||
},
|
||||
{
|
||||
displayName: 'Icon URL',
|
||||
name: 'iconUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Optional icon to be shown on the action in conjunction with the title. Supports data URI in version 1.2+',
|
||||
},
|
||||
{
|
||||
displayName: 'Style',
|
||||
name: 'style',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: 'Positive',
|
||||
value: 'positive',
|
||||
},
|
||||
{
|
||||
name: 'Destructive',
|
||||
value: 'destructive',
|
||||
},
|
||||
],
|
||||
default: 'default',
|
||||
description: 'Controls the style of an Action, which influences how the action is displayed, spoken, etc.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getTextBlockProperties() {
|
||||
return [
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'Text to display. A subset of markdown is supported (https://aka.ms/ACTextFeatures)',
|
||||
},
|
||||
{
|
||||
displayName: 'Color',
|
||||
name: 'color',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: 'Dark',
|
||||
value: 'dark',
|
||||
},
|
||||
{
|
||||
name: 'Light',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
name: 'Accent',
|
||||
value: 'accent',
|
||||
},
|
||||
{
|
||||
name: 'Good',
|
||||
value: 'good',
|
||||
},
|
||||
{
|
||||
name: 'Warning',
|
||||
value: 'warning',
|
||||
},
|
||||
{
|
||||
name: 'Attention',
|
||||
value: 'attention',
|
||||
},
|
||||
],
|
||||
default: 'default',
|
||||
description: 'Color of the TextBlock element',
|
||||
},
|
||||
{
|
||||
displayName: 'Font Type',
|
||||
name: 'fontType',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: 'Monospace',
|
||||
value: 'monospace',
|
||||
},
|
||||
],
|
||||
default: 'default',
|
||||
description: 'Type of font to use for rendering',
|
||||
},
|
||||
{
|
||||
displayName: 'Horizontal Alignment',
|
||||
name: 'horizontalAlignment',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Left',
|
||||
value: 'left',
|
||||
},
|
||||
{
|
||||
name: 'Center',
|
||||
value: 'center',
|
||||
},
|
||||
{
|
||||
name: 'Right',
|
||||
value: 'right',
|
||||
},
|
||||
],
|
||||
default: 'left',
|
||||
description: 'Controls the horizontal text alignment',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Subtle',
|
||||
name: 'isSubtle',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Displays text slightly toned down to appear less prominent',
|
||||
},
|
||||
{
|
||||
displayName: 'Max Lines',
|
||||
name: 'maxLines',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 1,
|
||||
description: 'Specifies the maximum number of lines to display',
|
||||
},
|
||||
{
|
||||
displayName: 'Size',
|
||||
name: 'size',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: 'Small',
|
||||
value: 'small',
|
||||
},
|
||||
{
|
||||
name: 'Medium',
|
||||
value: 'medium',
|
||||
},
|
||||
{
|
||||
name: 'Large',
|
||||
value: 'large',
|
||||
},
|
||||
{
|
||||
name: 'Extra Large',
|
||||
value: 'extraLarge',
|
||||
},
|
||||
],
|
||||
default: 'default',
|
||||
description: 'Controls size of text',
|
||||
},
|
||||
{
|
||||
displayName: 'Weight',
|
||||
name: 'weight',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: 'Lighter',
|
||||
value: 'lighter',
|
||||
},
|
||||
{
|
||||
name: 'Bolder',
|
||||
value: 'bolder',
|
||||
},
|
||||
],
|
||||
default: 'default',
|
||||
description: 'Controls the weight of TextBlock elements',
|
||||
},
|
||||
{
|
||||
displayName: 'Wrap',
|
||||
name: 'wrap',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'If true, allow text to wrap. Otherwise, text is clipped',
|
||||
},
|
||||
{
|
||||
displayName: 'Height',
|
||||
name: 'height',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Auto',
|
||||
value: 'auto',
|
||||
},
|
||||
{
|
||||
name: 'Stretch',
|
||||
value: 'stretch',
|
||||
},
|
||||
],
|
||||
default: 'auto',
|
||||
description: 'Specifies the height of the element',
|
||||
},
|
||||
{
|
||||
displayName: 'Separator',
|
||||
name: 'separator',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'When true, draw a separating line at the top of the element.',
|
||||
},
|
||||
{
|
||||
displayName: 'Spacing',
|
||||
name: 'spacing',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
name: 'Small',
|
||||
value: 'small',
|
||||
},
|
||||
{
|
||||
name: 'Medium',
|
||||
value: 'medium',
|
||||
},
|
||||
{
|
||||
name: 'Large',
|
||||
value: 'large',
|
||||
},
|
||||
{
|
||||
name: 'Extra Large',
|
||||
value: 'extraLarge',
|
||||
},
|
||||
{
|
||||
name: 'Padding',
|
||||
value: 'padding',
|
||||
},
|
||||
],
|
||||
default: 'default',
|
||||
description: 'Controls the amount of spacing between this element and the preceding element',
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'A unique identifier associated with the item',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Visible',
|
||||
name: 'isVisible',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'textBlock',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'If false, this item will be removed from the visual trees',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getInputTextProperties() {
|
||||
return [
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'inputText',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for the value. Used to identify collected input when the Submit action is performed',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Multiline',
|
||||
name: 'isMultiline',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'inputText',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If true, allow multiple lines of input',
|
||||
},
|
||||
{
|
||||
displayName: 'Max Length',
|
||||
name: 'maxLength',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'inputText',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: 1,
|
||||
description: 'Hint of maximum length characters to collect (may be ignored by some clients)',
|
||||
},
|
||||
{
|
||||
displayName: 'Placeholder',
|
||||
name: 'placeholder',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'inputText',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Description of the input desired. Displayed when no text has been input',
|
||||
},
|
||||
{
|
||||
displayName: 'Regex',
|
||||
name: 'regex',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'inputText',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Regular expression indicating the required format of this text input',
|
||||
},
|
||||
{
|
||||
displayName: 'Style',
|
||||
name: 'style',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'inputText',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Text',
|
||||
value: 'text',
|
||||
},
|
||||
{
|
||||
name: 'Tel',
|
||||
value: 'tel',
|
||||
},
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
{
|
||||
name: 'Email',
|
||||
value: 'email',
|
||||
},
|
||||
],
|
||||
default: 'text',
|
||||
description: 'Style hint for text input',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'inputText',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The initial value for this field',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: no-any
|
||||
function removeEmptyProperties(rest: { [key: string]: any }) {
|
||||
return Object.keys(rest)
|
||||
.filter((k) => rest[k] !== '')
|
||||
.reduce((a, k) => ({ ...a, [k]: rest[k] }), {});
|
||||
}
|
||||
|
||||
export function getAutomaticSecret(credentials: ICredentialDataDecryptedObject) {
|
||||
const data = `${credentials.clientId},${credentials.clientSecret}`;
|
||||
return createHash('md5').update(data).digest('hex');
|
||||
}
|
||||
1
packages/nodes-base/nodes/Cisco/Webex/ciscoWebex.svg
Normal file
1
packages/nodes-base/nodes/Cisco/Webex/ciscoWebex.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#7cb342" d="M32.334,13.733c0.092,0.706,0.144,1.424,0.144,2.155c0,9.181-7.463,16.623-16.669,16.623 c-0.733,0-1.453-0.052-2.161-0.144C14.305,33.584,15.895,36.132,19,38c0.699,0.421,3.13,1.851,6,2c7.195,0.374,14.844-7.424,15-15 c0.021-1.024,0.041-4.057-2-7C36.061,15.203,33.379,14.101,32.334,13.733z"/><path fill="#29b6f6" d="M10.661,24c0-7.315,5.947-13.246,13.283-13.246c3.668,0,6.989,1.483,9.392,3.88l4.789-4.776 C34.496,6.239,29.482,4,23.944,4C12.867,4,3.888,12.954,3.888,24c0,5.523,2.245,10.523,5.874,14.142l4.789-4.776 C12.148,30.969,10.661,27.658,10.661,24z"/><path fill="#244b71" d="M38.126,9.858c-1.323-1.319-3.467-1.319-4.789,0c-1.323,1.319-1.323,3.457,0,4.776c0,0,0,0,0,0 c2.404,2.397,3.89,5.708,3.89,9.366c0,7.315-5.947,13.246-13.283,13.246c-3.668,0-6.989-1.483-9.392-3.88l0,0 c-1.323-1.319-3.467-1.319-4.789,0c-1.323,1.319-1.323,3.457,0,4.776C13.392,41.761,18.406,44,23.944,44C35.021,44,44,35.046,44,24 C44,18.477,41.755,13.477,38.126,9.858z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,969 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const meetingOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const meetingFields = [
|
||||
// ----------------------------------------
|
||||
// meeting: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Meeting title. The title can be a maximum of 128 characters long',
|
||||
},
|
||||
{
|
||||
displayName: 'Start',
|
||||
name: 'start',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Date and time for the start of meeting. Acceptable <a href="https://datatracker.ietf.org/doc/html/rfc2445" target="_blank"> format</a>',
|
||||
},
|
||||
{
|
||||
displayName: 'End',
|
||||
name: 'end',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Date and time for the end of meeting. Acceptable <a href="https://datatracker.ietf.org/doc/html/rfc2445" target="_blank"> format</a>',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Field',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Agenda',
|
||||
name: 'agenda',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Meeting agenda. The agenda can be a maximum of 1300 characters long',
|
||||
},
|
||||
{
|
||||
displayName: 'Allow Any User To Be Co-Host',
|
||||
name: 'allowAnyUserToBeCoHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow any attendee with a host account on the target site to become a co-host when joining the meeting`,
|
||||
},
|
||||
{
|
||||
displayName: 'Allow Authenticated Devices',
|
||||
name: 'allowAuthenticatedDevices',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow authenticated video devices in the meeting's organization to start or join the meeting without a prompt`,
|
||||
},
|
||||
{
|
||||
displayName: 'Allow First User To Be Co-Host',
|
||||
name: 'allowFirstUserToBeCoHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow the first attendee of the meeting with a host account on the target site to become a co-host`,
|
||||
},
|
||||
{
|
||||
displayName: 'Auto Accept Request',
|
||||
name: 'autoAcceptRequest',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether or not meeting registration request is accepted automatically',
|
||||
},
|
||||
{
|
||||
displayName: 'Enable Connect Audio Before Host',
|
||||
name: 'enableConnectAudioBeforeHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow any attendee to connect audio in the meeting before the host joins the meeting`,
|
||||
},
|
||||
{
|
||||
displayName: 'Enabled Auto Record Meeting',
|
||||
name: 'enabledAutoRecordMeeting',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not meeting is recorded automatically`,
|
||||
},
|
||||
{
|
||||
displayName: 'Enabled Join Before Host',
|
||||
name: 'enabledJoinBeforeHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow any attendee to join the meeting before the host joins the meeting`,
|
||||
},
|
||||
{
|
||||
displayName: 'Exclude Password',
|
||||
name: 'excludePassword',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to exclude password from the meeting email invitation`,
|
||||
},
|
||||
{
|
||||
displayName: 'Host Email',
|
||||
name: 'hostEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Email address for the meeting host. Can only be set if you're an admin`,
|
||||
},
|
||||
{
|
||||
displayName: 'Integration Tags',
|
||||
name: 'integrationTags',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `External keys created by an integration application in its own domain. They could be Zendesk ticket IDs, Jira IDs, Salesforce Opportunity IDs, etc`,
|
||||
},
|
||||
{
|
||||
displayName: 'Invitees',
|
||||
name: 'inviteesUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'Add Invitee',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Invitee',
|
||||
name: 'inviteeValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Email address of meeting invitee',
|
||||
},
|
||||
{
|
||||
displayName: 'Display Name',
|
||||
name: 'displayName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Display name of meeting invitee',
|
||||
},
|
||||
{
|
||||
displayName: 'Co-Host',
|
||||
name: 'coHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether or not invitee is allowed to be a co-host for the meeting',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Join Before Host Minutes',
|
||||
name: 'joinBeforeHostMinutes',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: '0',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '5',
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
name: '10',
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
name: '15',
|
||||
value: 15,
|
||||
},
|
||||
],
|
||||
default: 0,
|
||||
description: `The number of minutes an attendee can join the meeting before the meeting start time and the host joins`,
|
||||
},
|
||||
{
|
||||
displayName: 'Public Meeting',
|
||||
name: 'publicMeeting',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow the meeting to be listed on the public calendar`,
|
||||
},
|
||||
{
|
||||
displayName: 'Recurrence',
|
||||
name: 'recurrence',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Rule for how the meeting should recur. Acceptable <a href="https://datatracker.ietf.org/doc/html/rfc2445" target="_blank"> format</a>`,
|
||||
},
|
||||
{
|
||||
displayName: 'Required Registration Info',
|
||||
name: 'requireRegistrationInfo',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: 'Require First Name',
|
||||
value: 'requireFirstName',
|
||||
},
|
||||
{
|
||||
name: 'Require Last Name',
|
||||
value: 'requireLastName',
|
||||
},
|
||||
{
|
||||
name: 'Require Email',
|
||||
value: 'requireEmail',
|
||||
},
|
||||
{
|
||||
name: 'Require Job Title',
|
||||
value: 'requireJobTitle',
|
||||
},
|
||||
{
|
||||
name: 'Require Company Name',
|
||||
value: 'requireCompanyName',
|
||||
},
|
||||
{
|
||||
name: 'Require Address 1',
|
||||
value: 'requireAddress1',
|
||||
},
|
||||
{
|
||||
name: 'Require Address 2',
|
||||
value: 'requireAddress2',
|
||||
},
|
||||
{
|
||||
name: 'Require City',
|
||||
value: 'requireCity',
|
||||
},
|
||||
{
|
||||
name: 'Require State',
|
||||
value: 'requireState',
|
||||
},
|
||||
{
|
||||
name: 'Require Zip Code',
|
||||
value: 'requireZipCode',
|
||||
},
|
||||
{
|
||||
name: 'Require Country Region',
|
||||
value: 'requireCountryRegion',
|
||||
},
|
||||
{
|
||||
name: 'Require Work Phone',
|
||||
value: 'requireWorkPhone',
|
||||
},
|
||||
{
|
||||
name: 'Require Fax',
|
||||
value: 'requireFax',
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
description: 'Data required for meeting registration',
|
||||
},
|
||||
{
|
||||
displayName: 'Reminder Time',
|
||||
name: 'reminderTime',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
description: `The number of minutes before the meeting begins, for sending an email reminder to the host`,
|
||||
},
|
||||
{
|
||||
displayName: 'Send Email',
|
||||
name: 'sendEmail',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: `Whether or not to send emails to host and invitees`,
|
||||
},
|
||||
{
|
||||
displayName: 'Site URL',
|
||||
name: 'siteUrl',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSites',
|
||||
},
|
||||
default: '',
|
||||
description: `URL of the Webex site which the meeting is created on. If not specified, the meeting is created on user's preferred site`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// meeting: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Meeting ID',
|
||||
name: 'meetingId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'ID of the meeting',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Host Email',
|
||||
name: 'hostEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Email address for the meeting host. This parameter is only used if the user or application calling the API has the admin-level scopes',
|
||||
},
|
||||
{
|
||||
displayName: 'Send Email',
|
||||
name: 'sendEmail',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether or not to send emails to host and invitees.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// meeting: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Meeting ID',
|
||||
name: 'meetingId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'ID of the meeting',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Host Email',
|
||||
name: 'hostEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Email address for the meeting host. This parameter is only used if the user or application calling the API has the admin-level scopes',
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Meeting password. It's required when the meeting is protected by a password and the current user is not privileged to view it if they are not a host, co-host or invitee of the meeting`,
|
||||
},
|
||||
{
|
||||
displayName: 'Send Email',
|
||||
name: 'sendEmail',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether or not to send emails to host and invitees. It is an optional field and default value is true',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// meeting: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'From',
|
||||
name: 'from',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Start date and time (inclusive) for the meeting. Acceptable <a href="https://datatracker.ietf.org/doc/html/rfc2445" target="_blank"> format</a>',
|
||||
},
|
||||
{
|
||||
displayName: 'Host Email',
|
||||
name: 'hostEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Email address for the meeting host',
|
||||
},
|
||||
{
|
||||
displayName: 'Integration Tag',
|
||||
name: 'integrationTag',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'External tag created by another application, e.g. Zendesk ticket ID or Jira ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit to Current Meetings',
|
||||
name: 'current',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'For meeting series, whether to return just the current meeting or all meetings',
|
||||
},
|
||||
{
|
||||
displayName: 'Meeting Number',
|
||||
name: 'meetingNumber',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Meeting number for the meeting objects being requested',
|
||||
},
|
||||
{
|
||||
displayName: 'Meeting Type',
|
||||
name: 'meetingType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Meeting Series',
|
||||
value: 'meetingSeries',
|
||||
description: 'Master of a scheduled series of meetings which consists of one or more scheduled meeting based on a recurrence rule',
|
||||
},
|
||||
{
|
||||
name: 'Scheduled Meeting',
|
||||
value: 'scheduledMeeting',
|
||||
description: 'Instance from a master meeting series',
|
||||
},
|
||||
{
|
||||
name: 'Meeting',
|
||||
value: 'meeting',
|
||||
description: 'Meeting instance that is actually happening or has happened',
|
||||
},
|
||||
],
|
||||
default: 'meetingSeries',
|
||||
},
|
||||
{
|
||||
displayName: 'Participant Email',
|
||||
name: 'participantEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Email of a person that must be a meeting participant',
|
||||
},
|
||||
{
|
||||
displayName: 'Site URL',
|
||||
name: 'siteUrl',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSites',
|
||||
},
|
||||
default: '',
|
||||
description: 'URL of the Webex site which the API lists meetings from',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Active',
|
||||
value: 'active',
|
||||
},
|
||||
{
|
||||
name: 'Scheduled',
|
||||
value: 'scheduled',
|
||||
},
|
||||
{
|
||||
name: 'Ready',
|
||||
value: 'ready',
|
||||
},
|
||||
{
|
||||
name: 'Lobby',
|
||||
value: 'lobby',
|
||||
},
|
||||
{
|
||||
name: 'In Progress',
|
||||
value: 'inProgress',
|
||||
},
|
||||
{
|
||||
name: 'Ended',
|
||||
value: 'ended',
|
||||
},
|
||||
{
|
||||
name: 'Missed',
|
||||
value: 'missed',
|
||||
},
|
||||
{
|
||||
name: 'Expired',
|
||||
value: 'expired',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'Meeting state for the meeting objects being requested',
|
||||
},
|
||||
{
|
||||
displayName: 'To',
|
||||
name: 'to',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'End date and time (inclusive) for the meeting. Acceptable <a href="https://datatracker.ietf.org/doc/html/rfc2445" target="_blank"> format</a>',
|
||||
},
|
||||
{
|
||||
displayName: 'Weblink',
|
||||
name: 'webLink',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL encoded link to information page for the meeting objects being requested',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// meeting: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Meeting ID',
|
||||
name: 'meetingId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'ID of the meeting',
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meeting',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Field',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Agenda',
|
||||
name: 'agenda',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `The meeting's agenda. Cannot be longer that 1300 characters`,
|
||||
},
|
||||
{
|
||||
displayName: 'Allow Any User To Be Co-Host',
|
||||
name: 'allowAnyUserToBeCoHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow any attendee with a host account on the target site to become a co-host when joining the meeting`,
|
||||
},
|
||||
{
|
||||
displayName: 'Allow Authenticated Devices',
|
||||
name: 'allowAuthenticatedDevices',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow authenticated video devices in the meeting's organization to start or join the meeting without a prompt`,
|
||||
},
|
||||
{
|
||||
displayName: 'Allow First User To Be Co-Host',
|
||||
name: 'allowFirstUserToBeCoHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow the first attendee of the meeting with a host account on the target site to become a co-host`,
|
||||
},
|
||||
{
|
||||
displayName: 'Enable Connect Audio Before Host',
|
||||
name: 'enableConnectAudioBeforeHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow any attendee to connect audio in the meeting before the host joins the meeting`,
|
||||
},
|
||||
{
|
||||
displayName: 'Enabled Auto Record Meeting',
|
||||
name: 'enabledAutoRecordMeeting',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not meeting is recorded automatically`,
|
||||
},
|
||||
{
|
||||
displayName: 'Enabled Join Before Host',
|
||||
name: 'enabledJoinBeforeHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow any attendee to join the meeting before the host joins the meeting`,
|
||||
},
|
||||
{
|
||||
displayName: 'End',
|
||||
name: 'end',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Date and time for the end of meeting. Acceptable <a href="https://datatracker.ietf.org/doc/html/rfc2445" target="_blank"> format</a>',
|
||||
},
|
||||
{
|
||||
displayName: 'Exclude Password',
|
||||
name: 'excludePassword',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to exclude password from the meeting email invitation`,
|
||||
},
|
||||
{
|
||||
displayName: 'Host Email',
|
||||
name: 'hostEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Email address for the meeting host. This attribute should only be set if the user or application calling the API has the admin-level scopes`,
|
||||
},
|
||||
{
|
||||
displayName: 'Invitees',
|
||||
name: 'inviteesUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'Add Invitee',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Invitee',
|
||||
name: 'inviteeValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Email address of meeting invitee',
|
||||
},
|
||||
{
|
||||
displayName: 'Display Name',
|
||||
name: 'displayName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Display name of meeting invitee',
|
||||
},
|
||||
{
|
||||
displayName: 'Co-Host',
|
||||
name: 'coHost',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether or not invitee is allowed to be a co-host for the meeting',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Join Before Host Minutes',
|
||||
name: 'joinBeforeHostMinutes',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: '0',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
name: '5',
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
name: '10',
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
name: '15',
|
||||
value: 15,
|
||||
},
|
||||
],
|
||||
default: 0,
|
||||
description: `The number of minutes an attendee can join the meeting before the meeting start time and the host joins`,
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Meeting password. Must conform to the site's password complexity settings.</br>
|
||||
If not specified, a random password conforming to the site's password rules will be generated automatically`,
|
||||
},
|
||||
{
|
||||
displayName: 'Public Meeting',
|
||||
name: 'publicMeeting',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to allow the meeting to be listed on the public calendar`,
|
||||
},
|
||||
{
|
||||
displayName: 'Recurrence',
|
||||
name: 'recurrence',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Meeting series recurrence rule (conforming with RFC 2445), applying only to meeting series`,
|
||||
},
|
||||
{
|
||||
displayName: 'Required Registration Info',
|
||||
name: 'requireRegistrationInfo',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: 'Require First Name',
|
||||
value: 'requireFirstName',
|
||||
},
|
||||
{
|
||||
name: 'Require Last Name',
|
||||
value: 'requireLastName',
|
||||
},
|
||||
{
|
||||
name: 'Require Email',
|
||||
value: 'requireEmail',
|
||||
},
|
||||
{
|
||||
name: 'Require Job Title',
|
||||
value: 'requireJobTitle',
|
||||
},
|
||||
{
|
||||
name: 'Require Company Name',
|
||||
value: 'requireCompanyName',
|
||||
},
|
||||
{
|
||||
name: 'Require Address 1',
|
||||
value: 'requireAddress1',
|
||||
},
|
||||
{
|
||||
name: 'Require Address 2',
|
||||
value: 'requireAddress2',
|
||||
},
|
||||
{
|
||||
name: 'Require City',
|
||||
value: 'requireCity',
|
||||
},
|
||||
{
|
||||
name: 'Require State',
|
||||
value: 'requireState',
|
||||
},
|
||||
{
|
||||
name: 'Require Zip Code',
|
||||
value: 'requireZipCode',
|
||||
},
|
||||
{
|
||||
name: 'Require Country Region',
|
||||
value: 'requireCountryRegion',
|
||||
},
|
||||
{
|
||||
name: 'Require Work Phone',
|
||||
value: 'requireWorkPhone',
|
||||
},
|
||||
{
|
||||
name: 'Require Fax',
|
||||
value: 'requireFax',
|
||||
},
|
||||
],
|
||||
default: [],
|
||||
description: 'Data required for meeting registration',
|
||||
},
|
||||
{
|
||||
displayName: 'Reminder Time',
|
||||
name: 'reminderTime',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
description: `The number of minutes before the meeting begins, for sending an email reminder to the host`,
|
||||
},
|
||||
{
|
||||
displayName: 'Send Email',
|
||||
name: 'sendEmail',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to send emails to host and invitees. It is an optional field and default value is true`,
|
||||
},
|
||||
{
|
||||
displayName: 'Site URL',
|
||||
name: 'siteUrl',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSites',
|
||||
},
|
||||
default: '',
|
||||
description: `URL of the Webex site which the meeting is created on. If not specified, the meeting is created on user's preferred site`,
|
||||
},
|
||||
{
|
||||
displayName: 'Start',
|
||||
name: 'start',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Date and time for the start of meeting. Acceptable <a href="https://datatracker.ietf.org/doc/html/rfc2445" target="_blank"> format</a>',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Meeting title',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,196 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const meetingTranscriptOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meetingTranscript',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Download',
|
||||
value: 'download',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
],
|
||||
default: 'download',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const meetingTranscriptFields = [
|
||||
// ----------------------------------------
|
||||
// meetingTranscript: download
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Transcript ID',
|
||||
name: 'transcriptId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meetingTranscript',
|
||||
],
|
||||
operation: [
|
||||
'download',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Unique identifier for the meeting transcript',
|
||||
},
|
||||
{
|
||||
displayName: 'Meeting ID',
|
||||
name: 'meetingId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meetingTranscript',
|
||||
],
|
||||
operation: [
|
||||
'download',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Unique identifier for the meeting instance which the transcripts belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meetingTranscript',
|
||||
],
|
||||
operation: [
|
||||
'download',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Option',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Format',
|
||||
name: 'format',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'txt',
|
||||
value: 'txt',
|
||||
},
|
||||
{
|
||||
name: 'vtt',
|
||||
value: 'vtt',
|
||||
},
|
||||
],
|
||||
default: 'vtt',
|
||||
description: 'Format for the downloaded meeting transcript',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// meetingTranscript: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Meeting ID',
|
||||
name: 'meetingId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meetingTranscript',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Unique identifier for the meeting instance which the transcripts belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meetingTranscript',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meetingTranscript',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'meetingTranscript',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Host Email',
|
||||
name: 'hostEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Email address for the meetingTranscript host',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,657 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
getActionInheritedProperties, getInputTextProperties, getTextBlockProperties,
|
||||
} from '../GenericFunctions';
|
||||
|
||||
export const messageOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'Operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const messageFields = [
|
||||
// ----------------------------------------
|
||||
// message: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Destination',
|
||||
name: 'destination',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Room',
|
||||
value: 'room',
|
||||
},
|
||||
{
|
||||
name: 'Person',
|
||||
value: 'person',
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
default: 'room',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Room ID',
|
||||
name: 'roomId',
|
||||
description: ' The room ID',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getRooms',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
destination: [
|
||||
'room',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Specify Person By',
|
||||
name: 'specifyPersonBy',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Email',
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
name: 'ID',
|
||||
value: 'id',
|
||||
},
|
||||
],
|
||||
required: true,
|
||||
default: 'email',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
destination: [
|
||||
'person',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Person ID',
|
||||
name: 'toPersonId',
|
||||
description: 'The person ID',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
specifyPersonBy: [
|
||||
'id',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Person Email',
|
||||
name: 'toPersonEmail',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
specifyPersonBy: [
|
||||
'email',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The message, in plain text',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Field',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Attachments',
|
||||
name: 'attachmentsUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Attachment',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Attachment',
|
||||
name: 'attachmentValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Elements',
|
||||
name: 'elementsUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Element',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Element',
|
||||
name: 'elementValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Text Block',
|
||||
value: 'textBlock',
|
||||
},
|
||||
{
|
||||
name: 'Input Text',
|
||||
value: 'inputText',
|
||||
},
|
||||
],
|
||||
default: 'textBlock',
|
||||
description: '',
|
||||
},
|
||||
...getTextBlockProperties(),
|
||||
...getInputTextProperties(),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Actions',
|
||||
name: 'actionsUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Action',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Action',
|
||||
name: 'actionValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Execute',
|
||||
value: 'execute',
|
||||
},
|
||||
{
|
||||
name: 'Open URL',
|
||||
value: 'openUrl',
|
||||
},
|
||||
{
|
||||
name: 'Submit',
|
||||
value: 'submit',
|
||||
},
|
||||
],
|
||||
default: 'openUrl',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'openUrl',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The URL to open',
|
||||
},
|
||||
{
|
||||
displayName: 'Data',
|
||||
name: 'data',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'submit',
|
||||
'execute',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Any extra data to pass along. These are essentially ‘hidden’ properties',
|
||||
},
|
||||
{
|
||||
displayName: 'Verb',
|
||||
name: 'verb',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'execute',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The card author-defined verb associated with this action',
|
||||
},
|
||||
...getActionInheritedProperties(),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'File',
|
||||
name: 'fileUi',
|
||||
placeholder: 'Add File',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'fileValue',
|
||||
displayName: 'File',
|
||||
values: [
|
||||
{
|
||||
displayName: 'File Location',
|
||||
name: 'fileLocation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'URL',
|
||||
value: 'url',
|
||||
},
|
||||
{
|
||||
name: 'Binary Data',
|
||||
value: 'binaryData',
|
||||
},
|
||||
],
|
||||
default: 'url',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Input Field With File',
|
||||
name: 'binaryPropertyName',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
fileLocation: [
|
||||
'binaryData',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The field in the node input containing the binary file data',
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
fileLocation: [
|
||||
'url',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The public URL of the file',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Markdown',
|
||||
name: 'markdown',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The message in markdown format. When used the text parameter is used to provide alternate text for UI clients that do not support rich text',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// message: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Message ID',
|
||||
name: 'messageId',
|
||||
description: 'ID of the message to delete',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// message: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Message ID',
|
||||
name: 'messageId',
|
||||
description: 'ID of the message to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// message: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Room ID',
|
||||
name: 'roomId',
|
||||
description: 'List messages in a room, by ID',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getRooms',
|
||||
},
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return all results',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'The number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Before',
|
||||
name: 'before',
|
||||
description: 'List messages sent before a date and time',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Before Message',
|
||||
name: 'beforeMessage',
|
||||
description: 'List messages sent before a message, by ID',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Parent Message ID',
|
||||
name: 'parentId',
|
||||
description: 'List messages with a parent, by ID',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Mentioned Person',
|
||||
name: 'mentionedPeople',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `List only messages with certain person mentioned. Enter their ID. You can use 'me' as a shorthand for yourself`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// message: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Message ID',
|
||||
name: 'messageId',
|
||||
description: 'ID of the message to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Is Markdown',
|
||||
name: 'markdown',
|
||||
description: 'Whether the message uses markdown',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
markdown: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The message, in plain text',
|
||||
},
|
||||
{
|
||||
displayName: 'Markdown',
|
||||
name: 'markdownText',
|
||||
description: 'The message, in Markdown format. The maximum message length is 7439 bytes',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
markdown: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './MessageDescription';
|
||||
export * from './MeetingDescription';
|
||||
export * from './MeetingTranscript';
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.elasticsearch",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Development",
|
||||
"Data & Storage"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/elasticsearch"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.elasticsearch/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
@@ -18,7 +20,7 @@ import {
|
||||
} from 'change-case';
|
||||
|
||||
import {
|
||||
facebookApiRequest,
|
||||
facebookApiRequest, getAllFields, getFields,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
@@ -61,6 +63,14 @@ export class FacebookTrigger implements INodeType {
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'APP ID',
|
||||
name: 'appId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Facebook APP ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Object',
|
||||
name: 'object',
|
||||
@@ -126,13 +136,20 @@ export class FacebookTrigger implements INodeType {
|
||||
default: 'user',
|
||||
description: 'The object to subscribe to',
|
||||
},
|
||||
//https://developers.facebook.com/docs/graph-api/webhooks/reference/page
|
||||
{
|
||||
displayName: 'App ID',
|
||||
name: 'appId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Facebook APP ID',
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getObjectFields',
|
||||
loadOptionsDependsOn: [
|
||||
'object',
|
||||
],
|
||||
},
|
||||
required: false,
|
||||
default: [],
|
||||
description: 'The set of fields in this object that are subscribed to',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
@@ -153,6 +170,18 @@ export class FacebookTrigger implements INodeType {
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available organizations to display them to user so that he can
|
||||
// select them easily
|
||||
async getObjectFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const object = this.getCurrentNodeParameter('object') as string;
|
||||
return getFields(object) as INodePropertyOptions[];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore (because of request)
|
||||
webhookMethods = {
|
||||
default: {
|
||||
@@ -175,12 +204,14 @@ export class FacebookTrigger implements INodeType {
|
||||
const webhookUrl = this.getNodeWebhookUrl('default') as string;
|
||||
const object = this.getNodeParameter('object') as string;
|
||||
const appId = this.getNodeParameter('appId') as string;
|
||||
const fields = this.getNodeParameter('fields') as string[];
|
||||
const options = this.getNodeParameter('options') as IDataObject;
|
||||
|
||||
const body = {
|
||||
object: snakeCase(object),
|
||||
callback_url: webhookUrl,
|
||||
verify_token: uuid(),
|
||||
fields: (fields.includes('*')) ? getAllFields(object) : fields,
|
||||
} as IDataObject;
|
||||
|
||||
if (options.includeValues !== undefined) {
|
||||
|
||||
@@ -14,6 +14,10 @@ import {
|
||||
IDataObject, NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
capitalCase,
|
||||
} from 'change-case';
|
||||
|
||||
export async function facebookApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
let credentials;
|
||||
@@ -34,7 +38,7 @@ export async function facebookApiRequest(this: IHookFunctions | IExecuteFunction
|
||||
qs,
|
||||
body,
|
||||
gzip: true,
|
||||
uri: uri ||`https://graph.facebook.com/v8.0${resource}`,
|
||||
uri: uri || `https://graph.facebook.com/v8.0${resource}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
@@ -44,3 +48,506 @@ export async function facebookApiRequest(this: IHookFunctions | IExecuteFunction
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
export function getFields(object: string) {
|
||||
const data = {
|
||||
'adAccount': [
|
||||
{
|
||||
value: 'in_process_ad_objects',
|
||||
},
|
||||
{
|
||||
value: 'with_issues_ad_objects',
|
||||
},
|
||||
],
|
||||
'page': [
|
||||
{
|
||||
value: 'affiliation',
|
||||
description: `Describes changes to a page's Affliation profile field`,
|
||||
},
|
||||
{
|
||||
value: 'attire',
|
||||
description: `Describes changes to a page's Attire profile field`,
|
||||
},
|
||||
{
|
||||
value: 'awards',
|
||||
description: `Describes changes to a page's Awards profile field`,
|
||||
},
|
||||
{
|
||||
value: 'bio',
|
||||
description: `Describes changes to a page's Biography profile field`,
|
||||
},
|
||||
{
|
||||
value: 'birthday',
|
||||
description: `Describes changes to a page's Birthday profile field`,
|
||||
},
|
||||
{
|
||||
value: 'category',
|
||||
description: `Describes changes to a page's Birthday profile field`,
|
||||
},
|
||||
{
|
||||
value: 'company_overview',
|
||||
description: `Describes changes to a page's Company Overview profile field`,
|
||||
},
|
||||
{
|
||||
value: 'culinary_team',
|
||||
description: `Describes changes to a page's Culinary Team profile field`,
|
||||
},
|
||||
{
|
||||
value: 'current_location',
|
||||
description: `Describes changes to a page's Current Location profile field`,
|
||||
},
|
||||
{
|
||||
value: 'description',
|
||||
description: `Describes changes to a page's Story Description profile field`,
|
||||
},
|
||||
{
|
||||
value: 'email',
|
||||
description: `Describes changes to a page's Email profile field`,
|
||||
},
|
||||
{
|
||||
value: 'feed',
|
||||
description: `Describes nearly all changes to a Page's feed, such as Posts, shares, likes, etc`,
|
||||
},
|
||||
{
|
||||
value: 'founded',
|
||||
description: `Describes changes to a page's Founded profile field. This is different from the Start Date field`,
|
||||
},
|
||||
{
|
||||
value: 'general_info',
|
||||
description: `Describes changes to a page's General Information profile field`,
|
||||
},
|
||||
{
|
||||
value: 'general_manager',
|
||||
description: `Describes changes to a page's General Information profile field`,
|
||||
},
|
||||
{
|
||||
value: 'hometown',
|
||||
description: `Describes changes to a page's Homewtown profile field`,
|
||||
},
|
||||
{
|
||||
value: 'hours',
|
||||
description: `Describes changes to a page's Hours profile field`,
|
||||
},
|
||||
{
|
||||
value: 'leadgen',
|
||||
description: `Describes changes to a page's leadgen settings`,
|
||||
},
|
||||
{
|
||||
value: 'live_videos',
|
||||
description: `Describes changes to a page's live video status`,
|
||||
},
|
||||
{
|
||||
value: 'location',
|
||||
description: `Describes changes to a page's Location profile field`,
|
||||
},
|
||||
{
|
||||
value: 'members',
|
||||
description: `Describes changes to a page's Members profile field`,
|
||||
},
|
||||
{
|
||||
value: 'mention',
|
||||
description: `Describes new mentions of a page, including mentions in comments, posts, etc`,
|
||||
},
|
||||
{
|
||||
value: 'merchant_review',
|
||||
description: `Describes changes to a page's merchant review settings`,
|
||||
},
|
||||
{
|
||||
value: 'mission',
|
||||
description: `Describes changes to a page's Mission profile field`,
|
||||
},
|
||||
{
|
||||
value: 'name',
|
||||
description: `Describes changes to a page's Name profile field.`,
|
||||
},
|
||||
{
|
||||
value: 'page_about_story',
|
||||
},
|
||||
{
|
||||
value: 'page_change_proposal',
|
||||
description: `Data for page change proposal.`,
|
||||
},
|
||||
{
|
||||
value: 'page_upcoming_change',
|
||||
description: `Webhooks data for page upcoming changes`,
|
||||
},
|
||||
{
|
||||
value: 'parking',
|
||||
description: `Describes changes to a page's Parking profile field`,
|
||||
},
|
||||
{
|
||||
value: 'payment_options',
|
||||
description: `Describes change to a page's Payment profile field`,
|
||||
},
|
||||
{
|
||||
value: 'personal_info',
|
||||
description: `Describes changes to a page's Personal Information profile field.`,
|
||||
},
|
||||
{
|
||||
value: 'personal_interests',
|
||||
description: `Describes changes to a page's Personal Interests profile field.`,
|
||||
},
|
||||
{
|
||||
value: 'phone',
|
||||
description: `Describes changes to a page's Phone profile field`,
|
||||
},
|
||||
{
|
||||
value: 'picture',
|
||||
description: `Describes changes to a page's profile picture`,
|
||||
},
|
||||
{
|
||||
value: 'price_range',
|
||||
description: `Describes changes to a page's Price Range profile field`,
|
||||
},
|
||||
{
|
||||
value: 'product_review',
|
||||
description: `Describes changes to a page's product review settings`,
|
||||
},
|
||||
{
|
||||
value: 'products',
|
||||
description: `Describes changes to a page's Products profile field`,
|
||||
},
|
||||
{
|
||||
value: 'public_transit',
|
||||
description: `Describes changes to a page's Public Transit profile field`,
|
||||
},
|
||||
{
|
||||
value: 'ratings',
|
||||
description: `Describes changes to a page's ratings, including new ratings or a user's comments or reactions on a rating`,
|
||||
},
|
||||
{
|
||||
value: 'videos',
|
||||
description: `Describes changes to the encoding status of a video on a page`,
|
||||
},
|
||||
{
|
||||
value: 'website',
|
||||
description: `Describes changes to a page's Website profile field`,
|
||||
},
|
||||
],
|
||||
'application': [
|
||||
{
|
||||
value: 'ad_account',
|
||||
},
|
||||
{
|
||||
value: 'ads_rules_engine',
|
||||
},
|
||||
{
|
||||
value: 'async_requests',
|
||||
},
|
||||
{
|
||||
value: 'async_sessions',
|
||||
},
|
||||
{
|
||||
value: 'group_install',
|
||||
},
|
||||
{
|
||||
value: 'oe_reseller_onboarding_request_created',
|
||||
},
|
||||
{
|
||||
value: 'plugin_comment',
|
||||
},
|
||||
{
|
||||
value: 'plugin_comment_reply',
|
||||
},
|
||||
{
|
||||
value: 'plugin_comment_reply',
|
||||
},
|
||||
],
|
||||
'certificateTransparency': [
|
||||
{
|
||||
value: 'certificate',
|
||||
},
|
||||
{
|
||||
value: 'phishing',
|
||||
},
|
||||
],
|
||||
'instagram': [
|
||||
{
|
||||
value: 'comments',
|
||||
description: 'Notifies you when an Instagram User comments on a media object that you own',
|
||||
},
|
||||
{
|
||||
value: 'messaging_handover',
|
||||
},
|
||||
{
|
||||
value: 'mentions',
|
||||
description: 'Notifies you when an Instagram User @mentions you in a comment or caption on a media object that you do not own',
|
||||
},
|
||||
{
|
||||
value: 'messages',
|
||||
},
|
||||
{
|
||||
value: 'messaging_seen',
|
||||
},
|
||||
{
|
||||
value: 'standby',
|
||||
},
|
||||
{
|
||||
value: 'story_insights',
|
||||
},
|
||||
],
|
||||
'permissions': [
|
||||
{
|
||||
value: 'bookmarked',
|
||||
description: 'Whether the user has added or removed the app bookmark',
|
||||
},
|
||||
{
|
||||
value: 'connected',
|
||||
description: 'Whether the user is connected or disconnected from the app',
|
||||
},
|
||||
{
|
||||
value: 'user_birthday',
|
||||
},
|
||||
{
|
||||
value: 'user_hometown',
|
||||
},
|
||||
{
|
||||
value: 'user_location',
|
||||
},
|
||||
{
|
||||
value: 'user_likes',
|
||||
},
|
||||
{
|
||||
value: 'user_managed_groups',
|
||||
},
|
||||
{
|
||||
value: 'user_events',
|
||||
},
|
||||
{
|
||||
value: 'user_photos',
|
||||
},
|
||||
{
|
||||
value: 'user_videos',
|
||||
},
|
||||
{
|
||||
value: 'user_friends',
|
||||
},
|
||||
{
|
||||
value: 'user_posts',
|
||||
},
|
||||
{
|
||||
value: 'user_gender',
|
||||
},
|
||||
{
|
||||
value: 'user_link',
|
||||
},
|
||||
{
|
||||
value: 'user_age_range',
|
||||
},
|
||||
{
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
value: 'read_insights',
|
||||
},
|
||||
{
|
||||
value: 'read_page_mailboxes',
|
||||
},
|
||||
{
|
||||
value: 'pages_show_list',
|
||||
},
|
||||
{
|
||||
value: 'pages_manage_cta',
|
||||
},
|
||||
{
|
||||
value: 'business_management',
|
||||
},
|
||||
{
|
||||
value: 'pages_messaging',
|
||||
},
|
||||
{
|
||||
value: 'pages_messaging_phone_number',
|
||||
},
|
||||
{
|
||||
value: 'pages_messaging_subscriptions',
|
||||
},
|
||||
{
|
||||
value: 'read_audience_network_insights',
|
||||
},
|
||||
{
|
||||
value: 'pages_manage_instant_articles',
|
||||
},
|
||||
{
|
||||
value: 'publish_video',
|
||||
},
|
||||
{
|
||||
value: 'openid',
|
||||
},
|
||||
{
|
||||
value: 'catalog_management',
|
||||
},
|
||||
{
|
||||
value: 'gaming_user_locale',
|
||||
},
|
||||
{
|
||||
value: 'groups_show_list',
|
||||
},
|
||||
{
|
||||
value: 'instagram_basic',
|
||||
},
|
||||
{
|
||||
value: 'instagram_manage_comments',
|
||||
},
|
||||
{
|
||||
value: 'instagram_manage_insights',
|
||||
},
|
||||
{
|
||||
value: 'instagram_content_publish',
|
||||
},
|
||||
{
|
||||
value: 'publish_to_groups',
|
||||
},
|
||||
{
|
||||
value: 'groups_access_member_info',
|
||||
},
|
||||
{
|
||||
value: 'leads_retrieval',
|
||||
},
|
||||
{
|
||||
value: 'whatsapp_business_management',
|
||||
},
|
||||
{
|
||||
value: 'instagram_manage_messages',
|
||||
},
|
||||
{
|
||||
value: 'attribution_read',
|
||||
},
|
||||
{
|
||||
value: 'page_events',
|
||||
},
|
||||
{
|
||||
value: 'ads_management',
|
||||
},
|
||||
{
|
||||
value: 'ads_read',
|
||||
},
|
||||
{
|
||||
value: 'pages_read_engagement',
|
||||
},
|
||||
{
|
||||
value: 'pages_manage_metadata',
|
||||
},
|
||||
{
|
||||
value: 'pages_read_user_content',
|
||||
},
|
||||
{
|
||||
value: 'pages_manage_ads',
|
||||
},
|
||||
{
|
||||
value: 'pages_manage_posts',
|
||||
},
|
||||
{
|
||||
value: 'pages_manage_engagement',
|
||||
},
|
||||
{
|
||||
value: 'public_search',
|
||||
},
|
||||
{
|
||||
value: 'social_ads',
|
||||
},
|
||||
],
|
||||
'users': [
|
||||
{
|
||||
value: 'about',
|
||||
},
|
||||
{
|
||||
value: 'birthday',
|
||||
},
|
||||
{
|
||||
value: 'books',
|
||||
},
|
||||
{
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
value: 'feed',
|
||||
},
|
||||
{
|
||||
value: 'first_name',
|
||||
},
|
||||
{
|
||||
value: 'friends',
|
||||
},
|
||||
{
|
||||
value: 'gender',
|
||||
},
|
||||
{
|
||||
value: 'hometown',
|
||||
},
|
||||
{
|
||||
value: 'last_name',
|
||||
},
|
||||
{
|
||||
value: 'likes',
|
||||
},
|
||||
{
|
||||
value: 'live_videos',
|
||||
},
|
||||
{
|
||||
value: 'location',
|
||||
},
|
||||
{
|
||||
value: 'music',
|
||||
},
|
||||
{
|
||||
value: 'name',
|
||||
},
|
||||
{
|
||||
value: 'photos',
|
||||
},
|
||||
{
|
||||
value: 'pic_big_https',
|
||||
},
|
||||
{
|
||||
value: 'pic_https',
|
||||
},
|
||||
{
|
||||
value: 'pic_small_https',
|
||||
},
|
||||
{
|
||||
value: 'pic_square_https',
|
||||
},
|
||||
{
|
||||
value: 'platform',
|
||||
},
|
||||
{
|
||||
value: 'quotes',
|
||||
},
|
||||
{
|
||||
value: 'status',
|
||||
},
|
||||
{
|
||||
value: 'television',
|
||||
},
|
||||
{
|
||||
value: 'videos',
|
||||
},
|
||||
],
|
||||
'whatsappBusinessAccount': [
|
||||
{
|
||||
value: 'message_template_status_update',
|
||||
},
|
||||
{
|
||||
value: 'phone_number_name_update',
|
||||
},
|
||||
{
|
||||
value: 'phone_number_quality_update',
|
||||
},
|
||||
{
|
||||
value: 'account_review_update',
|
||||
},
|
||||
{
|
||||
value: 'account_update',
|
||||
},
|
||||
],
|
||||
// tslint:disable-next-line: no-any
|
||||
} as { [key: string]: any };
|
||||
|
||||
return [{ name: '*', value: '*' }].concat(data[object as string] || [])
|
||||
.map((fieldObject: IDataObject) =>
|
||||
({ ...fieldObject, name: (fieldObject.value !== '*') ? capitalCase(fieldObject.value as string) : fieldObject.value }));
|
||||
}
|
||||
|
||||
export function getAllFields(object: string) {
|
||||
return getFields(object).filter((field: IDataObject) => field.value !== '*').map((field: IDataObject) => field.value);
|
||||
}
|
||||
996
packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts
Normal file
996
packages/nodes-base/nodes/FreshworksCrm/FreshworksCrm.node.ts
Normal file
@@ -0,0 +1,996 @@
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
adjustAccounts,
|
||||
adjustAttendees,
|
||||
freshworksCrmApiRequest,
|
||||
getAllItemsViewId,
|
||||
handleListing,
|
||||
loadResource,
|
||||
throwOnEmptyFilter,
|
||||
throwOnEmptyUpdate,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
accountFields,
|
||||
accountOperations,
|
||||
appointmentFields,
|
||||
appointmentOperations,
|
||||
contactFields,
|
||||
contactOperations,
|
||||
dealFields,
|
||||
dealOperations,
|
||||
noteFields,
|
||||
noteOperations,
|
||||
salesActivityFields,
|
||||
salesActivityOperations,
|
||||
taskFields,
|
||||
taskOperations,
|
||||
} from './descriptions';
|
||||
|
||||
import {
|
||||
FreshworksConfigResponse,
|
||||
LoadedCurrency,
|
||||
LoadedUser,
|
||||
LoadOption,
|
||||
} from './types';
|
||||
|
||||
import {
|
||||
tz,
|
||||
} from 'moment-timezone';
|
||||
|
||||
export class FreshworksCrm implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Freshworks CRM',
|
||||
name: 'freshworksCrm',
|
||||
icon: 'file:freshworksCrm.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume the Freshworks CRM API',
|
||||
defaults: {
|
||||
name: 'Freshworks CRM',
|
||||
color: '#ffa800',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'freshworksCrmApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Account',
|
||||
value: 'account',
|
||||
},
|
||||
{
|
||||
name: 'Appointment',
|
||||
value: 'appointment',
|
||||
},
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'deal',
|
||||
},
|
||||
{
|
||||
name: 'Note',
|
||||
value: 'note',
|
||||
},
|
||||
{
|
||||
name: 'Sales Activity',
|
||||
value: 'salesActivity',
|
||||
},
|
||||
{
|
||||
name: 'Task',
|
||||
value: 'task',
|
||||
},
|
||||
],
|
||||
default: 'account',
|
||||
},
|
||||
...accountOperations,
|
||||
...accountFields,
|
||||
...appointmentOperations,
|
||||
...appointmentFields,
|
||||
...contactOperations,
|
||||
...contactFields,
|
||||
...dealOperations,
|
||||
...dealFields,
|
||||
...noteOperations,
|
||||
...noteFields,
|
||||
...salesActivityOperations,
|
||||
...salesActivityFields,
|
||||
...taskOperations,
|
||||
...taskFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getAccounts(this: ILoadOptionsFunctions) {
|
||||
const viewId = await getAllItemsViewId.call(this, { fromLoadOptions: true });
|
||||
const responseData = await handleListing.call(this, 'GET', `/sales_accounts/view/${viewId}`);
|
||||
|
||||
return responseData.map(({ name, id }) => ({ name, value: id })) as LoadOption[];
|
||||
},
|
||||
|
||||
async getAccountViews(this: ILoadOptionsFunctions) {
|
||||
const responseData = await handleListing.call(this, 'GET', '/sales_accounts/filters');
|
||||
return responseData.map(({ name, id }) => ({ name, value: id })) as LoadOption[];
|
||||
},
|
||||
|
||||
async getBusinessTypes(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'business_types');
|
||||
},
|
||||
|
||||
async getCampaigns(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'campaigns');
|
||||
},
|
||||
|
||||
async getContactStatuses(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'contact_statuses');
|
||||
},
|
||||
|
||||
async getContactViews(this: ILoadOptionsFunctions) {
|
||||
const responseData = await handleListing.call(this, 'GET', '/contacts/filters');
|
||||
|
||||
return responseData.map(({ name, id }) => ({ name, value: id })) as LoadOption[];
|
||||
},
|
||||
|
||||
async getCurrencies(this: ILoadOptionsFunctions) {
|
||||
const response = await freshworksCrmApiRequest.call(
|
||||
this, 'GET', '/selector/currencies',
|
||||
) as FreshworksConfigResponse<LoadedCurrency>;
|
||||
|
||||
const key = Object.keys(response)[0];
|
||||
|
||||
return response[key].map(({ currency_code, id }) => ({ name: currency_code, value: id }));
|
||||
},
|
||||
|
||||
async getDealPaymentStatuses(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'deal_payment_statuses');
|
||||
},
|
||||
|
||||
async getDealPipelines(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'deal_pipelines');
|
||||
},
|
||||
|
||||
async getDealProducts(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'deal_products');
|
||||
},
|
||||
|
||||
async getDealReasons(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'deal_reasons');
|
||||
},
|
||||
|
||||
async getDealStages(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'deal_stages');
|
||||
},
|
||||
|
||||
async getDealTypes(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'deal_types');
|
||||
},
|
||||
|
||||
async getDealViews(this: ILoadOptionsFunctions) {
|
||||
const responseData = await handleListing.call(this, 'GET', '/deals/filters');
|
||||
|
||||
return responseData.map(({ name, id }) => ({ name, value: id })) as LoadOption[];
|
||||
},
|
||||
|
||||
async getIndustryTypes(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'industry_types');
|
||||
},
|
||||
|
||||
async getLifecycleStages(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'lifecycle_stages');
|
||||
},
|
||||
|
||||
async getOutcomes(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'sales_activity_outcomes');
|
||||
},
|
||||
|
||||
async getSalesActivityTypes(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'sales_activity_types');
|
||||
},
|
||||
|
||||
async getTerritories(this: ILoadOptionsFunctions) {
|
||||
return await loadResource.call(this, 'territories');
|
||||
},
|
||||
|
||||
async getUsers(this: ILoadOptionsFunctions) { // for attendees, owners, and creators
|
||||
const response = await freshworksCrmApiRequest.call(
|
||||
this, 'GET', `/selector/owners`,
|
||||
) as FreshworksConfigResponse<LoadedUser>;
|
||||
|
||||
const key = Object.keys(response)[0];
|
||||
|
||||
return response[key].map(
|
||||
({ display_name, id }) => ({ name: display_name, value: id }),
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
const defaultTimezone = this.getTimezone();
|
||||
|
||||
let responseData;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
try {
|
||||
|
||||
if (resource === 'account') {
|
||||
|
||||
// **********************************************************************
|
||||
// account
|
||||
// **********************************************************************
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#accounts
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// account: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#create_account
|
||||
|
||||
const body = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
} as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, additionalFields);
|
||||
}
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'POST', '/sales_accounts', body);
|
||||
responseData = responseData.sales_account;
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// account: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#delete_account
|
||||
|
||||
const accountId = this.getNodeParameter('accountId', i);
|
||||
|
||||
const endpoint = `/sales_accounts/${accountId}`;
|
||||
await freshworksCrmApiRequest.call(this, 'DELETE', endpoint);
|
||||
responseData = { success: true };
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// account: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#view_account
|
||||
|
||||
const accountId = this.getNodeParameter('accountId', i);
|
||||
|
||||
const endpoint = `/sales_accounts/${accountId}`;
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'GET', endpoint);
|
||||
responseData = responseData.sales_account;
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// account: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#list_all_accounts
|
||||
|
||||
const view = this.getNodeParameter('view', i) as string;
|
||||
|
||||
responseData = await handleListing.call(this, 'GET', `/sales_accounts/view/${view}`);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// account: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#update_a_account
|
||||
|
||||
const body = {} as IDataObject;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, updateFields);
|
||||
} else {
|
||||
throwOnEmptyUpdate.call(this, resource);
|
||||
}
|
||||
|
||||
const accountId = this.getNodeParameter('accountId', i);
|
||||
|
||||
const endpoint = `/sales_accounts/${accountId}`;
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'PUT', endpoint, body);
|
||||
responseData = responseData.sales_account;
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'appointment') {
|
||||
|
||||
// **********************************************************************
|
||||
// appointment
|
||||
// **********************************************************************
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#appointments
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#create_appointment
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject & {
|
||||
time_zone: string;
|
||||
is_allday: boolean;
|
||||
};
|
||||
|
||||
const startDate = this.getNodeParameter('fromDate', i) as string;
|
||||
const endDate = this.getNodeParameter('endDate', i) as string;
|
||||
const attendees = this.getNodeParameter('attendees.attendee', i, []) as [{ type: string, contactId: string, userId: string }];
|
||||
|
||||
const timezone = additionalFields.time_zone ?? defaultTimezone;
|
||||
|
||||
let allDay = false;
|
||||
|
||||
if (additionalFields.is_allday) {
|
||||
allDay = additionalFields.is_allday as boolean;
|
||||
}
|
||||
|
||||
const start = tz(startDate, timezone);
|
||||
const end = tz(endDate, timezone);
|
||||
|
||||
const body = {
|
||||
title: this.getNodeParameter('title', i),
|
||||
from_date: start.format(),
|
||||
end_date: (allDay) ? start.format() : end.format(),
|
||||
} as IDataObject;
|
||||
|
||||
Object.assign(body, additionalFields);
|
||||
|
||||
if (attendees.length) {
|
||||
body['appointment_attendees_attributes'] = adjustAttendees(attendees);
|
||||
}
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'POST', '/appointments', body);
|
||||
responseData = responseData.appointment;
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#delete_a_appointment
|
||||
|
||||
const appointmentId = this.getNodeParameter('appointmentId', i);
|
||||
|
||||
const endpoint = `/appointments/${appointmentId}`;
|
||||
await freshworksCrmApiRequest.call(this, 'DELETE', endpoint);
|
||||
responseData = { success: true };
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#view_a_appointment
|
||||
|
||||
const appointmentId = this.getNodeParameter('appointmentId', i);
|
||||
|
||||
const endpoint = `/appointments/${appointmentId}`;
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'GET', endpoint);
|
||||
responseData = responseData.appointment;
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#list_all_appointments
|
||||
|
||||
const { filter, include } = this.getNodeParameter('filters', i) as {
|
||||
filter: string;
|
||||
include: string[];
|
||||
};
|
||||
|
||||
const qs: IDataObject = {};
|
||||
|
||||
if (filter) {
|
||||
qs.filter = filter;
|
||||
}
|
||||
|
||||
if (include) {
|
||||
qs.include = include;
|
||||
}
|
||||
responseData = await handleListing.call(this, 'GET', '/appointments', {}, qs);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#update_a_appointment
|
||||
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject & {
|
||||
from_date: string;
|
||||
end_date: string;
|
||||
time_zone: string;
|
||||
};
|
||||
|
||||
const attendees = this.getNodeParameter('updateFields.attendees.attendee', i, []) as [{ type: string, contactId: string, userId: string }];
|
||||
|
||||
if (!Object.keys(updateFields).length) {
|
||||
throwOnEmptyUpdate.call(this, resource);
|
||||
}
|
||||
|
||||
const body = {} as IDataObject;
|
||||
const { from_date, end_date, ...rest } = updateFields;
|
||||
|
||||
const timezone = rest.time_zone ?? defaultTimezone;
|
||||
|
||||
if (from_date) {
|
||||
body.from_date = tz(from_date, timezone).format();
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
body.end_date = tz(end_date, timezone).format();
|
||||
}
|
||||
|
||||
Object.assign(body, rest);
|
||||
|
||||
if (attendees.length) {
|
||||
body['appointment_attendees_attributes'] = adjustAttendees(attendees);
|
||||
delete body.attendees;
|
||||
}
|
||||
|
||||
const appointmentId = this.getNodeParameter('appointmentId', i);
|
||||
|
||||
const endpoint = `/appointments/${appointmentId}`;
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'PUT', endpoint, body);
|
||||
responseData = responseData.appointment;
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'contact') {
|
||||
|
||||
// **********************************************************************
|
||||
// contact
|
||||
// **********************************************************************
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#contacts
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#create_contact
|
||||
|
||||
const body = {
|
||||
first_name: this.getNodeParameter('firstName', i),
|
||||
last_name: this.getNodeParameter('lastName', i),
|
||||
emails: this.getNodeParameter('emails', i),
|
||||
} as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, adjustAccounts(additionalFields));
|
||||
}
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'POST', '/contacts', body);
|
||||
responseData = responseData.contact;
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#delete_a_contact
|
||||
|
||||
const contactId = this.getNodeParameter('contactId', i);
|
||||
|
||||
const endpoint = `/contacts/${contactId}`;
|
||||
await freshworksCrmApiRequest.call(this, 'DELETE', endpoint);
|
||||
responseData = { success: true };
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#view_a_contact
|
||||
|
||||
const contactId = this.getNodeParameter('contactId', i);
|
||||
|
||||
const endpoint = `/contacts/${contactId}`;
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'GET', endpoint);
|
||||
responseData = responseData.contact;
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#list_all_contacts
|
||||
|
||||
const view = this.getNodeParameter('view', i) as string;
|
||||
|
||||
responseData = await handleListing.call(this, 'GET', `/contacts/view/${view}`);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#update_a_contact
|
||||
|
||||
const body = {} as IDataObject;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, adjustAccounts(updateFields));
|
||||
} else {
|
||||
throwOnEmptyUpdate.call(this, resource);
|
||||
}
|
||||
|
||||
const contactId = this.getNodeParameter('contactId', i);
|
||||
|
||||
const endpoint = `/contacts/${contactId}`;
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'PUT', endpoint, body);
|
||||
responseData = responseData.contact;
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'deal') {
|
||||
|
||||
// **********************************************************************
|
||||
// deal
|
||||
// **********************************************************************
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#deals
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#create_deal
|
||||
|
||||
const body = {
|
||||
name: this.getNodeParameter('name', i),
|
||||
amount: this.getNodeParameter('amount', i),
|
||||
} as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, adjustAccounts(additionalFields));
|
||||
}
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'POST', '/deals', body);
|
||||
responseData = responseData.deal;
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#delete_a_deal
|
||||
|
||||
const dealId = this.getNodeParameter('dealId', i);
|
||||
|
||||
await freshworksCrmApiRequest.call(this, 'DELETE', `/deals/${dealId}`);
|
||||
responseData = { success: true };
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#view_a_deal
|
||||
|
||||
const dealId = this.getNodeParameter('dealId', i);
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'GET', `/deals/${dealId}`);
|
||||
responseData = responseData.deal;
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#list_all_deals
|
||||
|
||||
const view = this.getNodeParameter('view', i) as string;
|
||||
|
||||
responseData = await handleListing.call(this, 'GET', `/deals/view/${view}`);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#update_a_deal
|
||||
|
||||
const body = {} as IDataObject;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, adjustAccounts(updateFields));
|
||||
} else {
|
||||
throwOnEmptyUpdate.call(this, resource);
|
||||
}
|
||||
|
||||
const dealId = this.getNodeParameter('dealId', i);
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'PUT', `/deals/${dealId}`, body);
|
||||
responseData = responseData.deal;
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'note') {
|
||||
|
||||
// **********************************************************************
|
||||
// note
|
||||
// **********************************************************************
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#notes
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// note: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#create_note
|
||||
|
||||
const body = {
|
||||
description: this.getNodeParameter('description', i),
|
||||
targetable_id: this.getNodeParameter('targetable_id', i),
|
||||
targetable_type: this.getNodeParameter('targetableType', i),
|
||||
} as IDataObject;
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'POST', '/notes', body);
|
||||
responseData = responseData.note;
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// note: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#delete_a_note
|
||||
|
||||
const noteId = this.getNodeParameter('noteId', i);
|
||||
|
||||
await freshworksCrmApiRequest.call(this, 'DELETE', `/notes/${noteId}`);
|
||||
responseData = { success: true };
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// note: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#update_a_note
|
||||
|
||||
const body = {} as IDataObject;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(updateFields).length) {
|
||||
Object.assign(body, updateFields);
|
||||
} else {
|
||||
throwOnEmptyUpdate.call(this, resource);
|
||||
}
|
||||
|
||||
const noteId = this.getNodeParameter('noteId', i);
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'PUT', `/notes/${noteId}`, body);
|
||||
responseData = responseData.note;
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'salesActivity') {
|
||||
|
||||
// **********************************************************************
|
||||
// salesActivity
|
||||
// **********************************************************************
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#sales-activities
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#create_sales_activity
|
||||
|
||||
const startDate = this.getNodeParameter('from_date', i) as string;
|
||||
const endDate = this.getNodeParameter('end_date', i) as string;
|
||||
|
||||
const body = {
|
||||
sales_activity_type_id: this.getNodeParameter('sales_activity_type_id', i),
|
||||
title: this.getNodeParameter('title', i),
|
||||
owner_id: this.getNodeParameter('ownerId', i),
|
||||
start_date: tz(startDate, defaultTimezone).format(),
|
||||
end_date: tz(endDate, defaultTimezone).format(),
|
||||
targetable_type: this.getNodeParameter('targetableType', i),
|
||||
targetable_id: this.getNodeParameter('targetable_id', i),
|
||||
} as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, additionalFields);
|
||||
}
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'POST', '/sales_activities', { sales_activity: body });
|
||||
responseData = responseData.sales_activity;
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#delete_a_sales_activity
|
||||
|
||||
const salesActivityId = this.getNodeParameter('salesActivityId', i);
|
||||
|
||||
const endpoint = `/sales_activities/${salesActivityId}`;
|
||||
await freshworksCrmApiRequest.call(this, 'DELETE', endpoint);
|
||||
responseData = { success: true };
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#view_a_sales_activity
|
||||
|
||||
const salesActivityId = this.getNodeParameter('salesActivityId', i);
|
||||
|
||||
const endpoint = `/sales_activities/${salesActivityId}`;
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'GET', endpoint);
|
||||
responseData = responseData.sales_activity;
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#list_all_sales_activities
|
||||
|
||||
responseData = await handleListing.call(this, 'GET', '/sales_activities');
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#update_a_sales_activity
|
||||
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject & {
|
||||
from_date: string;
|
||||
end_date: string;
|
||||
time_zone: string;
|
||||
};
|
||||
|
||||
if (!Object.keys(updateFields).length) {
|
||||
throwOnEmptyUpdate.call(this, resource);
|
||||
}
|
||||
|
||||
const body = {} as IDataObject;
|
||||
const { from_date, end_date, ...rest } = updateFields;
|
||||
|
||||
if (from_date) {
|
||||
body.from_date = tz(from_date, defaultTimezone).format();
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
body.end_date = tz(end_date, defaultTimezone).format();
|
||||
}
|
||||
|
||||
if (Object.keys(rest).length) {
|
||||
Object.assign(body, rest);
|
||||
}
|
||||
|
||||
const salesActivityId = this.getNodeParameter('salesActivityId', i);
|
||||
|
||||
const endpoint = `/sales_activities/${salesActivityId}`;
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'PUT', endpoint, body);
|
||||
responseData = responseData.sales_activity;
|
||||
|
||||
}
|
||||
|
||||
} else if (resource === 'task') {
|
||||
|
||||
// **********************************************************************
|
||||
// task
|
||||
// **********************************************************************
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#tasks
|
||||
|
||||
if (operation === 'create') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: create
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#create_task
|
||||
|
||||
const dueDate = this.getNodeParameter('dueDate', i);
|
||||
|
||||
const body = {
|
||||
title: this.getNodeParameter('title', i),
|
||||
owner_id: this.getNodeParameter('ownerId', i),
|
||||
due_date: tz(dueDate, defaultTimezone).format(),
|
||||
targetable_type: this.getNodeParameter('targetableType', i),
|
||||
targetable_id: this.getNodeParameter('targetable_id', i),
|
||||
} as IDataObject;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (Object.keys(additionalFields).length) {
|
||||
Object.assign(body, additionalFields);
|
||||
}
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'POST', '/tasks', body);
|
||||
responseData = responseData.task;
|
||||
|
||||
} else if (operation === 'delete') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: delete
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#delete_a_task
|
||||
|
||||
const taskId = this.getNodeParameter('taskId', i);
|
||||
|
||||
await freshworksCrmApiRequest.call(this, 'DELETE', `/tasks/${taskId}`);
|
||||
responseData = { success: true };
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: get
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#view_a_task
|
||||
|
||||
const taskId = this.getNodeParameter('taskId', i);
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'GET', `/tasks/${taskId}`);
|
||||
responseData = responseData.task;
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: getAll
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#list_all_tasks
|
||||
|
||||
const { filter, include } = this.getNodeParameter('filters', i) as {
|
||||
filter: string;
|
||||
include: string;
|
||||
};
|
||||
|
||||
const qs: IDataObject = {
|
||||
filter: 'open',
|
||||
};
|
||||
|
||||
if (filter) {
|
||||
qs.filter = filter;
|
||||
}
|
||||
|
||||
if (include) {
|
||||
qs.include = include;
|
||||
}
|
||||
|
||||
responseData = await handleListing.call(this, 'GET', '/tasks', {}, qs);
|
||||
|
||||
} else if (operation === 'update') {
|
||||
|
||||
// ----------------------------------------
|
||||
// task: update
|
||||
// ----------------------------------------
|
||||
|
||||
// https://developers.freshworks.com/crm/api/#update_a_task
|
||||
|
||||
const body = {} as IDataObject;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (!Object.keys(updateFields).length) {
|
||||
throwOnEmptyUpdate.call(this, resource);
|
||||
}
|
||||
|
||||
const { dueDate, ...rest } = updateFields;
|
||||
|
||||
if (dueDate) {
|
||||
body.due_date = tz(dueDate, defaultTimezone).format();
|
||||
}
|
||||
|
||||
if (Object.keys(rest).length) {
|
||||
Object.assign(body, rest);
|
||||
}
|
||||
|
||||
const taskId = this.getNodeParameter('taskId', i);
|
||||
|
||||
responseData = await freshworksCrmApiRequest.call(this, 'PUT', `/tasks/${taskId}`, body);
|
||||
responseData = responseData.task;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ json: { error: error.message } });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
Array.isArray(responseData)
|
||||
? returnData.push(...responseData)
|
||||
: returnData.push(responseData);
|
||||
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
||||
215
packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts
Normal file
215
packages/nodes-base/nodes/FreshworksCrm/GenericFunctions.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
NodeApiError,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
FreshworksConfigResponse,
|
||||
FreshworksCrmApiCredentials,
|
||||
SalesAccounts,
|
||||
ViewsResponse,
|
||||
} from './types';
|
||||
|
||||
import {
|
||||
omit,
|
||||
} from 'lodash';
|
||||
|
||||
export async function freshworksCrmApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
) {
|
||||
const { apiKey, domain } = this.getCredentials('freshworksCrmApi') as FreshworksCrmApiCredentials;
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
Authorization: `Token token=${apiKey}`,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: `https://${domain}.myfreshworks.com/crm/sales/api${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 getAllItemsViewId(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
{ fromLoadOptions } = { fromLoadOptions: false },
|
||||
) {
|
||||
let resource = this.getNodeParameter('resource', 0) as string;
|
||||
let keyword = 'All';
|
||||
|
||||
if (resource === 'account' || fromLoadOptions) {
|
||||
resource = 'sales_account'; // adjust resource to endpoint
|
||||
}
|
||||
|
||||
if (resource === 'deal') {
|
||||
keyword = 'My Deals'; // no 'All Deals' available
|
||||
}
|
||||
|
||||
const response = await freshworksCrmApiRequest.call(this, 'GET', `/${resource}s/filters`) as ViewsResponse;
|
||||
|
||||
const view = response.filters.find(v => v.name.includes(keyword));
|
||||
|
||||
if (!view) {
|
||||
throw new NodeOperationError(this.getNode(), 'Failed to get all items view');
|
||||
}
|
||||
|
||||
return view.id.toString();
|
||||
}
|
||||
|
||||
export async function freshworksCrmApiRequestAllItems(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
) {
|
||||
const returnData: IDataObject[] = [];
|
||||
let response: any; // tslint:disable-line: no-any
|
||||
|
||||
qs.page = 1;
|
||||
|
||||
do {
|
||||
response = await freshworksCrmApiRequest.call(this, method, endpoint, body, qs);
|
||||
const key = Object.keys(response)[0];
|
||||
returnData.push(...response[key]);
|
||||
qs.page++;
|
||||
} while (
|
||||
response.meta.total_pages && qs.page <= response.meta.total_pages
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function handleListing(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: string,
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
qs: IDataObject = {},
|
||||
) {
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
|
||||
if (returnAll) {
|
||||
return await freshworksCrmApiRequestAllItems.call(this, method, endpoint, body, qs);
|
||||
}
|
||||
|
||||
const responseData = await freshworksCrmApiRequestAllItems.call(this, method, endpoint, body, qs);
|
||||
const limit = this.getNodeParameter('limit', 0) as number;
|
||||
|
||||
if (limit) return responseData.slice(0, limit);
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load resources for options, except users.
|
||||
*
|
||||
* See: https://developers.freshworks.com/crm/api/#admin_configuration
|
||||
*/
|
||||
export async function loadResource(
|
||||
this: ILoadOptionsFunctions,
|
||||
resource: string,
|
||||
) {
|
||||
const response = await freshworksCrmApiRequest.call(
|
||||
this, 'GET', `/selector/${resource}`,
|
||||
) as FreshworksConfigResponse<LoadedResource>;
|
||||
|
||||
const key = Object.keys(response)[0];
|
||||
return response[key].map(({ name, id }) => ({ name, value: id }));
|
||||
}
|
||||
|
||||
export function adjustAttendees(attendees: [{ type: string, contactId: string, userId: string }]) {
|
||||
return attendees.map((attendee) => {
|
||||
if (attendee.type === 'contact') {
|
||||
return {
|
||||
attendee_type: 'Contact',
|
||||
attendee_id: attendee.contactId.toString(),
|
||||
};
|
||||
} else if (attendee.type === 'user') {
|
||||
return {
|
||||
attendee_type: 'FdMultitenant::User',
|
||||
attendee_id: attendee.userId.toString(),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// /**
|
||||
// * Adjust attendee data from n8n UI to the format expected by Freshworks CRM API.
|
||||
// */
|
||||
// export function adjustAttendees(additionalFields: IDataObject & Attendees) {
|
||||
// if (!additionalFields?.appointment_attendees_attributes) return additionalFields;
|
||||
|
||||
// return {
|
||||
// ...omit(additionalFields, ['appointment_attendees_attributes']),
|
||||
// appointment_attendees_attributes: additionalFields.appointment_attendees_attributes.map(attendeeId => {
|
||||
// return { type: 'user', id: attendeeId };
|
||||
// }),
|
||||
// };
|
||||
// }
|
||||
|
||||
/**
|
||||
* Adjust account data from n8n UI to the format expected by Freshworks CRM API.
|
||||
*/
|
||||
export function adjustAccounts(additionalFields: IDataObject & SalesAccounts) {
|
||||
if (!additionalFields?.sales_accounts) return additionalFields;
|
||||
|
||||
const adjusted = additionalFields.sales_accounts.map(accountId => {
|
||||
return { id: accountId, is_primary: false };
|
||||
});
|
||||
|
||||
adjusted[0].is_primary = true;
|
||||
|
||||
return {
|
||||
...omit(additionalFields, ['sales_accounts']),
|
||||
sales_accounts: adjusted,
|
||||
};
|
||||
}
|
||||
|
||||
export function throwOnEmptyUpdate(
|
||||
this: IExecuteFunctions,
|
||||
resource: string,
|
||||
) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Please enter at least one field to update for the ${resource}.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function throwOnEmptyFilter(
|
||||
this: IExecuteFunctions,
|
||||
) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Please select at least one filter.`,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const accountOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create an account',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete an account',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve an account',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve all accounts',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update an account',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const accountFields = [
|
||||
// ----------------------------------------
|
||||
// account: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
description: 'Name of the account',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Address of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Annual Revenue',
|
||||
name: 'annual_revenue',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Annual revenue of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Business Type ID',
|
||||
name: 'business_type_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getBusinessTypes',
|
||||
},
|
||||
description: 'ID of the business that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'City',
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'City that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Country that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Facebook',
|
||||
name: 'facebook',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Facebook username of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Industry Type ID',
|
||||
name: 'industry_type_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getIndustryTypes',
|
||||
},
|
||||
description: 'ID of the industry that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'LinkedIn',
|
||||
name: 'linkedin',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'LinkedIn account of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Number of Employees',
|
||||
name: 'number_of_employees',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Number of employees in the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'owner_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user to whom the account is assigned',
|
||||
},
|
||||
{
|
||||
displayName: 'Parent Sales Account ID',
|
||||
name: 'parent_sales_account_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Parent account ID of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Phone number of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'State that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Territory ID',
|
||||
name: 'territory_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTerritories',
|
||||
},
|
||||
description: 'ID of the territory that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Twitter',
|
||||
name: 'twitter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Twitter username of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Website',
|
||||
name: 'website',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Website of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Zipcode',
|
||||
name: 'zipcode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Zipcode of the region that the account belongs to',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// account: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Account ID',
|
||||
name: 'accountId',
|
||||
description: 'ID of the account to delete',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// account: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Account ID',
|
||||
name: 'accountId',
|
||||
description: 'ID of the account to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// account: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'View',
|
||||
name: 'view',
|
||||
type: 'options',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getAccountViews',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'How many results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// account: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Account ID',
|
||||
name: 'accountId',
|
||||
description: 'ID of the account to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'account',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Address of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Annual Revenue',
|
||||
name: 'annual_revenue',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Annual revenue of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Business Type ID',
|
||||
name: 'business_type_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getBusinessTypes',
|
||||
},
|
||||
description: 'ID of the business that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'City',
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'City that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Country that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Facebook',
|
||||
name: 'facebook',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Facebook username of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Industry Type ID',
|
||||
name: 'industry_type_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getIndustryTypes',
|
||||
},
|
||||
description: 'ID of the industry that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'LinkedIn',
|
||||
name: 'linkedin',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'LinkedIn account of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Number of Employees',
|
||||
name: 'number_of_employees',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Number of employees in the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'owner_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user to whom the account is assigned',
|
||||
},
|
||||
{
|
||||
displayName: 'Parent Sales Account ID',
|
||||
name: 'parent_sales_account_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Parent account ID of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Phone number of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'State that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Territory ID',
|
||||
name: 'territory_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTerritories',
|
||||
},
|
||||
description: 'ID of the territory that the account belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Twitter',
|
||||
name: 'twitter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Twitter username of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Website',
|
||||
name: 'website',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Website of the account',
|
||||
},
|
||||
{
|
||||
displayName: 'Zipcode',
|
||||
name: 'zipcode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Zipcode of the region that the account belongs to',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,636 @@
|
||||
import {
|
||||
tz,
|
||||
} from 'moment-timezone';
|
||||
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const appointmentOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create an appointment',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete an appointment',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve an appointment',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve all appointments',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update an appointment',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const appointmentFields = [
|
||||
// ----------------------------------------
|
||||
// appointment: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
description: 'Title of the appointment',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date',
|
||||
name: 'fromDate',
|
||||
description: 'Timestamp that denotes the start of appointment. Start date if this is an all-day appointment.',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'End Date',
|
||||
name: 'endDate',
|
||||
description: 'Timestamp that denotes the end of appointment. End date if this is an all-day appointment.',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Attendees',
|
||||
name: 'attendees',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: 'Add Attendee',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'attendee',
|
||||
displayName: 'Attendee',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'contact',
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
],
|
||||
default: 'contact',
|
||||
},
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'userId',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'user',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Creator ID',
|
||||
name: 'creater_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user who created the appointment',
|
||||
},
|
||||
{
|
||||
displayName: 'Is All-Day',
|
||||
name: 'is_allday',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether it is an all-day appointment or not',
|
||||
},
|
||||
{
|
||||
displayName: 'Latitude',
|
||||
name: 'latitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Latitude of the location when you check in for an appointment',
|
||||
},
|
||||
{
|
||||
displayName: 'Location',
|
||||
name: 'location',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Location of the appointment',
|
||||
},
|
||||
{
|
||||
displayName: 'Longitude',
|
||||
name: 'longitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Longitude of the location when you check in for an appointment',
|
||||
},
|
||||
{
|
||||
displayName: 'Outcome ID',
|
||||
name: 'outcome_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOutcomes',
|
||||
},
|
||||
description: 'ID of outcome of Appointment sales activity type',
|
||||
},
|
||||
{
|
||||
displayName: 'Target ID',
|
||||
name: 'targetable_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'ID of contact/account against whom appointment is created',
|
||||
},
|
||||
{
|
||||
displayName: 'Target Type',
|
||||
name: 'targetable_type',
|
||||
type: 'options',
|
||||
default: 'Contact',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'Contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'Deal',
|
||||
},
|
||||
{
|
||||
name: 'SalesAccount',
|
||||
value: 'SalesAccount',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Time Zone',
|
||||
name: 'time_zone',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Timezone that the appointment is scheduled in',
|
||||
options: tz.names().map(tz => ({ name: tz, value: tz })),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Appointment ID',
|
||||
name: 'appointmentId',
|
||||
description: 'ID of the appointment to delete',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Appointment ID',
|
||||
name: 'appointmentId',
|
||||
description: 'ID of the appointment to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'How many results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
default: '',
|
||||
placeholder: 'Add Filter',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Include',
|
||||
name: 'include',
|
||||
type: 'options',
|
||||
default: 'creater',
|
||||
options: [
|
||||
{
|
||||
name: 'Appointment Attendees',
|
||||
value: 'appointment_attendees',
|
||||
},
|
||||
{
|
||||
name: 'Creator',
|
||||
value: 'creater',
|
||||
},
|
||||
{
|
||||
name: 'Target',
|
||||
value: 'targetable',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Time',
|
||||
name: 'filter',
|
||||
type: 'options',
|
||||
default: 'upcoming',
|
||||
options: [
|
||||
{
|
||||
name: 'Past',
|
||||
value: 'past',
|
||||
},
|
||||
{
|
||||
name: 'Upcoming',
|
||||
value: 'upcoming',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// appointment: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Appointment ID',
|
||||
name: 'appointmentId',
|
||||
description: 'ID of the appointment to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'appointment',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Attendees',
|
||||
name: 'attendees',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Attendee',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'attendee',
|
||||
displayName: 'Attendee',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'contact',
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
],
|
||||
default: 'contact',
|
||||
},
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'userId',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'user',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Creator ID',
|
||||
name: 'creater_id',
|
||||
type: 'options',
|
||||
default: [],
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user who created the appointment',
|
||||
},
|
||||
{
|
||||
displayName: 'End Date',
|
||||
name: 'endDate',
|
||||
description: 'Timestamp that denotes the end of appointment. End date if this is an all-day appointment.',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Is All-Day',
|
||||
name: 'is_allday',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether it is an all-day appointment or not',
|
||||
},
|
||||
{
|
||||
displayName: 'Latitude',
|
||||
name: 'latitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Latitude of the location when you check in for an appointment',
|
||||
},
|
||||
{
|
||||
displayName: 'Location',
|
||||
name: 'location',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Location of the appointment',
|
||||
},
|
||||
{
|
||||
displayName: 'Longitude',
|
||||
name: 'longitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Longitude of the location when you check in for an appointment',
|
||||
},
|
||||
{
|
||||
displayName: 'Outcome ID',
|
||||
name: 'outcome_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOutcomes',
|
||||
},
|
||||
description: 'ID of outcome of Appointment sales activity type',
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date',
|
||||
name: 'fromDate',
|
||||
description: 'Timestamp that denotes the start of appointment. Start date if this is an all-day appointment.',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Target ID',
|
||||
name: 'targetable_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'ID of contact/account against whom appointment is created',
|
||||
},
|
||||
{
|
||||
displayName: 'Target Type',
|
||||
name: 'targetable_type',
|
||||
type: 'options',
|
||||
default: 'Contact',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'Contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'Deal',
|
||||
},
|
||||
{
|
||||
name: 'SalesAccount',
|
||||
value: 'SalesAccount',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Time Zone',
|
||||
name: 'time_zone',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Timezone that the appointment is scheduled in',
|
||||
options: tz.names().map(tz => ({ name: tz, value: tz })),
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Title of the appointment',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,668 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const contactOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a contact',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a contact',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a contact',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve all contacts',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a contact',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const contactFields = [
|
||||
// ----------------------------------------
|
||||
// contact: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstName',
|
||||
description: 'First name of the contact',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'lastName',
|
||||
description: 'Last name of the contact',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Email Address',
|
||||
name: 'emails',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Email addresses of the contact',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Address of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaign_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCampaigns',
|
||||
},
|
||||
description: 'ID of the campaign that led your contact to your webapp',
|
||||
},
|
||||
{
|
||||
displayName: 'City',
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'City that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact Status ID',
|
||||
name: 'contact_status_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getContactStatuses',
|
||||
},
|
||||
description: 'ID of the contact status that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Country that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'External ID',
|
||||
name: 'external_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'External ID of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Facebook',
|
||||
name: 'facebook',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Facebook username of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Job Title',
|
||||
name: 'job_title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Designation of the contact in the account they belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Keywords',
|
||||
name: 'keyword',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Keywords that the contact used to reach your website/web app',
|
||||
},
|
||||
{
|
||||
displayName: 'Lead Source ID',
|
||||
name: 'lead_source_id',
|
||||
type: 'string', // not obtainable from API
|
||||
default: '',
|
||||
description: 'ID of the source where contact came from',
|
||||
},
|
||||
{
|
||||
displayName: 'Lifecycle Stage ID',
|
||||
name: 'lifecycle_stage_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLifecycleStages',
|
||||
},
|
||||
description: 'ID of the lifecycle stage that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'LinkedIn',
|
||||
name: 'linkedin',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'LinkedIn account of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Medium',
|
||||
name: 'medium',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Medium that led your contact to your website/webapp',
|
||||
},
|
||||
{
|
||||
displayName: 'Mobile Number',
|
||||
name: 'mobile_number',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Mobile phone number of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'owner_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user to whom the contact is assigned',
|
||||
},
|
||||
{
|
||||
displayName: 'Sales Accounts',
|
||||
name: 'sales_accounts',
|
||||
type: 'multiOptions',
|
||||
default: [],
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getAccounts',
|
||||
},
|
||||
description: 'Accounts which contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'State that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Subscription Status',
|
||||
name: 'subscription_status',
|
||||
type: 'string', // not obtainable from API
|
||||
default: '',
|
||||
description: 'Status of subscription that the contact is in',
|
||||
},
|
||||
{
|
||||
displayName: 'Subscription Types',
|
||||
name: 'subscription_types',
|
||||
type: 'string', // not obtainable from API
|
||||
default: '',
|
||||
description: 'Type of subscription that the contact is in',
|
||||
},
|
||||
{
|
||||
displayName: 'Territory ID',
|
||||
name: 'territory_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTerritories',
|
||||
},
|
||||
description: 'ID of the territory that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Time Zone',
|
||||
name: 'time_zone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Timezone that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Twitter',
|
||||
name: 'twitter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Twitter username of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Work Number',
|
||||
name: 'work_number',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Work phone number of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Zipcode',
|
||||
name: 'zipcode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Zipcode of the region that the contact belongs to',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
description: 'ID of the contact to delete',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
description: 'ID of the contact to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'View',
|
||||
name: 'view',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getContactViews',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'How many results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// contact: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
description: 'ID of the contact to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Address of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaign_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCampaigns',
|
||||
},
|
||||
description: 'ID of the campaign that led your contact to your webapp',
|
||||
},
|
||||
{
|
||||
displayName: 'City',
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'City that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact Status ID',
|
||||
name: 'contact_status_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getContactStatuses',
|
||||
},
|
||||
description: 'ID of the contact status that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Country that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'External ID',
|
||||
name: 'external_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'External ID of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Facebook',
|
||||
name: 'facebook',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Facebook username of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'first_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'First name of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Job Title',
|
||||
name: 'job_title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Designation of the contact in the account they belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Keywords',
|
||||
name: 'keyword',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Keywords that the contact used to reach your website/web app',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'last_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Last name of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Lead Source ID',
|
||||
name: 'lead_source_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLeadSources',
|
||||
},
|
||||
description: 'ID of the source where contact came from',
|
||||
},
|
||||
{
|
||||
displayName: 'Lifecycle Stage ID',
|
||||
name: 'lifecycle_stage_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLifecycleStages',
|
||||
},
|
||||
description: 'ID of the lifecycle stage that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'LinkedIn',
|
||||
name: 'linkedin',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'LinkedIn account of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Medium',
|
||||
name: 'medium',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Medium that led your contact to your website/webapp',
|
||||
},
|
||||
{
|
||||
displayName: 'Mobile Number',
|
||||
name: 'mobile_number',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Mobile phone number of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'owner_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user to whom the contact is assigned',
|
||||
},
|
||||
{
|
||||
displayName: 'Sales Accounts',
|
||||
name: 'sales_accounts',
|
||||
type: 'multiOptions',
|
||||
default: [],
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getAccounts',
|
||||
},
|
||||
description: 'Accounts which contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'State that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Subscription Status',
|
||||
name: 'subscription_status',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSubscriptionStatuses',
|
||||
},
|
||||
description: 'Status of subscription that the contact is in',
|
||||
},
|
||||
{
|
||||
displayName: 'Subscription Types',
|
||||
name: 'subscription_types',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSubscriptionTypes',
|
||||
},
|
||||
description: 'Type of subscription that the contact is in',
|
||||
},
|
||||
{
|
||||
displayName: 'Territory ID',
|
||||
name: 'territory_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTerritories',
|
||||
},
|
||||
description: 'ID of the territory that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Time Zone',
|
||||
name: 'time_zone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Timezone that the contact belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Twitter',
|
||||
name: 'twitter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Twitter username of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Work Number',
|
||||
name: 'work_number',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Work phone number of the contact',
|
||||
},
|
||||
{
|
||||
displayName: 'Zipcode',
|
||||
name: 'zipcode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Zipcode of the region that the contact belongs to',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,545 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const dealOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a deal',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a deal',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a deal',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve all deals',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a deal',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const dealFields = [
|
||||
// ----------------------------------------
|
||||
// deal: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Amount',
|
||||
name: 'amount',
|
||||
description: 'Value of the deal',
|
||||
type: 'number',
|
||||
required: true,
|
||||
default: 0,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
description: 'Name of the deal',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Base Currency Amount',
|
||||
name: 'base_currency_amount',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Value of the deal in base currency',
|
||||
},
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaign_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCampaigns',
|
||||
},
|
||||
description: 'ID of the campaign that landed this deal',
|
||||
},
|
||||
{
|
||||
displayName: 'Currency ID',
|
||||
name: 'currency_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCurrencies',
|
||||
},
|
||||
description: 'ID of the currency that the deal belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Payment Status ID',
|
||||
name: 'deal_payment_status_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealPaymentStatuses',
|
||||
},
|
||||
description: 'ID of the mode of payment for the deal',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Pipeline ID',
|
||||
name: 'deal_pipeline_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealPipelines',
|
||||
},
|
||||
description: 'ID of the deal pipeline that it belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Product ID',
|
||||
name: 'deal_product_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealProducts',
|
||||
},
|
||||
description: 'ID of the product that the deal belongs to (in a multi-product company)',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Reason ID',
|
||||
name: 'deal_reason_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealReasons',
|
||||
},
|
||||
description: 'ID of the reason for losing the deal. Can only be set if the deal is in \'Lost\' stage.',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Stage ID',
|
||||
name: 'deal_stage_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealStages',
|
||||
},
|
||||
description: 'ID of the deal stage that the deal belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Type ID',
|
||||
name: 'deal_type_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealTypes',
|
||||
},
|
||||
description: 'ID of the deal type that the deal belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Lead Source ID',
|
||||
name: 'lead_source_id',
|
||||
type: 'string', // not obtainable from API
|
||||
default: '',
|
||||
description: 'ID of the source where deal came from',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'owner_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user to whom the deal is assigned',
|
||||
},
|
||||
{
|
||||
displayName: 'Probability',
|
||||
name: 'probability',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
},
|
||||
description: 'Probability of winning the deal as a number between 0 and 100',
|
||||
},
|
||||
{
|
||||
displayName: 'Sales Account ID',
|
||||
name: 'sales_account_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getAccounts',
|
||||
},
|
||||
description: 'ID of the account that the deal belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Territory ID',
|
||||
name: 'territory_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTerritories',
|
||||
},
|
||||
description: 'ID of the territory that the deal belongs to',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Deal ID',
|
||||
name: 'dealId',
|
||||
description: 'ID of the deal to delete',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Deal ID',
|
||||
name: 'dealId',
|
||||
description: 'ID of the deal to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'View',
|
||||
name: 'view',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealViews',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'How many results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// deal: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Deal ID',
|
||||
name: 'dealId',
|
||||
description: 'ID of the deal to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'deal',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Amount',
|
||||
name: 'amount',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
description: 'Value of the deal',
|
||||
},
|
||||
{
|
||||
displayName: 'Base Currency Amount',
|
||||
name: 'base_currency_amount',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
description: 'Value of the deal in base currency',
|
||||
},
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaign_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCampaigns',
|
||||
},
|
||||
description: 'ID of the campaign that landed this deal',
|
||||
},
|
||||
{
|
||||
displayName: 'Currency ID',
|
||||
name: 'currency_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCurrencies',
|
||||
},
|
||||
description: 'ID of the currency that the deal belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Payment Status ID',
|
||||
name: 'deal_payment_status_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealPaymentStatuses',
|
||||
},
|
||||
description: 'ID of the mode of payment for the deal',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Pipeline ID',
|
||||
name: 'deal_pipeline_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealPipelines',
|
||||
},
|
||||
description: 'ID of the deal pipeline that it belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Product ID',
|
||||
name: 'deal_product_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealProducts',
|
||||
},
|
||||
description: 'ID of the product that the deal belongs to (in a multi-product company)',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Reason ID',
|
||||
name: 'deal_reason_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealReasons',
|
||||
},
|
||||
description: 'ID of the reason for losing the deal. Can only be set if the deal is in \'Lost\' stage.',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Stage ID',
|
||||
name: 'deal_stage_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealStages',
|
||||
},
|
||||
description: 'ID of the deal stage that the deal belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Deal Type ID',
|
||||
name: 'deal_type_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDealTypes',
|
||||
},
|
||||
description: 'ID of the deal type that the deal belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Lead Source ID',
|
||||
name: 'lead_source_id',
|
||||
type: 'string', // not obtainable from API
|
||||
default: '',
|
||||
description: 'ID of the source where deal came from',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the deal',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'owner_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user to whom the deal is assigned',
|
||||
},
|
||||
{
|
||||
displayName: 'Probability',
|
||||
name: 'probability',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
},
|
||||
description: 'Probability of winning the deal as a number between 0 and 100',
|
||||
},
|
||||
{
|
||||
displayName: 'Sales Account ID',
|
||||
name: 'sales_account_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getAccounts',
|
||||
},
|
||||
description: 'ID of the account that the deal belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Territory ID',
|
||||
name: 'territory_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTerritories',
|
||||
},
|
||||
description: 'ID of the territory that the deal belongs to',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,214 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const noteOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'note',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a note',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a note',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a note',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const noteFields = [
|
||||
// ----------------------------------------
|
||||
// note: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Content',
|
||||
name: 'description',
|
||||
description: 'Content of the note',
|
||||
type: 'string',
|
||||
required: true,
|
||||
typeOptions: {
|
||||
rows: 5,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'note',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Target Type',
|
||||
name: 'targetableType',
|
||||
description: 'Type of the entity for which the note is created',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'Contact',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'Contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'Deal',
|
||||
},
|
||||
{
|
||||
name: 'Sales Account',
|
||||
value: 'SalesAccount',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'note',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Target ID',
|
||||
name: 'targetable_id',
|
||||
description: 'ID of the entity for which note is created. The type of entity is selected in "Target Type".',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'note',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// note: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Note ID',
|
||||
name: 'noteId',
|
||||
description: 'ID of the note to delete',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'note',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// note: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Note ID',
|
||||
name: 'noteId',
|
||||
description: 'ID of the note to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'note',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'note',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Content',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 5,
|
||||
},
|
||||
default: '',
|
||||
description: 'Content of the note',
|
||||
},
|
||||
{
|
||||
displayName: 'Target ID',
|
||||
name: 'targetable_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'ID of the entity for which the note is updated',
|
||||
},
|
||||
{
|
||||
displayName: 'Target Type',
|
||||
name: 'targetable_type',
|
||||
type: 'options',
|
||||
default: 'Contact',
|
||||
description: 'Type of the entity for which the note is updated',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'Contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'Deal',
|
||||
},
|
||||
{
|
||||
name: 'Sales Account',
|
||||
value: 'SalesAccount',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,508 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const salesActivityOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
// {
|
||||
// name: 'Create',
|
||||
// value: 'create',
|
||||
// description: 'Create a sales activity',
|
||||
// },
|
||||
// {
|
||||
// name: 'Delete',
|
||||
// value: 'delete',
|
||||
// description: 'Delete a sales activity',
|
||||
// },
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a sales activity',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve all sales activities',
|
||||
},
|
||||
// {
|
||||
// name: 'Update',
|
||||
// value: 'update',
|
||||
// description: 'Update a sales activity',
|
||||
// },
|
||||
],
|
||||
default: 'get',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const salesActivityFields = [
|
||||
// ----------------------------------------
|
||||
// salesActivity: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Sales Activity Type ID',
|
||||
name: 'sales_activity_type_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSalesActivityTypes',
|
||||
},
|
||||
description: 'ID of a sales activity type for which the sales activity is created',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
description: 'Title of the sales activity to create',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'ownerId',
|
||||
description: 'ID of the user who owns the sales activity',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date',
|
||||
name: 'from_date',
|
||||
description: 'Timestamp that denotes the end of sales activity',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'End Date',
|
||||
name: 'end_date',
|
||||
description: 'Timestamp that denotes the end of sales activity',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Target Type',
|
||||
name: 'targetableType',
|
||||
description: 'Type of the entity for which the sales activity is created',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'Contact',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'Contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'Deal',
|
||||
},
|
||||
{
|
||||
name: 'Sales Account',
|
||||
value: 'SalesAccount',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Target ID',
|
||||
name: 'targetable_id',
|
||||
description: 'ID of the entity for which the sales activity is created. The type of entity is selected in "Target Type".',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Creator ID',
|
||||
name: 'creater_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user who created the sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Latitude',
|
||||
name: 'latitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Latitude of the location when you check in on a sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Location',
|
||||
name: 'location',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Location of the sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Longitude',
|
||||
name: 'longitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Longitude of the location when you check in for a sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Notes',
|
||||
name: 'notes',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Description about the sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Sales Activity Outcome ID',
|
||||
name: 'sales_activity_outcome_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOutcomes',
|
||||
},
|
||||
description: 'ID of a sales activity\'s outcome',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Sales Activity ID',
|
||||
name: 'salesActivityId',
|
||||
description: 'ID of the salesActivity to delete',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Sales Activity ID',
|
||||
name: 'salesActivityId',
|
||||
description: 'ID of the salesActivity to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'How many results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// salesActivity: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Sales Activity ID',
|
||||
name: 'salesActivityId',
|
||||
description: 'ID of the salesActivity to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'salesActivity',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Creator ID',
|
||||
name: 'creater_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user who created the sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date',
|
||||
name: 'end_date',
|
||||
description: 'Timestamp that denotes the start of the sales activity',
|
||||
type: 'dateTime',
|
||||
},
|
||||
{
|
||||
displayName: 'Latitude',
|
||||
name: 'latitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Latitude of the location when you check in on a sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Location',
|
||||
name: 'location',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Location of the sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Longitude',
|
||||
name: 'longitude',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Longitude of the location when you check in for a sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Notes',
|
||||
name: 'notes',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Description about the sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'owner_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user who owns the sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Sales Activity Outcome ID',
|
||||
name: 'sales_activity_outcome_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOutcomes',
|
||||
},
|
||||
description: 'ID of a sales activity\'s outcome',
|
||||
},
|
||||
{
|
||||
displayName: 'Sales Activity Type ID',
|
||||
name: 'sales_activity_type_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getSalesActivityTypes',
|
||||
},
|
||||
description: 'ID of a sales activity type for which the sales activity is updated',
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date',
|
||||
name: 'from_date',
|
||||
description: 'Timestamp that denotes the start of the sales activity',
|
||||
type: 'dateTime',
|
||||
},
|
||||
{
|
||||
displayName: 'Target ID',
|
||||
name: 'targetable_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'ID of the entity for which the sales activity is updated. The type of entity is selected in "Target Type".',
|
||||
},
|
||||
{
|
||||
displayName: 'Target Type',
|
||||
name: 'targetable_type',
|
||||
type: 'options',
|
||||
default: 'Contact',
|
||||
description: 'Type of the entity for which the sales activity is updated',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'Contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'Deal',
|
||||
},
|
||||
{
|
||||
name: 'SalesAccount',
|
||||
value: 'SalesAccount',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Title of the sales activity to update',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,480 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const taskOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a task',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a task',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a task',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve all tasks',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a task',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const taskFields = [
|
||||
// ----------------------------------------
|
||||
// task: create
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
description: 'Title of the task',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date',
|
||||
name: 'dueDate',
|
||||
description: 'Timestamp that denotes when the task is due to be completed',
|
||||
type: 'dateTime',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'ownerId',
|
||||
description: 'ID of the user to whom the task is assigned',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Target Type',
|
||||
name: 'targetableType',
|
||||
description: 'Type of the entity for which the task is updated',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'Contact',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'Contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'Deal',
|
||||
},
|
||||
{
|
||||
name: 'SalesAccount',
|
||||
value: 'SalesAccount',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Target ID',
|
||||
name: 'targetable_id',
|
||||
description: 'ID of the entity for which the task is created. The type of entity is selected in "Target Type".',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Creator ID',
|
||||
name: 'creater_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user who created the task',
|
||||
},
|
||||
{
|
||||
displayName: 'Outcome ID',
|
||||
name: 'outcome_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOutcomes',
|
||||
},
|
||||
description: 'ID of the outcome of the task',
|
||||
},
|
||||
{
|
||||
displayName: 'Task Type ID',
|
||||
name: 'task_type_id',
|
||||
type: 'string', // not obtainable from API
|
||||
default: '',
|
||||
description: 'ID of the type of task',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// task: delete
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'taskId',
|
||||
description: 'ID of the task to delete',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// task: get
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'taskId',
|
||||
description: 'ID of the task to retrieve',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// task: getAll
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'How many results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
default: false,
|
||||
placeholder: 'Add Filter',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Include',
|
||||
name: 'include',
|
||||
type: 'options',
|
||||
default: 'owner',
|
||||
options: [
|
||||
{
|
||||
name: 'Owner',
|
||||
value: 'owner',
|
||||
},
|
||||
{
|
||||
name: 'Target',
|
||||
value: 'targetable',
|
||||
},
|
||||
{
|
||||
name: 'Users',
|
||||
value: 'users',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'filter',
|
||||
type: 'options',
|
||||
default: 'open',
|
||||
options: [
|
||||
{
|
||||
name: 'Completed',
|
||||
value: 'completed',
|
||||
},
|
||||
{
|
||||
name: 'Due Today',
|
||||
value: 'due_today',
|
||||
},
|
||||
{
|
||||
name: 'Due Tomorrow',
|
||||
value: 'due_tomorrow',
|
||||
},
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
name: 'Overdue',
|
||||
value: 'overdue',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------------
|
||||
// task: update
|
||||
// ----------------------------------------
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'taskId',
|
||||
description: 'ID of the task to update',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Creator ID',
|
||||
name: 'creater_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user who created the sales activity',
|
||||
},
|
||||
{
|
||||
displayName: 'Due Date',
|
||||
name: 'dueDate',
|
||||
description: 'Timestamp that denotes when the task is due to be completed',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Outcome ID',
|
||||
name: 'outcome_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOutcomes',
|
||||
},
|
||||
description: 'ID of the outcome of the task',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner ID',
|
||||
name: 'owner_id',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
description: 'ID of the user to whom the task is assigned',
|
||||
},
|
||||
{
|
||||
displayName: 'Target ID',
|
||||
name: 'targetable_id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'ID of the entity for which the task is updated. The type of entity is selected in "Target Type".',
|
||||
},
|
||||
{
|
||||
displayName: 'Target Type',
|
||||
name: 'targetable_type',
|
||||
description: 'Type of the entity for which the task is updated',
|
||||
type: 'options',
|
||||
default: 'Contact',
|
||||
options: [
|
||||
{
|
||||
name: 'Contact',
|
||||
value: 'Contact',
|
||||
},
|
||||
{
|
||||
name: 'Deal',
|
||||
value: 'Deal',
|
||||
},
|
||||
{
|
||||
name: 'SalesAccount',
|
||||
value: 'SalesAccount',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Task Type ID',
|
||||
name: 'task_type_id',
|
||||
type: 'string', // not obtainable from API
|
||||
default: '',
|
||||
description: 'ID of the type of task',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Title of the task',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './AccountDescription';
|
||||
export * from './AppointmentDescription';
|
||||
export * from './ContactDescription';
|
||||
export * from './DealDescription';
|
||||
export * from './NoteDescription';
|
||||
export * from './SalesActivityDescription';
|
||||
export * from './TaskDescription';
|
||||
151
packages/nodes-base/nodes/FreshworksCrm/freshworksCrm.svg
Normal file
151
packages/nodes-base/nodes/FreshworksCrm/freshworksCrm.svg
Normal file
@@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="layer"
|
||||
viewBox="0 0 102.6 102.6"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="freshworksCrm.svg"
|
||||
width="102.6"
|
||||
height="102.6"
|
||||
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview156"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.7642045"
|
||||
inkscape:cx="166.36393"
|
||||
inkscape:cy="122.15137"
|
||||
inkscape:window-width="1312"
|
||||
inkscape:window-height="847"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer" /><defs
|
||||
id="defs173" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style108">
|
||||
.st0{fill:#4D4D4D;}
|
||||
.st1{fill:#9B65C3;}
|
||||
.st2{fill:#BE63C5;}
|
||||
.st3{fill:#BF63C6;}
|
||||
.st4{fill:#3278B1;}
|
||||
.st5{fill:#3278B2;}
|
||||
.st6{fill:#45A4EC;}
|
||||
.st7{fill:#19BB7D;}
|
||||
.st8{fill:#08C7FB;}
|
||||
.st9{fill:#59F2F6;}
|
||||
.st10{fill:#FFA700;}
|
||||
.st11{fill:#DA3757;}
|
||||
.st12{fill:#D33C4E;}
|
||||
.st13{fill:#EE5A24;}
|
||||
.st14{fill:#8BDF55;}
|
||||
.st15{fill:#25C16F;}
|
||||
.st16{fill:#FFBB00;}
|
||||
.st17{fill:#FFA800;}
|
||||
</style>
|
||||
<g
|
||||
id="g168"
|
||||
transform="translate(-21.6,-279.3)">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<path
|
||||
class="st1"
|
||||
d="m 22.2,338.2 c 1.7,11.1 6.9,21.1 14.4,28.6 l 17.5,-17.5 c 0,-17.7 9.2,-34.9 25.6,-44.4 z"
|
||||
id="path130" />
|
||||
<path
|
||||
class="st1"
|
||||
d="m 54.1,349.3 c -8.7,0 -17.5,-2.2 -25.6,-6.9 -2.2,-1.3 -4.3,-2.7 -6.3,-4.3 1.7,11.1 6.9,21.1 14.4,28.6 z"
|
||||
id="path132" />
|
||||
<path
|
||||
class="st2"
|
||||
d="m 54.1,349.3 v 0 l -17.5,17.5 c 7.6,7.6 17.5,12.8 28.6,14.4 L 98.4,323.7 C 89,340.1 71.8,349.3 54.1,349.3"
|
||||
id="path134" />
|
||||
<path
|
||||
class="st3"
|
||||
d="m 61,375 c -4.7,-8.1 -6.9,-16.9 -6.9,-25.6 l -17.5,17.5 c 7.6,7.6 17.5,12.8 28.6,14.4 -1.5,-2 -2.9,-4.1 -4.2,-6.3"
|
||||
id="path136" />
|
||||
<path
|
||||
class="st4"
|
||||
d="M 119.3,282.1 79.8,305 c -16.4,9.5 -35.9,8.8 -51.2,0 -4.4,7.5 -6.9,16.3 -6.9,25.6 0,2.6 0.2,5.1 0.6,7.6 2,1.6 4.1,3 6.3,4.3 8.1,4.7 16.9,6.9 25.6,6.9 l 66.2,-66.2 c -0.4,-0.4 -0.7,-0.8 -1.1,-1.1"
|
||||
id="path138" />
|
||||
<path
|
||||
class="st5"
|
||||
d="m 28.5,342.5 c 8.1,4.7 16.9,6.9 25.6,6.9 0,-17.7 9.2,-34.9 25.6,-44.4 l -57.6,33.2 c 2.1,1.5 4.2,3 6.4,4.3"
|
||||
id="path140" />
|
||||
<path
|
||||
class="st6"
|
||||
d="m 28.5,305 c -4.4,7.5 -6.9,16.3 -6.9,25.6 0,2.6 0.2,5.1 0.6,7.6 L 79.8,305 c -16.5,9.4 -36,8.8 -51.3,0"
|
||||
id="path142" />
|
||||
<path
|
||||
class="st7"
|
||||
d="m 120.4,283.1 c -0.3,-0.3 -0.7,-0.7 -1.1,-1 -0.5,-0.4 -1,-0.8 -1.6,-1.1 -1.9,-1.1 -4.1,-1.7 -6.4,-1.7 H 72.9 c -19,0 -35.5,10.3 -44.4,25.6 15.3,8.8 34.8,9.5 51.2,0 -16.4,9.5 -25.6,26.7 -25.6,44.4 17.7,0 34.9,-9.2 44.4,-25.6 l 22.8,-39.5 c -0.3,-0.4 -0.6,-0.8 -0.9,-1.1"
|
||||
id="path144" />
|
||||
<path
|
||||
class="st8"
|
||||
d="m 79.8,305 39.5,-22.8 c -0.5,-0.4 -1,-0.8 -1.6,-1.1 L 28.5,305 c 15.3,8.8 34.8,9.4 51.3,0"
|
||||
id="path146" />
|
||||
<path
|
||||
class="st9"
|
||||
d="M 111.3,279.3 H 72.9 c -19,0 -35.5,10.3 -44.4,25.6 L 117.7,281 c -1.9,-1 -4,-1.7 -6.4,-1.7"
|
||||
id="path148" />
|
||||
<path
|
||||
class="st10"
|
||||
d="M 54.1,349.3"
|
||||
id="path150" />
|
||||
<path
|
||||
class="st11"
|
||||
d="m 98.5,323.7 22.8,-39.5 c -0.3,-0.4 -0.6,-0.7 -1,-1.1 l -66.2,66.2 c 0,8.7 2.2,17.5 6.9,25.6 1.3,2.2 2.7,4.3 4.3,6.3 2.5,0.4 5,0.6 7.6,0.6 9.3,0 18.1,-2.5 25.6,-6.9 -8.8,-15.3 -9.5,-34.8 0,-51.2"
|
||||
id="path152" />
|
||||
<path
|
||||
class="st12"
|
||||
d="m 54.1,349.3 v 0 c 0,8.7 2.2,17.5 6.9,25.6 1.3,2.2 2.7,4.3 4.3,6.3 L 98.5,323.7 C 89,340.1 71.8,349.3 54.1,349.3"
|
||||
id="path154" />
|
||||
<path
|
||||
class="st13"
|
||||
d="m 98.5,323.7 -33.2,57.6 c 2.5,0.4 5,0.6 7.6,0.6 9.3,0 18.1,-2.5 25.6,-6.9 -8.8,-15.4 -9.5,-34.9 0,-51.3"
|
||||
id="path156" />
|
||||
<path
|
||||
class="st14"
|
||||
d="m 122.4,285.8 c -0.3,-0.6 -0.7,-1.1 -1.1,-1.6 -0.3,-0.4 -0.6,-0.7 -1,-1.1 -0.3,-0.3 -0.7,-0.7 -1.1,-1 L 79.8,305 c -16.4,9.5 -25.6,26.7 -25.6,44.4 17.7,0 34.9,-9.2 44.4,-25.6 -9.5,16.4 -8.8,35.9 0,51.2 15.3,-8.9 25.6,-25.4 25.6,-44.4 v -38.4 c -0.1,-2.4 -0.7,-4.6 -1.8,-6.4"
|
||||
id="path158" />
|
||||
<path
|
||||
class="st15"
|
||||
d="M 119.3,282.1 79.8,305 c -16.4,9.5 -25.6,26.7 -25.6,44.4 l 66.2,-66.2 c -0.4,-0.4 -0.7,-0.8 -1.1,-1.1"
|
||||
id="path160" />
|
||||
<path
|
||||
class="st14"
|
||||
d="m 54.1,349.3 c 17.7,0 34.9,-9.2 44.4,-25.6 l 22.8,-39.5 c -0.3,-0.4 -0.6,-0.7 -1,-1.1 z"
|
||||
id="path162" />
|
||||
<path
|
||||
class="st16"
|
||||
d="m 121.3,284.2 -22.8,39.5 c -9.5,16.4 -8.8,35.9 0,51.2 l 23.9,-89.2 c -0.3,-0.5 -0.7,-1 -1.1,-1.5"
|
||||
id="path164" />
|
||||
<path
|
||||
class="st17"
|
||||
d="m 98.5,375 c 15.3,-8.9 25.6,-25.4 25.6,-44.4 v -38.4 c 0,-2.3 -0.6,-4.5 -1.7,-6.4 z"
|
||||
id="path166" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
43
packages/nodes-base/nodes/FreshworksCrm/types.d.ts
vendored
Normal file
43
packages/nodes-base/nodes/FreshworksCrm/types.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
export type FreshworksCrmApiCredentials = {
|
||||
apiKey: string;
|
||||
domain: string;
|
||||
}
|
||||
|
||||
export type FreshworksConfigResponse<T> = {
|
||||
[key: string]: T[];
|
||||
};
|
||||
|
||||
export type LoadOption = {
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type LoadedCurrency = {
|
||||
currency_code: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type LoadedUser = {
|
||||
id: string;
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
export type SalesAccounts = {
|
||||
sales_accounts?: number[];
|
||||
};
|
||||
|
||||
export type ViewsResponse = {
|
||||
filters: View[];
|
||||
meta: object;
|
||||
}
|
||||
|
||||
export type View = {
|
||||
id: number;
|
||||
name: string;
|
||||
model_class_name: string;
|
||||
user_id: number;
|
||||
is_default: boolean;
|
||||
updated_at: string;
|
||||
user_name: string;
|
||||
current_user_permissions: string[];
|
||||
};
|
||||
@@ -142,6 +142,7 @@ export async function encodeEmail(email: IEmail) {
|
||||
let mailBody: Buffer;
|
||||
|
||||
const mailOptions = {
|
||||
from: email.from,
|
||||
to: email.to,
|
||||
cc: email.cc,
|
||||
bcc: email.bcc,
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
} from 'lodash';
|
||||
|
||||
export interface IEmail {
|
||||
from?: string;
|
||||
to?: string;
|
||||
cc?: string;
|
||||
bcc?: string;
|
||||
@@ -355,6 +356,7 @@ export class Gmail implements INodeType {
|
||||
}
|
||||
|
||||
const email: IEmail = {
|
||||
from: additionalFields.senderName as string || '',
|
||||
to: toStr,
|
||||
cc: ccStr,
|
||||
bcc: bccStr,
|
||||
@@ -455,6 +457,7 @@ export class Gmail implements INodeType {
|
||||
}
|
||||
|
||||
const email: IEmail = {
|
||||
from: additionalFields.senderName as string || '',
|
||||
to: toStr,
|
||||
cc: ccStr,
|
||||
bcc: bccStr,
|
||||
|
||||
@@ -277,6 +277,16 @@ export const messageFields = [
|
||||
placeholder: 'info@example.com',
|
||||
default: [],
|
||||
},
|
||||
{
|
||||
displayName: 'Sender Name',
|
||||
name: 'senderName',
|
||||
type: 'string',
|
||||
placeholder: 'Name <test@gmail.com>',
|
||||
default: '',
|
||||
description: `The name displayed in your contacts inboxes.</br>
|
||||
It has to be in the format: "Display-Name <name@gmail.com>".</br>
|
||||
The email address has to match the email address of the logged in user for the API`,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function googleApiRequest(
|
||||
this: IExecuteFunctions,
|
||||
method: 'POST',
|
||||
endpoint: string,
|
||||
body: IDataObject = {},
|
||||
) {
|
||||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method,
|
||||
body,
|
||||
uri: `https://commentanalyzer.googleapis.com${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.requestOAuth2.call(this, 'googlePerspectiveOAuth2Api', options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.perspective",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": [
|
||||
"Development"
|
||||
],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/credentials/perspective"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.perspective/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
AttributesValuesUi,
|
||||
CommentAnalyzeBody,
|
||||
Language,
|
||||
RequestedAttributes,
|
||||
} from './types';
|
||||
|
||||
import {
|
||||
googleApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
const ISO6391 = require('iso-639-1');
|
||||
|
||||
export class GooglePerspective implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Google Perspective',
|
||||
name: 'googlePerspective',
|
||||
icon: 'file:perspective.svg',
|
||||
group: [
|
||||
'transform',
|
||||
],
|
||||
version: 1,
|
||||
description: 'Consume Google Perspective API',
|
||||
subtitle: '={{$parameter["operation"]}}',
|
||||
defaults: {
|
||||
name: 'Google Perspective',
|
||||
color: '#200647',
|
||||
},
|
||||
inputs: [
|
||||
'main',
|
||||
],
|
||||
outputs: [
|
||||
'main',
|
||||
],
|
||||
credentials: [
|
||||
{
|
||||
name: 'googlePerspectiveOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Analyze Comment',
|
||||
value: 'analyzeComment',
|
||||
},
|
||||
],
|
||||
default: 'analyzeComment',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'analyzeComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Attributes to Analyze',
|
||||
name: 'requestedAttributesUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Atrribute',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'analyzeComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Properties',
|
||||
name: 'requestedAttributesValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Attribute Name',
|
||||
name: 'attributeName',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Flirtation',
|
||||
value: 'flirtation',
|
||||
},
|
||||
{
|
||||
name: 'Identity Attack',
|
||||
value: 'identity_attack',
|
||||
},
|
||||
{
|
||||
name: 'Insult',
|
||||
value: 'insult',
|
||||
},
|
||||
{
|
||||
name: 'Profanity',
|
||||
value: 'profanity',
|
||||
},
|
||||
{
|
||||
name: 'Severe Toxicity',
|
||||
value: 'severe_toxicity',
|
||||
},
|
||||
{
|
||||
name: 'Sexually Explicit',
|
||||
value: 'sexually_explicit',
|
||||
},
|
||||
{
|
||||
name: 'Threat',
|
||||
value: 'threat',
|
||||
},
|
||||
{
|
||||
name: 'Toxicity',
|
||||
value: 'toxicity',
|
||||
},
|
||||
],
|
||||
description: 'Attribute to analyze in the text. Details <a target="_blank" href="https://developers.perspectiveapi.com/s/about-the-api-attributes-and-languages">here</a>',
|
||||
default: 'flirtation',
|
||||
},
|
||||
{
|
||||
displayName: 'Score Threshold',
|
||||
name: 'scoreThreshold',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
numberStepSize: 0.1,
|
||||
numberPrecision: 2,
|
||||
minValue: 0,
|
||||
maxValue: 1,
|
||||
},
|
||||
description: 'Score above which to return results. At zero, all scores are returned.',
|
||||
default: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'analyzeComment',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Option',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Languages',
|
||||
name: 'languages',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLanguages',
|
||||
},
|
||||
default: '',
|
||||
description: 'Languages of the text input. If unspecified, the API will auto-detect the comment language',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the available languages to display them to user so that he can
|
||||
// select them easily
|
||||
async getLanguages(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const supportedLanguages = [
|
||||
'English',
|
||||
'Spanish',
|
||||
'French',
|
||||
'German',
|
||||
'Portuguese',
|
||||
'Italian',
|
||||
'Russian',
|
||||
];
|
||||
|
||||
const languages = ISO6391.getAllNames().filter((language: string) => supportedLanguages.includes(language));
|
||||
for (const language of languages) {
|
||||
const languageName = language;
|
||||
const languageId = ISO6391.getCode(language);
|
||||
returnData.push({
|
||||
name: languageName,
|
||||
value: languageId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
let responseData;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
try {
|
||||
|
||||
|
||||
if (operation === 'analyzeComment') {
|
||||
|
||||
// https://developers.perspectiveapi.com/s/about-the-api-methods
|
||||
|
||||
const attributes = this.getNodeParameter(
|
||||
'requestedAttributesUi.requestedAttributesValues', i, [],
|
||||
) as AttributesValuesUi[];
|
||||
|
||||
if (!attributes.length) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Please enter at least one attribute to analyze.',
|
||||
);
|
||||
}
|
||||
|
||||
const requestedAttributes = attributes.reduce<RequestedAttributes>((acc, cur) => {
|
||||
return Object.assign(acc, {
|
||||
[cur.attributeName.toUpperCase()]: {
|
||||
scoreType: 'probability',
|
||||
scoreThreshold: cur.scoreThreshold,
|
||||
},
|
||||
});
|
||||
}, {});
|
||||
|
||||
const body: CommentAnalyzeBody = {
|
||||
comment: {
|
||||
type: 'PLAIN_TEXT',
|
||||
text: this.getNodeParameter('text', i) as string,
|
||||
},
|
||||
requestedAttributes,
|
||||
};
|
||||
|
||||
const { languages } = this.getNodeParameter('options', i) as { languages: Language };
|
||||
|
||||
if (languages?.length) {
|
||||
body.languages = languages;
|
||||
}
|
||||
|
||||
responseData = await googleApiRequest.call(this, 'POST', '/v1alpha1/comments:analyze', body);
|
||||
}
|
||||
|
||||
|
||||
} 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(responseData)];
|
||||
}
|
||||
}
|
||||
30
packages/nodes-base/nodes/Google/Perspective/perspective.svg
Normal file
30
packages/nodes-base/nodes/Google/Perspective/perspective.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="6 10 125 125"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8">
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath937">
|
||||
<rect
|
||||
style="fill:#0000ff;fill-rule:evenodd"
|
||||
id="rect939"
|
||||
width="123.4947"
|
||||
height="119.02773"
|
||||
x="12.25"
|
||||
y="12.482271" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
id="g532"
|
||||
clip-path="url(#clipPath937)">
|
||||
<path
|
||||
d="M131.53 72a59.79 59.79 0 0 1-53.81 59.49v-119A59.79 59.79 0 0 1 131.53 72zM12.25 78v53.51h53.52A59.81 59.81 0 0 0 12.25 78zm0-12h53.52V12.51A59.81 59.81 0 0 0 12.25 66zm176.83-37.06H223c21.77 0 35.8 11.32 35.8 29.65s-14 29.64-35.8 29.64h-15.22v26.82h-18.7zm33.34 44c11.81 0 17.34-4.55 17.34-14.39s-5.53-14.4-17.34-14.4h-14.64V73zm37.14 11.72v-.61c0-19.93 12.91-33 32-33 18 0 31 12.67 31 33.46v4.18h-44.9c1 9.72 6 14.27 14.14 14.27 5.91 0 10.09-2.58 11.94-7h18.2c-2.7 12.42-14.64 21-30.26 21-19.82.04-32.12-11.96-32.12-32.3zm18.57-7.87h26.2c-1.72-8.12-6.27-12.18-12.91-12.18s-11.69 3.94-13.29 12.18zm54.73-23.74h17.59v9.47C354.26 55.88 361 52.31 370 52.31h3.56v17h-15.5c-4.67 0-7.13 2.46-7.13 7v38.75h-18.07zM378.12 96H395c.62 4.92 4.55 7.5 13.29 7.5 7.38 0 10.94-2.33 10.94-5.65 0-2.71-1.72-3.94-6.15-4.8l-14.79-2.85c-13.65-2.46-19.06-8.12-19.06-18.08 0-12.3 10-21 27.92-21 16.73 0 28.17 8.37 29 20.54h-16.82c-.74-4.42-4.8-7-12.3-7-7 0-10.46 2.22-10.46 5.54 0 2.58 1.72 3.81 6 4.67l14.88 2.83c13.53 2.46 18.94 8.12 18.94 18.21 0 12.91-10.45 21.15-28.29 21.15-18.79-.06-29.37-8.78-29.98-21.06zm68.13-42.95h17.59v9.84c3.7-7.38 10.71-11.56 19.56-11.56 14.89 0 26.33 11.68 26.33 32v.62c0 20.17-12.06 32.84-26.94 32.84-8.12 0-14.76-3.32-18.45-9.47v30.63h-18.09zM491 84.42v-1.6c0-11.32-4.92-17.35-13.16-17.35-8.61 0-13.53 5.91-13.53 17.23v2.7c0 12.18 5 17.22 13.16 17.22 7.9 0 13.53-6.62 13.53-18.2zm26.1.24v-.61c0-19.93 12.92-33 32-33 18 0 31 12.67 31 33.46v4.18h-44.9c1 9.72 6 14.27 14.15 14.27 5.9 0 10.08-2.58 11.93-7h18.2c-2.7 12.42-14.63 21-30.25 21-19.83.04-32.13-11.96-32.13-32.3zm18.57-7.87h26.21c-1.73-8.12-6.28-12.18-12.92-12.18s-11.69 3.94-13.29 12.18zm51.53 7.75v-.74c0-19.31 13.16-32.72 31.37-32.72 16.24 0 27.8 9.35 29.4 24.6h-17.71c-.74-6.64-5.42-10.45-11.44-10.45-7.88 0-13 6.52-13 18.2v1.48c0 11.81 5.16 18 12.79 18 6.52 0 10.95-4.06 11.81-11.2h17.71c-1.6 15.75-13 25.34-29.77 25.34C600 117 587.2 104.34 587.2 84.54zm76.01 9.35V67.32h-10.7V53.05h10.7v-18h18v18h18.21v14.27h-18.25v30.75c0 1.72.74 2.46 2.46 2.46h15.75v14.52H685.6c-14.52 0-22.39-7.51-22.39-21.16zm46-58.31a10.09 10.09 0 0 1 20.17 0 10.09 10.09 0 1 1-20.17 0zm1 17.47h18.08v62h-18.1zm25.19 0H754l14.39 45.27 14.27-45.27h17.71l-20.91 62h-23.15zm68.88 31.61v-.61c0-19.93 12.92-33 32-33 18 0 31 12.67 31 33.46v4.18h-44.9c1 9.72 6 14.27 14.15 14.27 5.9 0 10.09-2.58 11.93-7h18.21c-2.71 12.42-14.64 21-30.26 21-19.83.04-32.13-11.96-32.13-32.3zm18.57-7.87h26.21c-1.73-8.12-6.28-12.18-12.92-12.18s-11.69 3.94-13.29 12.18z"
|
||||
fill="#451735"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
26
packages/nodes-base/nodes/Google/Perspective/types.d.ts
vendored
Normal file
26
packages/nodes-base/nodes/Google/Perspective/types.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
export type CommentAnalyzeBody = {
|
||||
comment: Comment;
|
||||
requestedAttributes: RequestedAttributes;
|
||||
languages?: Language;
|
||||
};
|
||||
|
||||
export type Language = 'de' | 'en' | 'fr' | 'ar' | 'es' | 'it' | 'pt' | 'ru';
|
||||
|
||||
export type Comment = {
|
||||
text?: string;
|
||||
type?: string;
|
||||
};
|
||||
|
||||
export type RequestedAttributes = {
|
||||
[key: string]: {
|
||||
scoreType?: string;
|
||||
scoreThreshold?: {
|
||||
value: number
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type AttributesValuesUi = {
|
||||
attributeName: string;
|
||||
scoreThreshold: number;
|
||||
};
|
||||
@@ -15,16 +15,16 @@ export const eventOperations = [
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create an event',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all events',
|
||||
},
|
||||
{
|
||||
name: 'Post',
|
||||
value: 'post',
|
||||
description: 'Post an event',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
description: 'The operation to perform.',
|
||||
@@ -79,7 +79,7 @@ export const eventFields = [
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* event:post */
|
||||
/* event:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Event Type',
|
||||
@@ -88,7 +88,7 @@ export const eventFields = [
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'post',
|
||||
'create',
|
||||
],
|
||||
resource: [
|
||||
'event',
|
||||
@@ -114,7 +114,7 @@ export const eventFields = [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'post',
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -87,10 +87,10 @@ export class HomeAssistant implements INodeType {
|
||||
name: 'Config',
|
||||
value: 'config',
|
||||
},
|
||||
// {
|
||||
// name: 'Event',
|
||||
// value: 'event',
|
||||
// },
|
||||
{
|
||||
name: 'Event',
|
||||
value: 'event',
|
||||
},
|
||||
// {
|
||||
// name: 'History',
|
||||
// value: 'history',
|
||||
@@ -226,7 +226,7 @@ export class HomeAssistant implements INodeType {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
} else if (operation === 'post') {
|
||||
} else if (operation === 'create') {
|
||||
const eventType = this.getNodeParameter('eventType', i) as string;
|
||||
const eventAttributes = this.getNodeParameter('eventAttributes', i) as {
|
||||
attributes: IDataObject[],
|
||||
|
||||
@@ -78,7 +78,13 @@ export class Interval implements INodeType {
|
||||
this.emit([this.helpers.returnJsonArray([{}])]);
|
||||
};
|
||||
|
||||
const intervalObj = setInterval(executeTrigger, intervalValue * 1000);
|
||||
intervalValue *= 1000;
|
||||
|
||||
if (intervalValue > Number.MAX_SAFE_INTEGER) {
|
||||
throw new Error('The interval value is too large.');
|
||||
}
|
||||
|
||||
const intervalObj = setInterval(executeTrigger, );
|
||||
|
||||
async function closeFunction() {
|
||||
clearInterval(intervalObj);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user