mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 12:44:07 -08:00
Merge branch 'master' into save-changes-warning
This commit is contained in:
commit
6961050a42
|
@ -30,7 +30,7 @@ n8n is split up in different modules which are all in a single mono repository.
|
|||
|
||||
The most important directories:
|
||||
|
||||
- [/docker/image](/docker/image) - Dockerfiles to create n8n containers
|
||||
- [/docker/image](/docker/images) - Dockerfiles to create n8n containers
|
||||
- [/docker/compose](/docker/compose) - Examples Docker Setups
|
||||
- [/packages](/packages) - The different n8n modules
|
||||
- [/packages/cli](/packages/cli) - CLI code to run front- & backend
|
||||
|
@ -57,11 +57,16 @@ dependencies are installed and the packages get linked correctly. Here a short g
|
|||
|
||||
The packages which n8n uses depend on a few build tools:
|
||||
|
||||
Linux:
|
||||
Debian/Ubuntu:
|
||||
```
|
||||
apt-get install -y build-essential python
|
||||
```
|
||||
|
||||
CentOS:
|
||||
```
|
||||
yum install gcc gcc-c++ make
|
||||
```
|
||||
|
||||
Windows:
|
||||
```
|
||||
npm install -g windows-build-tools
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.79.3",
|
||||
"version": "0.80.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -100,9 +100,9 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"mongodb": "^3.5.5",
|
||||
"mysql2": "^2.0.1",
|
||||
"n8n-core": "~0.43.0",
|
||||
"n8n-core": "~0.44.0",
|
||||
"n8n-editor-ui": "~0.55.0",
|
||||
"n8n-nodes-base": "~0.74.1",
|
||||
"n8n-nodes-base": "~0.75.0",
|
||||
"n8n-workflow": "~0.39.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
|
|
|
@ -38,7 +38,12 @@ class CredentialsOverwritesClass {
|
|||
}
|
||||
|
||||
const returnData = JSON.parse(JSON.stringify(data));
|
||||
Object.assign(returnData, overwrites);
|
||||
// Overwrite only if there is currently no data set
|
||||
for (const key of Object.keys(overwrites)) {
|
||||
if ([null, undefined, ''].includes(returnData[key])) {
|
||||
returnData[key] = overwrites[key];
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
|
|
@ -298,7 +298,7 @@ class App {
|
|||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
(req as ICustomRequest).parsedUrl = parseUrl(req);
|
||||
// @ts-ignore
|
||||
req.rawBody = new Buffer('', 'base64');
|
||||
req.rawBody = Buffer.from('', 'base64');
|
||||
next();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.43.0",
|
||||
"version": "0.44.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.9.0",
|
||||
"version": "0.10.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -58,8 +58,8 @@
|
|||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "^0.43.0",
|
||||
"n8n-workflow": "^0.33.0",
|
||||
"n8n-core": "^0.44.0",
|
||||
"n8n-workflow": "^0.39.0",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
"tmp-promise": "^2.0.2",
|
||||
|
|
|
@ -63,8 +63,15 @@ export async function createCustomTsconfig () {
|
|||
export async function buildFiles (options?: IBuildOptions): Promise<string> {
|
||||
options = options || {};
|
||||
|
||||
// Get the path of the TypeScript cli of this project
|
||||
const tscPath = join(__dirname, '../../node_modules/.bin/tsc');
|
||||
let typescriptPath;
|
||||
|
||||
// Check for OS to designate correct tsc path
|
||||
if (process.platform === 'win32') {
|
||||
typescriptPath = '../../node_modules/TypeScript/lib/tsc';
|
||||
} else {
|
||||
typescriptPath = '../../node_modules/.bin/tsc';
|
||||
}
|
||||
const tscPath = join(__dirname, typescriptPath);
|
||||
|
||||
const tsconfigData = await createCustomTsconfig();
|
||||
|
||||
|
|
|
@ -10,11 +10,26 @@ export class CustomerIoApi implements ICredentialType {
|
|||
documentationUrl = 'customerIo';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'App API Key',
|
||||
name: 'apiKey',
|
||||
displayName: 'Tracking API Key',
|
||||
name: 'trackingApiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Required for tracking API.',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Tracking Site ID',
|
||||
name: 'trackingSiteId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Required for tracking API.',
|
||||
},
|
||||
{
|
||||
displayName: 'App API Key',
|
||||
name: 'appApiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
description: 'Required for App API.',
|
||||
},
|
||||
|
||||
];
|
||||
}
|
||||
|
|
59
packages/nodes-base/credentials/Mqtt.credentials.ts
Normal file
59
packages/nodes-base/credentials/Mqtt.credentials.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class Mqtt implements ICredentialType {
|
||||
name = 'mqtt';
|
||||
displayName = 'MQTT';
|
||||
properties = [
|
||||
// The credentials to get from user and save encrypted.
|
||||
// Properties can be defined exactly in the same way
|
||||
// as node properties.
|
||||
{
|
||||
displayName: 'Protocol',
|
||||
name: 'protocol',
|
||||
type: 'options' as NodePropertyTypes,
|
||||
options: [
|
||||
{
|
||||
name: 'mqtt',
|
||||
value: 'mqtt',
|
||||
},
|
||||
{
|
||||
name: 'ws',
|
||||
value: 'ws',
|
||||
},
|
||||
],
|
||||
default: 'mqtt',
|
||||
},
|
||||
{
|
||||
displayName: 'Host',
|
||||
name: 'host',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Port',
|
||||
name: 'port',
|
||||
type: 'number' as NodePropertyTypes,
|
||||
default: 1883,
|
||||
},
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Password',
|
||||
name: 'password',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -36,11 +36,11 @@ export class PagerDutyOAuth2Api implements ICredentialType {
|
|||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'header',
|
||||
description: 'Method of authentication.',
|
||||
},
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'header',
|
||||
description: 'Method of authentication.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
45
packages/nodes-base/credentials/S3.credentials.ts
Normal file
45
packages/nodes-base/credentials/S3.credentials.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class S3 implements ICredentialType {
|
||||
name = 's3';
|
||||
displayName = 'S3';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'S3 endpoint',
|
||||
name: 'endpoint',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: ''
|
||||
},
|
||||
{
|
||||
displayName: 'Region',
|
||||
name: 'region',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: 'us-east-1',
|
||||
},
|
||||
{
|
||||
displayName: 'Access Key Id',
|
||||
name: 'accessKeyId',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Secret Access Key',
|
||||
name: 'secretAccessKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Force path style',
|
||||
name: 'forcePathStyle',
|
||||
type: 'boolean' as NodePropertyTypes,
|
||||
default: false
|
||||
},
|
||||
];
|
||||
}
|
|
@ -37,5 +37,12 @@ export class SalesforceOAuth2Api implements ICredentialType {
|
|||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'header',
|
||||
description: 'Method of authentication.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
17
packages/nodes-base/credentials/SentryIoApi.credentials.ts
Normal file
17
packages/nodes-base/credentials/SentryIoApi.credentials.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class SentryIoApi implements ICredentialType {
|
||||
name = 'sentryIoApi';
|
||||
displayName = 'Sentry.io API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Token',
|
||||
name: 'token',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class SentryIoOAuth2Api implements ICredentialType {
|
||||
name = 'sentryIoOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'Sentry.io OAuth2 API';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://sentry.io/oauth/authorize/',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://sentry.io/oauth/token/',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'event:admin event:read org:read project:read project:releases team:read event:write org:admin project:write team:write project:admin team:admin',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'body',
|
||||
},
|
||||
];
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -4,8 +4,17 @@ import {
|
|||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import { OptionsWithUri } from 'request';
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
get,
|
||||
} from 'lodash';
|
||||
|
||||
/**
|
||||
* Make an API request to Asana
|
||||
|
@ -16,7 +25,7 @@ import { OptionsWithUri } from 'request';
|
|||
* @param {object} body
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: object): Promise<any> { // tslint:disable-line:no-any
|
||||
export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: object, uri?: string | undefined): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('asanaApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
|
@ -30,7 +39,7 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions |
|
|||
method,
|
||||
body: { data: body },
|
||||
qs: query,
|
||||
uri: `https://app.asana.com/api/1.0/${endpoint}`,
|
||||
uri: uri || `https://app.asana.com/api/1.0${endpoint}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
|
@ -54,3 +63,22 @@ export async function asanaApiRequest(this: IHookFunctions | IExecuteFunctions |
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function asanaApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
let uri: string | undefined;
|
||||
query.limit = 100;
|
||||
|
||||
do {
|
||||
responseData = await asanaApiRequest.call(this, method, endpoint, body, query, uri);
|
||||
uri = get(responseData, 'next_page.uri');
|
||||
returnData.push.apply(returnData, responseData['data']);
|
||||
} while (
|
||||
responseData['next_page'] !== null
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = { headers: headers || {}, host: endpoint, method, path, body };
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`, secretAccessKey: `${credentials.secretAccessKey}` });
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
|
@ -36,7 +36,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
// Sign AWS API request with the user credentials
|
||||
const signOpts = {headers: headers || {}, host: endpoint, method, path: `${path}?${queryToString(query).replace(/\+/g, '%2B')}`, body};
|
||||
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`, secretAccessKey: `${credentials.secretAccessKey}`});
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim()});
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
|
@ -99,7 +99,7 @@ export class ClickUp implements INodeType {
|
|||
{
|
||||
name: 'clickUpApi',
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
|
@ -296,7 +296,7 @@ export class ClickUp implements INodeType {
|
|||
const { tags } = await clickupApiRequest.call(this, 'GET', `/space/${spaceId}/tag`);
|
||||
for (const tag of tags) {
|
||||
const tagName = tag.name;
|
||||
const tagId = tag.id;
|
||||
const tagId = tag.name;
|
||||
returnData.push({
|
||||
name: tagName,
|
||||
value: tagId,
|
||||
|
@ -320,6 +320,23 @@ export class ClickUp implements INodeType {
|
|||
}
|
||||
return returnData;
|
||||
},
|
||||
|
||||
// Get all the custom fields to display them to user so that he can
|
||||
// select them easily
|
||||
async getCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const listId = this.getCurrentNodeParameter('list') as string;
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { fields } = await clickupApiRequest.call(this, 'GET', `/list/${listId}/field`);
|
||||
for (const field of fields) {
|
||||
const fieldName = field.name;
|
||||
const fieldId = field.id;
|
||||
returnData.push({
|
||||
name: fieldName,
|
||||
value: fieldId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -846,6 +863,22 @@ export class ClickUp implements INodeType {
|
|||
if (filters.dateUpdatedLt) {
|
||||
qs.date_updated_lt = new Date(filters.dateUpdatedLt as string).getTime();
|
||||
}
|
||||
if (filters.customFieldsUi) {
|
||||
const customFieldsValues = (filters.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
|
||||
if (customFieldsValues) {
|
||||
const customFields: IDataObject[] = [];
|
||||
for (const customFieldValue of customFieldsValues) {
|
||||
customFields.push({
|
||||
field_id: customFieldValue.fieldId,
|
||||
operator: (customFieldValue.operator === 'equal') ? '=' : customFieldValue.operator,
|
||||
value: customFieldValue.value as string,
|
||||
});
|
||||
}
|
||||
|
||||
qs.custom_fields = JSON.stringify(customFields);
|
||||
}
|
||||
}
|
||||
|
||||
const listId = this.getNodeParameter('list', i) as string;
|
||||
if (returnAll === true) {
|
||||
responseData = await clickupApiRequestAllItems.call(this, 'tasks', 'GET', `/list/${listId}/task`, {}, qs);
|
||||
|
@ -855,6 +888,19 @@ export class ClickUp implements INodeType {
|
|||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'member') {
|
||||
const taskId = this.getNodeParameter('id', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
if (returnAll === true) {
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/task/${taskId}/member`, {}, qs);
|
||||
responseData = responseData.members;
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/task/${taskId}/member`, {}, qs);
|
||||
responseData = responseData.members;
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'setCustomField') {
|
||||
const taskId = this.getNodeParameter('task', i) as string;
|
||||
const fieldId = this.getNodeParameter('field', i) as string;
|
||||
|
@ -984,6 +1030,19 @@ export class ClickUp implements INodeType {
|
|||
responseData = await clickupApiRequest.call(this, 'POST', `/folder/${folderId}/list`, body);
|
||||
}
|
||||
}
|
||||
if (operation === 'member') {
|
||||
const listId = this.getNodeParameter('id', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
if (returnAll === true) {
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/list/${listId}/member`, {}, qs);
|
||||
responseData = responseData.members;
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/list/${listId}/member`, {}, qs);
|
||||
responseData = responseData.members;
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'customFields') {
|
||||
const listId = this.getNodeParameter('list', i) as string;
|
||||
responseData = await clickupApiRequest.call(this, 'GET', `/list/${listId}/field`);
|
||||
|
|
|
@ -40,6 +40,11 @@ export const listOperations = [
|
|||
value: 'getAll',
|
||||
description: 'Get all lists',
|
||||
},
|
||||
{
|
||||
name: 'Member',
|
||||
value: 'member',
|
||||
description: 'Get list members',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
|
@ -229,6 +234,68 @@ export const listFields = [
|
|||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:member */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'List ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'member',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Task ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'member',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'list',
|
||||
],
|
||||
operation: [
|
||||
'member',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* list:customFields */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
|
|
|
@ -31,12 +31,17 @@ export const taskOperations = [
|
|||
description: 'Get a task',
|
||||
},
|
||||
{
|
||||
name: 'Get all',
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all tasks',
|
||||
},
|
||||
{
|
||||
name: 'Set custom field',
|
||||
name: 'Member',
|
||||
value: 'member',
|
||||
description: 'Get task members',
|
||||
},
|
||||
{
|
||||
name: 'Set Custom Field',
|
||||
value: 'setCustomField',
|
||||
description: 'Set a custom field',
|
||||
},
|
||||
|
@ -95,7 +100,7 @@ export const taskFields = [
|
|||
loadOptionsMethod: 'getSpaces',
|
||||
loadOptionsDependsOn: [
|
||||
'team',
|
||||
]
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
@ -190,7 +195,7 @@ export const taskFields = [
|
|||
loadOptionsMethod: 'getLists',
|
||||
loadOptionsDependsOn: [
|
||||
'folder',
|
||||
]
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
@ -239,7 +244,6 @@ export const taskFields = [
|
|||
typeOptions: {
|
||||
loadOptionsMethod: 'getAssignees',
|
||||
},
|
||||
|
||||
default: [],
|
||||
},
|
||||
{
|
||||
|
@ -302,6 +306,12 @@ export const taskFields = [
|
|||
description: 'Integer mapping as 1 : Urgent, 2 : High, 3 : Normal, 4 : Low',
|
||||
default: 3,
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date',
|
||||
name: 'startDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date Time',
|
||||
name: 'startDateTime',
|
||||
|
@ -457,6 +467,12 @@ export const taskFields = [
|
|||
default: '',
|
||||
description: 'status'
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date',
|
||||
name: 'startDate',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Start Date Time',
|
||||
name: 'startDateTime',
|
||||
|
@ -631,7 +647,7 @@ export const taskFields = [
|
|||
loadOptionsMethod: 'getLists',
|
||||
loadOptionsDependsOn: [
|
||||
'folder',
|
||||
]
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
|
@ -712,6 +728,91 @@ export const taskFields = [
|
|||
|
||||
default: [],
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
placeholder: 'Add Custom Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
description: 'Filter by custom fields ',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'customFieldsValues',
|
||||
displayName: 'Custom Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field ID',
|
||||
name: 'fieldId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCustomFields',
|
||||
},
|
||||
default: '',
|
||||
description: 'The ID of the field to add custom field to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Operator',
|
||||
name: 'operator',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
name: '<',
|
||||
value: '<',
|
||||
},
|
||||
{
|
||||
name: '<=',
|
||||
value: '<=',
|
||||
},
|
||||
{
|
||||
name: '>',
|
||||
value: '>',
|
||||
},
|
||||
{
|
||||
name: '>=',
|
||||
value: '>=',
|
||||
},
|
||||
{
|
||||
name: '!=',
|
||||
value: '!=',
|
||||
},
|
||||
{
|
||||
name: 'Is Null',
|
||||
value: 'IS NULL',
|
||||
},
|
||||
{
|
||||
name: 'Is Not Null',
|
||||
value: 'IS NOT NULL',
|
||||
},
|
||||
],
|
||||
default: 'equal',
|
||||
description: 'The value to set on custom field.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
operator: [
|
||||
'IS NULL',
|
||||
'IS NOT NULL',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The value to set on custom field.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Date Created Greater Than',
|
||||
name: 'dateCreatedGt',
|
||||
|
@ -841,6 +942,68 @@ export const taskFields = [
|
|||
description: 'task ID',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:member */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Task ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'member',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Task ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'member',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'task',
|
||||
],
|
||||
operation: [
|
||||
'member',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* task:setCustomField */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
|
|
|
@ -118,6 +118,7 @@ export class ClockifyTrigger implements INodeType {
|
|||
|
||||
if (Array.isArray(result) && result.length !== 0) {
|
||||
result = [this.helpers.returnJsonArray(result)];
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
199
packages/nodes-base/nodes/CustomerIo/CampaignDescription.ts
Normal file
199
packages/nodes-base/nodes/CustomerIo/CampaignDescription.ts
Normal file
|
@ -0,0 +1,199 @@
|
|||
import { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const campaignOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
},
|
||||
{
|
||||
name: 'Get Metrics',
|
||||
value: 'getMetrics',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const campaignFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* campaign:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaignId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
default: 0,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The unique identifier for the campaign',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* campaign:getMetrics */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Campaign ID',
|
||||
name: 'campaignId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
default: 0,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'getMetrics',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The unique identifier for the campaign',
|
||||
},
|
||||
{
|
||||
displayName: 'Period',
|
||||
name: 'period',
|
||||
type: 'options',
|
||||
default: 'days',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'getMetrics',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'Specify metric period',
|
||||
options: [
|
||||
{
|
||||
name: 'Hours',
|
||||
value: 'hours',
|
||||
},
|
||||
{
|
||||
name: 'Days',
|
||||
value: 'days',
|
||||
},
|
||||
{
|
||||
name: 'Weeks',
|
||||
value: 'weeks',
|
||||
},
|
||||
{
|
||||
name: 'Months',
|
||||
value: 'months',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'getMetrics',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'getMetrics'
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Steps',
|
||||
name: 'steps',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Integer specifying how many steps to return. Defaults to the maximum number of timeperiods available, or 12 when using the months period. Maximum timeperiods available are 24 hours, 45 days, 12 weeks and 120 months',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 120,
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
default: 'empty',
|
||||
description: 'Specify metric type',
|
||||
options: [
|
||||
{
|
||||
name: 'Empty',
|
||||
value: 'empty',
|
||||
},
|
||||
{
|
||||
name: 'Email',
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
name: 'Push',
|
||||
value: 'push',
|
||||
},
|
||||
{
|
||||
name: 'Slack',
|
||||
value: 'slack',
|
||||
},
|
||||
{
|
||||
name: 'twilio',
|
||||
value: 'twilio',
|
||||
},
|
||||
{
|
||||
name: 'Urban Airship',
|
||||
value: 'urbanAirship',
|
||||
},
|
||||
{
|
||||
name: 'Webhook',
|
||||
value: 'webhook',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
189
packages/nodes-base/nodes/CustomerIo/CustomerDescription.ts
Normal file
189
packages/nodes-base/nodes/CustomerIo/CustomerDescription.ts
Normal file
|
@ -0,0 +1,189 @@
|
|||
import { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const customerOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customer',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create/Update',
|
||||
value: 'upsert',
|
||||
description: 'Create/Update a customer.',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a customer.',
|
||||
},
|
||||
],
|
||||
default: 'upsert',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const customerFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* customer:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customer',
|
||||
],
|
||||
operation: [
|
||||
'delete',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The unique identifier for the customer.',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* customer:upsert */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customer',
|
||||
],
|
||||
operation: [
|
||||
'upsert',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The unique identifier for the customer.',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customer',
|
||||
],
|
||||
operation: [
|
||||
'upsert',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: ' Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customer',
|
||||
],
|
||||
operation: [
|
||||
'upsert',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Object of values to set as described <a href="https://github.com/agilecrm/rest-api#1-companys---companies-api" target="_blank">here</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'customer',
|
||||
],
|
||||
operation: [
|
||||
'upsert',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Properties',
|
||||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'customProperty',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Property name.',
|
||||
placeholder: 'Plan',
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Property value.',
|
||||
placeholder: 'Basic',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The email address of the user.',
|
||||
},
|
||||
{
|
||||
displayName: 'Created at',
|
||||
name: 'createdAt',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The UNIX timestamp from when the user was created.',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
327
packages/nodes-base/nodes/CustomerIo/CustomerIo.node.ts
Normal file
327
packages/nodes-base/nodes/CustomerIo/CustomerIo.node.ts
Normal file
|
@ -0,0 +1,327 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
import {
|
||||
IDataObject,
|
||||
INodeTypeDescription,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
} from 'n8n-workflow';
|
||||
import { customerIoApiRequest, validateJSON } from './GenericFunctions';
|
||||
import { campaignOperations, campaignFields } from './CampaignDescription';
|
||||
import { customerOperations, customerFields } from './CustomerDescription';
|
||||
import { eventOperations, eventFields } from './EventDescription';
|
||||
import { segmentOperations, segmentFields } from './SegmentDescription';
|
||||
|
||||
|
||||
export class CustomerIo implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Customer.io',
|
||||
name: 'customerIo',
|
||||
icon: 'file:customerio.png',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Customer.io API',
|
||||
defaults: {
|
||||
name: 'CustomerIo',
|
||||
color: '#ffcd00',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'customerIoApi',
|
||||
required: true,
|
||||
}
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Customer',
|
||||
value: 'customer',
|
||||
},
|
||||
{
|
||||
name: 'Event',
|
||||
value: 'event',
|
||||
},
|
||||
{
|
||||
name: 'Campaign',
|
||||
value: 'campaign',
|
||||
},
|
||||
{
|
||||
name: 'Segment',
|
||||
value: 'segment',
|
||||
},
|
||||
],
|
||||
default: 'customer',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
// CAMPAIGN
|
||||
...campaignOperations,
|
||||
...campaignFields,
|
||||
// CUSTOMER
|
||||
...customerOperations,
|
||||
...customerFields,
|
||||
// EVENT
|
||||
...eventOperations,
|
||||
...eventFields,
|
||||
// SEGMENT
|
||||
...segmentOperations,
|
||||
...segmentFields
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const returnData: IDataObject[] = [];
|
||||
const items = this.getInputData();
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
const body: IDataObject = {};
|
||||
|
||||
let responseData;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
if (resource === 'campaign') {
|
||||
if (operation === 'get') {
|
||||
const campaignId = this.getNodeParameter('campaignId', i) as number;
|
||||
const endpoint = `/campaigns/${campaignId}`;
|
||||
|
||||
responseData = await customerIoApiRequest.call(this, 'GET', endpoint, body, 'beta');
|
||||
responseData = responseData.campaign;
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
const endpoint = `/campaigns`;
|
||||
|
||||
responseData = await customerIoApiRequest.call(this, 'GET', endpoint, body, 'beta');
|
||||
responseData = responseData.campaigns;
|
||||
}
|
||||
|
||||
if (operation === 'getMetrics') {
|
||||
const campaignId = this.getNodeParameter('campaignId', i) as number;
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const period = this.getNodeParameter('period', i) as string;
|
||||
let endpoint = `/campaigns/${campaignId}/metrics`;
|
||||
|
||||
if (period !== 'days') {
|
||||
endpoint = `${endpoint}?period=${period}`;
|
||||
}
|
||||
if (additionalFields.steps) {
|
||||
body.steps = additionalFields.steps as number;
|
||||
}
|
||||
if (additionalFields.type) {
|
||||
if (additionalFields.type === 'urbanAirship') {
|
||||
additionalFields.type = 'urban_airship';
|
||||
} else {
|
||||
body.type = additionalFields.type as string;
|
||||
}
|
||||
}
|
||||
|
||||
responseData = await customerIoApiRequest.call(this, 'GET', endpoint, body, 'beta');
|
||||
responseData = responseData.metric;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'customer') {
|
||||
|
||||
if (operation === 'upsert') {
|
||||
const id = this.getNodeParameter('id', i) as number;
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.customProperties) {
|
||||
const data: any = {}; // tslint:disable-line:no-any
|
||||
//@ts-ignore
|
||||
additionalFields.customProperties.customProperty.map(property => {
|
||||
data[property.key] = property.value;
|
||||
});
|
||||
|
||||
body.data = data;
|
||||
}
|
||||
|
||||
if (additionalFields.email) {
|
||||
body.email = additionalFields.email as string;
|
||||
}
|
||||
|
||||
if (additionalFields.createdAt) {
|
||||
body.created_at = new Date(additionalFields.createdAt as string).getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
const endpoint = `/customers/${id}`;
|
||||
|
||||
responseData = await customerIoApiRequest.call(this, 'PUT', endpoint, body, 'tracking');
|
||||
|
||||
responseData = Object.assign({ id }, body);
|
||||
}
|
||||
|
||||
if (operation === 'delete') {
|
||||
const id = this.getNodeParameter('id', i) as number;
|
||||
|
||||
body.id = id;
|
||||
|
||||
const endpoint = `/customers/${id}`;
|
||||
|
||||
await customerIoApiRequest.call(this, 'DELETE', endpoint, body, 'tracking');
|
||||
|
||||
responseData = {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'event') {
|
||||
if (operation === 'track') {
|
||||
const customerId = this.getNodeParameter('customerId', i) as number;
|
||||
const eventName = this.getNodeParameter('eventName', i) as string;
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
body.name = eventName;
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const data: any = {}; // tslint:disable-line:no-any
|
||||
|
||||
if (additionalFields.customAttributes) {
|
||||
//@ts-ignore
|
||||
additionalFields.customAttributes.customAttribute.map(property => {
|
||||
data[property.key] = property.value;
|
||||
});
|
||||
}
|
||||
|
||||
if (additionalFields.type) {
|
||||
data.type = additionalFields.type as string;
|
||||
}
|
||||
|
||||
body.data = data;
|
||||
}
|
||||
|
||||
const endpoint = `/customers/${customerId}/events`;
|
||||
|
||||
await customerIoApiRequest.call(this, 'POST', endpoint, body, 'tracking');
|
||||
responseData = {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (operation === 'trackAnonymous') {
|
||||
const eventName = this.getNodeParameter('eventName', i) as string;
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
|
||||
body.name = eventName;
|
||||
|
||||
if (jsonParameters) {
|
||||
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
|
||||
|
||||
if (additionalFieldsJson !== '') {
|
||||
|
||||
if (validateJSON(additionalFieldsJson) !== undefined) {
|
||||
|
||||
Object.assign(body, JSON.parse(additionalFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const data: any = {}; // tslint:disable-line:no-any
|
||||
|
||||
if (additionalFields.customAttributes) {
|
||||
//@ts-ignore
|
||||
additionalFields.customAttributes.customAttribute.map(property => {
|
||||
data[property.key] = property.value;
|
||||
});
|
||||
}
|
||||
body.data = data;
|
||||
}
|
||||
|
||||
const endpoint = `/events`;
|
||||
await customerIoApiRequest.call(this, 'POST', endpoint, body, 'tracking');
|
||||
|
||||
responseData = {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'segment') {
|
||||
const segmentId = this.getNodeParameter('segmentId', i) as number;
|
||||
const customerIds = this.getNodeParameter('customerIds', i) as string;
|
||||
|
||||
body.id = segmentId;
|
||||
body.ids = customerIds.split(',');
|
||||
|
||||
let endpoint = '';
|
||||
|
||||
if (operation === 'add') {
|
||||
endpoint = `/segments/${segmentId}/add_customers`;
|
||||
} else {
|
||||
endpoint = `/segments/${segmentId}/remove_customers`;
|
||||
}
|
||||
|
||||
responseData = await customerIoApiRequest.call(this, 'POST', endpoint, body, 'tracking');
|
||||
|
||||
responseData = {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as unknown as IDataObject);
|
||||
}
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
apiRequest,
|
||||
customerIoApiRequest,
|
||||
eventExists,
|
||||
} from './GenericFunctions';
|
||||
|
||||
|
@ -34,7 +34,7 @@ export class CustomerIoTrigger implements INodeType {
|
|||
description: 'Starts the workflow on a Customer.io update. (Beta)',
|
||||
defaults: {
|
||||
name: 'Customer.io Trigger',
|
||||
color: '#7131ff',
|
||||
color: '#ffcd00',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
|
@ -237,7 +237,7 @@ export class CustomerIoTrigger implements INodeType {
|
|||
|
||||
const endpoint = '/reporting_webhooks';
|
||||
|
||||
let { reporting_webhooks: webhooks } = await apiRequest.call(this, 'GET', endpoint, {});
|
||||
let { reporting_webhooks: webhooks } = await customerIoApiRequest.call(this, 'GET', endpoint, {}, 'beta');
|
||||
|
||||
if (webhooks === null) {
|
||||
webhooks = [];
|
||||
|
@ -295,7 +295,7 @@ export class CustomerIoTrigger implements INodeType {
|
|||
events: data,
|
||||
};
|
||||
|
||||
webhook = await apiRequest.call(this, 'POST', endpoint, body);
|
||||
webhook = await customerIoApiRequest.call(this, 'POST', endpoint, body, 'beta');
|
||||
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
webhookData.webhookId = webhook.id as string;
|
||||
|
@ -307,7 +307,7 @@ export class CustomerIoTrigger implements INodeType {
|
|||
if (webhookData.webhookId !== undefined) {
|
||||
const endpoint = `/reporting_webhooks/${webhookData.webhookId}`;
|
||||
try {
|
||||
await apiRequest.call(this, 'DELETE', endpoint, {});
|
||||
await customerIoApiRequest.call(this, 'DELETE', endpoint, {}, 'beta');
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
|
295
packages/nodes-base/nodes/CustomerIo/EventDescription.ts
Normal file
295
packages/nodes-base/nodes/CustomerIo/EventDescription.ts
Normal file
|
@ -0,0 +1,295 @@
|
|||
import { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const eventOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Track',
|
||||
value: 'track',
|
||||
description: 'Track a customer event.',
|
||||
},
|
||||
{
|
||||
name: 'Track Anonymous',
|
||||
value: 'trackAnonymous',
|
||||
description: 'Track an anonymous event.',
|
||||
},
|
||||
],
|
||||
default: 'track',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const eventFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* event:track */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Customer ID',
|
||||
name: 'customerId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'track',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The unique identifier for the customer.',
|
||||
},
|
||||
{
|
||||
displayName: 'Event Name',
|
||||
name: 'eventName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'track',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'Name of the event to track.',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'track',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'track',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Object of values to set as described <a href="https://customer.io/docs/api-triggered-data-format#basic-data-formatting" target="_blank">here</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'track',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
]
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Attributes',
|
||||
name: 'customAttributes',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Attribute',
|
||||
name: 'customAttribute',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Attribute name.',
|
||||
placeholder: 'Price',
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Attribute value.',
|
||||
placeholder: '25.50',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Used to change event type. For Page View events set to "page".',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* event:track anonymous */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Event Name',
|
||||
name: 'eventName',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'trackAnonymous',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The unique identifier for the customer.',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parameters',
|
||||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'trackAnonymous',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'trackAnonymous',
|
||||
],
|
||||
jsonParameters: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Object of values to set as described <a href="https://customer.io/docs/api-triggered-data-format#basic-data-formatting" target="_blank">here</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'trackAnonymous',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
]
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Attributes',
|
||||
name: 'customAttributes',
|
||||
type: 'fixedCollection',
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Attribute',
|
||||
name: 'customAttribute',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Attribute name.',
|
||||
placeholder: 'Price',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Attribute value.',
|
||||
placeholder: '25.50',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -16,7 +16,7 @@ import {
|
|||
get,
|
||||
} from 'lodash';
|
||||
|
||||
export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||
export async function customerIoApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, baseApi?: string, query?: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('customerIoApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
|
@ -28,14 +28,26 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
|
|||
const options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${credentials.apiKey}`,
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs: query,
|
||||
uri: `https://beta-api.customer.io/v1/api${endpoint}`,
|
||||
uri: '',
|
||||
json: true,
|
||||
};
|
||||
|
||||
if (baseApi === 'tracking') {
|
||||
options.uri = `https://track.customer.io/api/v1${endpoint}`;
|
||||
const basicAuthKey = Buffer.from(`${credentials.trackingSiteId}:${credentials.trackingApiKey}`).toString('base64');
|
||||
Object.assign(options.headers, { 'Authorization': `Basic ${basicAuthKey}` });
|
||||
} else if (baseApi === 'api') {
|
||||
options.uri = `https://api.customer.io/v1/api${endpoint}`;
|
||||
const basicAuthKey = Buffer.from(`${credentials.trackingSiteId}:${credentials.trackingApiKey}`).toString('base64');
|
||||
Object.assign(options.headers, { 'Authorization': `Basic ${basicAuthKey}` });
|
||||
} else if (baseApi === 'beta') {
|
||||
options.uri = `https://beta-api.customer.io/v1/api${endpoint}`;
|
||||
Object.assign(options.headers, { 'Authorization': `Bearer ${credentials.appApiKey as string}` });
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
|
@ -63,3 +75,13 @@ export function eventExists(currentEvents: string[], webhookEvents: IDataObject)
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(json!);
|
||||
} catch (exception) {
|
||||
result = undefined;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
73
packages/nodes-base/nodes/CustomerIo/SegmentDescription.ts
Normal file
73
packages/nodes-base/nodes/CustomerIo/SegmentDescription.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const segmentOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'segment',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add Customer',
|
||||
value: 'add',
|
||||
},
|
||||
{
|
||||
name: 'Remove Customer',
|
||||
value: 'remove',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const segmentFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* segment:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Segment ID',
|
||||
name: 'segmentId',
|
||||
type: 'number',
|
||||
required: true,
|
||||
default: 0,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'segment',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
'remove',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The unique identifier of the segment.',
|
||||
},
|
||||
{
|
||||
displayName: 'Customer IDs',
|
||||
name: 'customerIds',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'segment',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
'remove',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'A list of customer ids to add to the segment.',
|
||||
},
|
||||
] as INodeProperties[];
|
BIN
packages/nodes-base/nodes/CustomerIo/customerio.png
Normal file
BIN
packages/nodes-base/nodes/CustomerIo/customerio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -17,6 +17,24 @@ import {
|
|||
import * as ftpClient from 'promise-ftp';
|
||||
import * as sftpClient from 'ssh2-sftp-client';
|
||||
|
||||
interface ReturnFtpItem {
|
||||
type: string;
|
||||
name: string;
|
||||
size: number;
|
||||
accessTime: Date;
|
||||
modifyTime: Date;
|
||||
rights: {
|
||||
user: string;
|
||||
group: string;
|
||||
other: string;
|
||||
};
|
||||
owner: string | number;
|
||||
group: string | number;
|
||||
target: string;
|
||||
sticky?: boolean;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export class Ftp implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'FTP',
|
||||
|
@ -220,6 +238,21 @@ export class Ftp implements INodeType {
|
|||
description: 'Path of directory to list contents of.',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Recursive',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
},
|
||||
name: 'recursive',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Return object representing all directories / objects recursively found within SFTP server',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -234,6 +267,7 @@ export class Ftp implements INodeType {
|
|||
|
||||
let credentials: ICredentialDataDecryptedObject | undefined = undefined;
|
||||
const protocol = this.getNodeParameter('protocol', 0) as string;
|
||||
|
||||
if (protocol === 'sftp') {
|
||||
credentials = this.getCredentials('sftp');
|
||||
} else {
|
||||
|
@ -244,11 +278,11 @@ export class Ftp implements INodeType {
|
|||
throw new Error('Failed to get credentials!');
|
||||
}
|
||||
|
||||
let ftp: ftpClient;
|
||||
let sftp: sftpClient;
|
||||
let ftp : ftpClient;
|
||||
let sftp : sftpClient;
|
||||
|
||||
if (protocol === 'sftp') {
|
||||
sftp = new sftpClient();
|
||||
|
||||
await sftp.connect({
|
||||
host: credentials.host as string,
|
||||
port: credentials.port as number,
|
||||
|
@ -258,7 +292,6 @@ export class Ftp implements INodeType {
|
|||
|
||||
} else {
|
||||
ftp = new ftpClient();
|
||||
|
||||
await ftp.connect({
|
||||
host: credentials.host as string,
|
||||
port: credentials.port as number,
|
||||
|
@ -286,8 +319,16 @@ export class Ftp implements INodeType {
|
|||
const path = this.getNodeParameter('path', i) as string;
|
||||
|
||||
if (operation === 'list') {
|
||||
responseData = await sftp!.list(path);
|
||||
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
|
||||
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
||||
|
||||
if (recursive) {
|
||||
responseData = await callRecursiveList(path, sftp!, normalizeSFtpItem);
|
||||
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
|
||||
} else {
|
||||
responseData = await sftp!.list(path);
|
||||
responseData.forEach(item => normalizeSFtpItem(item as sftpClient.FileInfo, path));
|
||||
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'download') {
|
||||
|
@ -347,8 +388,16 @@ export class Ftp implements INodeType {
|
|||
const path = this.getNodeParameter('path', i) as string;
|
||||
|
||||
if (operation === 'list') {
|
||||
responseData = await ftp!.list(path);
|
||||
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
|
||||
const recursive = this.getNodeParameter('recursive', i) as boolean;
|
||||
|
||||
if (recursive) {
|
||||
responseData = await callRecursiveList(path, ftp!, normalizeFtpItem);
|
||||
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
|
||||
} else {
|
||||
responseData = await ftp!.list(path);
|
||||
responseData.forEach(item => normalizeFtpItem(item as ftpClient.ListingElement, path));
|
||||
returnItems.push.apply(returnItems, this.helpers.returnJsonArray(responseData as unknown as IDataObject[]));
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'download') {
|
||||
|
@ -432,3 +481,54 @@ export class Ftp implements INodeType {
|
|||
return [returnItems];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function normalizeFtpItem(input: ftpClient.ListingElement, path: string) {
|
||||
const item = input as unknown as ReturnFtpItem;
|
||||
item.modifyTime = input.date;
|
||||
item.path = `${path}${path.endsWith('/') ? '' : '/'}${item.name}`;
|
||||
// @ts-ignore
|
||||
item.date = undefined;
|
||||
}
|
||||
|
||||
|
||||
function normalizeSFtpItem(input: sftpClient.FileInfo, path: string) {
|
||||
const item = input as unknown as ReturnFtpItem;
|
||||
item.accessTime = new Date(input.accessTime);
|
||||
item.modifyTime = new Date(input.modifyTime);
|
||||
item.path = `${path}${path.endsWith('/') ? '' : '/'}${item.name}`;
|
||||
}
|
||||
|
||||
async function callRecursiveList(path: string, client: sftpClient | ftpClient, normalizeFunction: (input: ftpClient.ListingElement & sftpClient.FileInfo, path: string) => void) {
|
||||
const pathArray : string[] = [path];
|
||||
let currentPath = path;
|
||||
const directoryItems : sftpClient.FileInfo[] = [];
|
||||
let index = 0;
|
||||
|
||||
do {
|
||||
// tslint:disable-next-line: array-type
|
||||
const returnData : sftpClient.FileInfo[] | (string | ftpClient.ListingElement)[] = await client.list(pathArray[index]);
|
||||
|
||||
// @ts-ignore
|
||||
returnData.map((item : sftpClient.FileInfo) => {
|
||||
if ((pathArray[index] as string).endsWith('/')) {
|
||||
currentPath = `${pathArray[index]}${item.name}`;
|
||||
} else {
|
||||
currentPath = `${pathArray[index]}/${item.name}`;
|
||||
}
|
||||
|
||||
// Is directory
|
||||
if (item.type === 'd') {
|
||||
pathArray.push(currentPath);
|
||||
}
|
||||
|
||||
normalizeFunction(item as ftpClient.ListingElement & sftpClient.FileInfo, currentPath);
|
||||
directoryItems.push(item);
|
||||
});
|
||||
index++;
|
||||
|
||||
} while (index <= pathArray.length - 1);
|
||||
|
||||
|
||||
return directoryItems;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
getFileSha,
|
||||
} from './GenericFunctions';
|
||||
|
||||
|
||||
export class Github implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'GitHub',
|
||||
|
|
|
@ -35,6 +35,11 @@ export const contactOperations = [
|
|||
value: 'getAll',
|
||||
description: 'Retrieve all contacts',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a contact',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.'
|
||||
|
@ -347,6 +352,18 @@ export const contactFields = [
|
|||
},
|
||||
default: [],
|
||||
},
|
||||
{
|
||||
displayName: 'Honorific Prefix',
|
||||
name: 'honorificPrefix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Honorific Suffix',
|
||||
name: 'honorificSuffix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Middle Name',
|
||||
name: 'middleName',
|
||||
|
@ -935,4 +952,628 @@ export const contactFields = [
|
|||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* contact:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'multiOptions',
|
||||
options: [
|
||||
{
|
||||
name: '*',
|
||||
value: '*',
|
||||
},
|
||||
{
|
||||
name: 'Addresses',
|
||||
value: 'addresses',
|
||||
},
|
||||
{
|
||||
name: 'Biographies',
|
||||
value: 'biographies',
|
||||
},
|
||||
{
|
||||
name: 'Birthdays',
|
||||
value: 'birthdays',
|
||||
},
|
||||
{
|
||||
name: 'Cover Photos',
|
||||
value: 'coverPhotos',
|
||||
},
|
||||
{
|
||||
name: 'Email Addresses',
|
||||
value: 'emailAddresses',
|
||||
},
|
||||
{
|
||||
name: 'Events',
|
||||
value: 'events',
|
||||
},
|
||||
{
|
||||
name: 'Genders',
|
||||
value: 'genders',
|
||||
},
|
||||
{
|
||||
name: 'IM Clients',
|
||||
value: 'imClients',
|
||||
},
|
||||
{
|
||||
name: 'Interests',
|
||||
value: 'interests',
|
||||
},
|
||||
{
|
||||
name: 'Locales',
|
||||
value: 'locales',
|
||||
},
|
||||
{
|
||||
name: 'Memberships',
|
||||
value: 'memberships',
|
||||
},
|
||||
{
|
||||
name: 'Metadata',
|
||||
value: 'metadata',
|
||||
},
|
||||
{
|
||||
name: 'Names',
|
||||
value: 'names',
|
||||
},
|
||||
{
|
||||
name: 'Nicknames',
|
||||
value: 'nicknames',
|
||||
},
|
||||
{
|
||||
name: 'Occupations',
|
||||
value: 'occupations',
|
||||
},
|
||||
{
|
||||
name: 'Organizations',
|
||||
value: 'organizations',
|
||||
},
|
||||
{
|
||||
name: 'Phone Numbers',
|
||||
value: 'phoneNumbers',
|
||||
},
|
||||
{
|
||||
name: 'Photos',
|
||||
value: 'photos',
|
||||
},
|
||||
{
|
||||
name: 'Relations',
|
||||
value: 'relations',
|
||||
},
|
||||
{
|
||||
name: 'Residences',
|
||||
value: 'residences',
|
||||
},
|
||||
{
|
||||
name: 'Sip Addresses',
|
||||
value: 'sipAddresses',
|
||||
},
|
||||
{
|
||||
name: 'Skills',
|
||||
value: 'skills',
|
||||
},
|
||||
{
|
||||
name: 'URLs',
|
||||
value: 'urls',
|
||||
},
|
||||
{
|
||||
name: 'User Defined',
|
||||
value: 'userDefined',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'A field mask to restrict which fields on each person are returned. Multiple fields can be specified by separating them with commas.',
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
name: 'updateFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
resource: [
|
||||
'contact',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Etag',
|
||||
name: 'etag',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The etag field in the person is nedded to make sure the contact has not changed since your last read',
|
||||
},
|
||||
{
|
||||
displayName: 'Family Name',
|
||||
name: 'familyName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Given Name',
|
||||
name: 'givenName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Addresses',
|
||||
name: 'addressesUi',
|
||||
placeholder: 'Add Address',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'addressesValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Street Address',
|
||||
name: 'streetAddress',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'City',
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'City',
|
||||
},
|
||||
{
|
||||
displayName: 'Region',
|
||||
name: 'region',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Region',
|
||||
},
|
||||
{
|
||||
displayName: 'Country Code',
|
||||
name: 'countryCode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Postal Code',
|
||||
name: 'postalCode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Postal code',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Home',
|
||||
value: 'home',
|
||||
},
|
||||
{
|
||||
name: 'Work',
|
||||
value: 'work',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
value: 'other',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Birthday',
|
||||
name: 'birthday',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Company',
|
||||
name: 'companyUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Company',
|
||||
typeOptions: {
|
||||
multipleValues: true
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'companyValues',
|
||||
displayName: 'Company',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Current',
|
||||
name: 'current',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Domain',
|
||||
name: 'domain',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Custom Field',
|
||||
typeOptions: {
|
||||
multipleValues: true
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'customFieldsValues',
|
||||
displayName: 'Custom Field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
description: 'The end user specified key of the user defined data.',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
description: 'The end user specified value of the user defined data.',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Emails',
|
||||
name: 'emailsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Email',
|
||||
typeOptions: {
|
||||
multipleValues: true
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'emailsValues',
|
||||
displayName: 'Email',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Home',
|
||||
value: 'home',
|
||||
},
|
||||
{
|
||||
name: 'Work',
|
||||
value: 'work',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
value: 'other',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: `The type of the email address. The type can be custom or one of these predefined values`,
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The email address.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Events',
|
||||
name: 'eventsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Event',
|
||||
description: 'An event related to the person.',
|
||||
typeOptions: {
|
||||
multipleValues: true
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'eventsValues',
|
||||
displayName: 'Event',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Date',
|
||||
name: 'date',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'The date of the event.',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Anniversary',
|
||||
value: 'anniversary',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
value: 'other',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: `The type of the event. The type can be custom or one of these predefined values`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'File As',
|
||||
name: 'fileAs',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The name that should be used to sort the person in a list.',
|
||||
},
|
||||
{
|
||||
displayName: 'Group',
|
||||
name: 'group',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getGroups',
|
||||
},
|
||||
default: [],
|
||||
},
|
||||
{
|
||||
displayName: 'Honorific Prefix',
|
||||
name: 'honorificPrefix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Honorific Suffix',
|
||||
name: 'honorificSuffix',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Middle Name',
|
||||
name: 'middleName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Notes',
|
||||
name: 'biographies',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phoneUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Phone',
|
||||
typeOptions: {
|
||||
multipleValues: true
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'phoneValues',
|
||||
displayName: 'Phone',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Home',
|
||||
value: 'home',
|
||||
},
|
||||
{
|
||||
name: 'Work',
|
||||
value: 'work',
|
||||
},
|
||||
{
|
||||
name: 'Mobile',
|
||||
value: 'mobile',
|
||||
},
|
||||
{
|
||||
name: 'Home Fax',
|
||||
value: 'homeFax',
|
||||
},
|
||||
{
|
||||
name: 'Work Fax',
|
||||
value: 'workFax',
|
||||
},
|
||||
{
|
||||
name: 'Other Fax',
|
||||
value: 'otherFax',
|
||||
},
|
||||
{
|
||||
name: 'Pager',
|
||||
value: 'pager',
|
||||
},
|
||||
{
|
||||
name: 'Work Mobile',
|
||||
value: 'workMobile',
|
||||
},
|
||||
{
|
||||
name: 'Work Pager',
|
||||
value: 'workPager',
|
||||
},
|
||||
{
|
||||
name: 'Main',
|
||||
value: 'main',
|
||||
},
|
||||
{
|
||||
name: 'Google Voice',
|
||||
value: 'googleVoice',
|
||||
},
|
||||
{
|
||||
name: 'Other',
|
||||
value: 'other',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The phone number.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Relations',
|
||||
name: 'relationsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
placeholder: 'Add Relation',
|
||||
typeOptions: {
|
||||
multipleValues: true
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'relationsValues',
|
||||
displayName: 'Relation',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Person',
|
||||
name: 'person',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The name of the other person this relation refers to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Assistant',
|
||||
value: 'assistant',
|
||||
},
|
||||
{
|
||||
name: 'Brother',
|
||||
value: 'brother',
|
||||
},
|
||||
{
|
||||
name: 'Child',
|
||||
value: 'child',
|
||||
},
|
||||
{
|
||||
name: 'Domestic Partner',
|
||||
value: 'domesticPartner',
|
||||
},
|
||||
{
|
||||
name: 'Father',
|
||||
value: 'father',
|
||||
},
|
||||
{
|
||||
name: 'Friend',
|
||||
value: 'friend',
|
||||
},
|
||||
{
|
||||
name: 'Manager',
|
||||
value: 'manager',
|
||||
},
|
||||
{
|
||||
name: 'Mother',
|
||||
value: 'mother',
|
||||
},
|
||||
{
|
||||
name: 'Parent',
|
||||
value: 'parent',
|
||||
},
|
||||
{
|
||||
name: 'Referred By',
|
||||
value: 'referredBy',
|
||||
},
|
||||
{
|
||||
name: 'Relative',
|
||||
value: 'relative',
|
||||
},
|
||||
{
|
||||
name: 'Sister',
|
||||
value: 'sister',
|
||||
},
|
||||
{
|
||||
name: 'Spouse',
|
||||
value: 'spouse',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: `The person's relation to the other person. The type can be custom or one of these predefined values`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
|
|
@ -123,6 +123,16 @@ export class GoogleContacts implements INodeType {
|
|||
body.names[0].middleName = additionalFields.middleName as string;
|
||||
}
|
||||
|
||||
if (additionalFields.honorificPrefix) {
|
||||
//@ts-ignore
|
||||
body.names[0].honorificPrefix = additionalFields.honorificPrefix as string;
|
||||
}
|
||||
|
||||
if (additionalFields.honorificSuffix) {
|
||||
//@ts-ignore
|
||||
body.names[0].honorificSuffix = additionalFields.honorificSuffix as string;
|
||||
}
|
||||
|
||||
if (additionalFields.companyUi) {
|
||||
const companyValues = (additionalFields.companyUi as IDataObject).companyValues as IDataObject[];
|
||||
body.organizations = companyValues;
|
||||
|
@ -298,6 +308,182 @@ export class GoogleContacts implements INodeType {
|
|||
responseData[i].contactId = responseData[i].resourceName.split('/')[1];
|
||||
}
|
||||
}
|
||||
//https://developers.google.com/people/api/rest/v1/people/updateContact
|
||||
if (operation === 'update') {
|
||||
const updatePersonFields = [];
|
||||
|
||||
const contactId = this.getNodeParameter('contactId', i) as string;
|
||||
|
||||
const fields = this.getNodeParameter('fields', i) as string[];
|
||||
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
let etag;
|
||||
|
||||
if (updateFields.etag) {
|
||||
|
||||
etag = updateFields.etag as string;
|
||||
|
||||
} else {
|
||||
|
||||
const data = await googleApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/people/${contactId}`,
|
||||
{},
|
||||
{ personFields: 'Names' },
|
||||
);
|
||||
|
||||
etag = data.etag;
|
||||
|
||||
}
|
||||
|
||||
if (fields.includes('*')) {
|
||||
qs.personFields = allFields.join(',');
|
||||
} else {
|
||||
qs.personFields = (fields as string[]).join(',');
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
etag,
|
||||
names: [
|
||||
{},
|
||||
],
|
||||
};
|
||||
|
||||
if (updateFields.givenName) {
|
||||
//@ts-ignore
|
||||
body.names[0].givenName = updateFields.givenName as string;
|
||||
}
|
||||
|
||||
if (updateFields.familyName) {
|
||||
//@ts-ignore
|
||||
body.names[0].familyName = updateFields.familyName as string;
|
||||
}
|
||||
|
||||
if (updateFields.middleName) {
|
||||
//@ts-ignore
|
||||
body.names[0].middleName = updateFields.middleName as string;
|
||||
}
|
||||
|
||||
if (updateFields.honorificPrefix) {
|
||||
//@ts-ignore
|
||||
body.names[0].honorificPrefix = updateFields.honorificPrefix as string;
|
||||
}
|
||||
|
||||
if (updateFields.honorificSuffix) {
|
||||
//@ts-ignore
|
||||
body.names[0].honorificSuffix = updateFields.honorificSuffix as string;
|
||||
}
|
||||
|
||||
if (updateFields.companyUi) {
|
||||
const companyValues = (updateFields.companyUi as IDataObject).companyValues as IDataObject[];
|
||||
body.organizations = companyValues;
|
||||
updatePersonFields.push('organizations');
|
||||
}
|
||||
|
||||
if (updateFields.phoneUi) {
|
||||
const phoneValues = (updateFields.phoneUi as IDataObject).phoneValues as IDataObject[];
|
||||
body.phoneNumbers = phoneValues;
|
||||
updatePersonFields.push('phoneNumbers');
|
||||
}
|
||||
|
||||
if (updateFields.addressesUi) {
|
||||
const addressesValues = (updateFields.addressesUi as IDataObject).addressesValues as IDataObject[];
|
||||
body.addresses = addressesValues;
|
||||
updatePersonFields.push('addresses');
|
||||
}
|
||||
|
||||
if (updateFields.relationsUi) {
|
||||
const relationsValues = (updateFields.relationsUi as IDataObject).relationsValues as IDataObject[];
|
||||
body.relations = relationsValues;
|
||||
updatePersonFields.push('relations');
|
||||
}
|
||||
|
||||
if (updateFields.eventsUi) {
|
||||
const eventsValues = (updateFields.eventsUi as IDataObject).eventsValues as IDataObject[];
|
||||
for (let i = 0; i < eventsValues.length; i++) {
|
||||
const [month, day, year] = moment(eventsValues[i].date as string).format('MM/DD/YYYY').split('/');
|
||||
eventsValues[i] = {
|
||||
date: {
|
||||
day,
|
||||
month,
|
||||
year,
|
||||
},
|
||||
type: eventsValues[i].type,
|
||||
};
|
||||
}
|
||||
body.events = eventsValues;
|
||||
updatePersonFields.push('events');
|
||||
}
|
||||
|
||||
if (updateFields.birthday) {
|
||||
const [month, day, year] = moment(updateFields.birthday as string).format('MM/DD/YYYY').split('/');
|
||||
|
||||
body.birthdays = [
|
||||
{
|
||||
date: {
|
||||
day,
|
||||
month,
|
||||
year
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
updatePersonFields.push('birthdays');
|
||||
}
|
||||
|
||||
if (updateFields.emailsUi) {
|
||||
const emailsValues = (updateFields.emailsUi as IDataObject).emailsValues as IDataObject[];
|
||||
body.emailAddresses = emailsValues;
|
||||
updatePersonFields.push('emailAddresses');
|
||||
}
|
||||
|
||||
if (updateFields.biographies) {
|
||||
body.biographies = [
|
||||
{
|
||||
value: updateFields.biographies,
|
||||
contentType: 'TEXT_PLAIN',
|
||||
},
|
||||
];
|
||||
updatePersonFields.push('biographies');
|
||||
}
|
||||
|
||||
if (updateFields.customFieldsUi) {
|
||||
const customFieldsValues = (updateFields.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
|
||||
body.userDefined = customFieldsValues;
|
||||
updatePersonFields.push('userDefined');
|
||||
}
|
||||
|
||||
if (updateFields.group) {
|
||||
const memberships = (updateFields.group as string[]).map((groupId: string) => {
|
||||
return {
|
||||
contactGroupMembership: {
|
||||
contactGroupResourceName: groupId
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
body.memberships = memberships;
|
||||
updatePersonFields.push('memberships');
|
||||
}
|
||||
|
||||
if ((body.names as IDataObject[]).length > 0) {
|
||||
updatePersonFields.push('names');
|
||||
}
|
||||
|
||||
qs.updatePersonFields = updatePersonFields.join(',');
|
||||
|
||||
responseData = await googleApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/people/${contactId}:updateContact`,
|
||||
body,
|
||||
qs
|
||||
);
|
||||
|
||||
responseData.contactId = responseData.resourceName.split('/')[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
|
|
|
@ -476,7 +476,7 @@ export class Gmail implements INodeType {
|
|||
if (qs.labelIds == '') {
|
||||
delete qs.labelIds;
|
||||
} else {
|
||||
qs.labelIds = (qs.labelIds as string[]).join(',');
|
||||
qs.labelIds = qs.labelIds as string[];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -442,7 +442,7 @@ export const messageFields = [
|
|||
typeOptions: {
|
||||
loadOptionsMethod: 'getLabels',
|
||||
},
|
||||
default: '',
|
||||
default: [],
|
||||
description: 'Only return messages with labels that match all of the specified label IDs.',
|
||||
},
|
||||
{
|
||||
|
|
157
packages/nodes-base/nodes/MQTT/MqttTrigger.node.ts
Normal file
157
packages/nodes-base/nodes/MQTT/MqttTrigger.node.ts
Normal file
|
@ -0,0 +1,157 @@
|
|||
import {
|
||||
ITriggerFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ITriggerResponse,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as mqtt from 'mqtt';
|
||||
|
||||
import {
|
||||
IClientOptions,
|
||||
} from 'mqtt';
|
||||
|
||||
export class MqttTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'MQTT Trigger',
|
||||
name: 'mqttTrigger',
|
||||
icon: 'file:mqtt.png',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Listens to MQTT events',
|
||||
defaults: {
|
||||
name: 'MQTT Trigger',
|
||||
color: '#9b27af',
|
||||
},
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'mqtt',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Topics',
|
||||
name: 'topics',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Topics to subscribe to, multiple can be defined with comma.<br/>
|
||||
wildcard characters are supported (+ - for single level and # - for multi level)`,
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Only Message',
|
||||
name: 'onlyMessage',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Returns only the message property.',
|
||||
},
|
||||
{
|
||||
displayName: 'JSON Parse Message',
|
||||
name: 'jsonParseMessage',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Try to parse the message to an object.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
|
||||
|
||||
const credentials = this.getCredentials('mqtt');
|
||||
|
||||
if (!credentials) {
|
||||
throw new Error('Credentials are mandatory!');
|
||||
}
|
||||
|
||||
const topics = (this.getNodeParameter('topics') as string).split(',');
|
||||
|
||||
const options = this.getNodeParameter('options') as IDataObject;
|
||||
|
||||
if (!topics) {
|
||||
throw new Error('Topics are mandatory!');
|
||||
}
|
||||
|
||||
const protocol = credentials.protocol as string || 'mqtt';
|
||||
const host = credentials.host as string;
|
||||
const brokerUrl = `${protocol}://${host}`;
|
||||
const port = credentials.port as number || 1883;
|
||||
|
||||
const clientOptions: IClientOptions = {
|
||||
port,
|
||||
};
|
||||
|
||||
if (credentials.username && credentials.password) {
|
||||
clientOptions.username = credentials.username as string;
|
||||
clientOptions.password = credentials.password as string;
|
||||
}
|
||||
|
||||
const client = mqtt.connect(brokerUrl, clientOptions);
|
||||
|
||||
const self = this;
|
||||
|
||||
async function manualTriggerFunction() {
|
||||
await new Promise((resolve, reject) => {
|
||||
client.on('connect', () => {
|
||||
client.subscribe(topics, (err, granted) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
client.on('message', (topic: string, message: Buffer | string) => { // tslint:disable-line:no-any
|
||||
|
||||
let result: IDataObject = {};
|
||||
|
||||
message = message.toString() as string;
|
||||
|
||||
if (options.jsonParseMessage) {
|
||||
try {
|
||||
message = JSON.parse(message.toString());
|
||||
} catch (err) { }
|
||||
}
|
||||
|
||||
result.message = message;
|
||||
result.topic = topic;
|
||||
|
||||
if (options.onlyMessage) {
|
||||
//@ts-ignore
|
||||
result = message;
|
||||
}
|
||||
|
||||
self.emit([self.helpers.returnJsonArray([result])]);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
client.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
manualTriggerFunction();
|
||||
|
||||
async function closeFunction() {
|
||||
client.end();
|
||||
}
|
||||
|
||||
return {
|
||||
closeFunction,
|
||||
manualTriggerFunction,
|
||||
};
|
||||
}
|
||||
}
|
BIN
packages/nodes-base/nodes/MQTT/mqtt.png
Normal file
BIN
packages/nodes-base/nodes/MQTT/mqtt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
155
packages/nodes-base/nodes/S3/GenericFunctions.ts
Normal file
155
packages/nodes-base/nodes/S3/GenericFunctions.ts
Normal file
|
@ -0,0 +1,155 @@
|
|||
import {
|
||||
sign,
|
||||
} from 'aws4';
|
||||
|
||||
import {
|
||||
get,
|
||||
} from 'lodash';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
parseString,
|
||||
} from 'xml2js';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { URL } from 'url';
|
||||
|
||||
export async function s3ApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, bucket: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
let credentials;
|
||||
|
||||
credentials = this.getCredentials('s3');
|
||||
|
||||
if (credentials === undefined) {
|
||||
throw new Error('No credentials got returned!');
|
||||
}
|
||||
|
||||
if (!(credentials.endpoint as string).startsWith('http')) {
|
||||
throw new Error('HTTP(S) Scheme is required in endpoint definition');
|
||||
}
|
||||
|
||||
const endpoint = new URL(credentials.endpoint as string);
|
||||
|
||||
if (bucket) {
|
||||
if (credentials.forcePathStyle) {
|
||||
path = `/${bucket}${path}`;
|
||||
} else {
|
||||
endpoint.host = `${bucket}.${endpoint.host}`;
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.pathname = path;
|
||||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = {
|
||||
headers: headers || {},
|
||||
region: region || credentials.region,
|
||||
host: endpoint.host,
|
||||
method,
|
||||
path: `${path}?${queryToString(query).replace(/\+/g, '%2B')}`,
|
||||
service: 's3',
|
||||
body
|
||||
};
|
||||
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
method,
|
||||
qs: query,
|
||||
uri: endpoint.toString(),
|
||||
body: signOpts.body,
|
||||
};
|
||||
|
||||
if (Object.keys(option).length !== 0) {
|
||||
Object.assign(options, option);
|
||||
}
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
const errorMessage = error.response?.body.message || error.response?.body.Message || error.message;
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
if (errorMessage === 'The security token included in the request is invalid.') {
|
||||
throw new Error('The S3 credentials are not valid!');
|
||||
} else if (errorMessage.startsWith('The request signature we calculated does not match the signature you provided')) {
|
||||
throw new Error('The S3 credentials are not valid!');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`S3 error response [${error.statusCode}]: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function s3ApiRequestREST(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, bucket: string, method: string, path: string, body?: string, query: IDataObject = {}, headers?: object, options: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const response = await s3ApiRequest.call(this, bucket, method, path, body, query, headers, options, region);
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
export async function s3ApiRequestSOAP(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, bucket: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const response = await s3ApiRequest.call(this, bucket, method, path, body, query, headers, option, region);
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
parseString(response, { explicitArray: false }, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function s3ApiRequestSOAPAllItems(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, propertyName: string, service: string, method: string, path: string, body?: string, query: IDataObject = {}, headers: IDataObject = {}, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
do {
|
||||
responseData = await s3ApiRequestSOAP.call(this, service, method, path, body, query, headers, option, region);
|
||||
|
||||
//https://forums.aws.amazon.com/thread.jspa?threadID=55746
|
||||
if (get(responseData, `${propertyName.split('.')[0]}.NextContinuationToken`)) {
|
||||
query['continuation-token'] = get(responseData, `${propertyName.split('.')[0]}.NextContinuationToken`);
|
||||
}
|
||||
if (get(responseData, propertyName)) {
|
||||
if (Array.isArray(get(responseData, propertyName))) {
|
||||
returnData.push.apply(returnData, get(responseData, propertyName));
|
||||
} else {
|
||||
returnData.push(get(responseData, propertyName));
|
||||
}
|
||||
}
|
||||
if (query.limit && query.limit <= returnData.length) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
get(responseData, `${propertyName.split('.')[0]}.IsTruncated`) !== undefined &&
|
||||
get(responseData, `${propertyName.split('.')[0]}.IsTruncated`) !== 'false'
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
function queryToString(params: IDataObject) {
|
||||
return Object.keys(params).map(key => key + '=' + params[key]).join('&');
|
||||
}
|
640
packages/nodes-base/nodes/S3/S3.node.ts
Normal file
640
packages/nodes-base/nodes/S3/S3.node.ts
Normal file
|
@ -0,0 +1,640 @@
|
|||
|
||||
import {
|
||||
snakeCase,
|
||||
paramCase,
|
||||
} from 'change-case';
|
||||
|
||||
import {
|
||||
createHash,
|
||||
} from 'crypto';
|
||||
|
||||
import {
|
||||
Builder,
|
||||
} from 'xml2js';
|
||||
|
||||
import {
|
||||
BINARY_ENCODING,
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IBinaryKeyData,
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
bucketFields,
|
||||
bucketOperations,
|
||||
} from '../Aws/S3/BucketDescription';
|
||||
|
||||
import {
|
||||
folderFields,
|
||||
folderOperations,
|
||||
} from '../Aws/S3/FolderDescription';
|
||||
|
||||
import {
|
||||
fileFields,
|
||||
fileOperations,
|
||||
} from '../Aws/S3/FileDescription';
|
||||
|
||||
import {
|
||||
s3ApiRequestREST,
|
||||
s3ApiRequestSOAP,
|
||||
s3ApiRequestSOAPAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
export class S3 implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'S3',
|
||||
name: 's3',
|
||||
icon: 'file:s3.png',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Sends data to any S3-compatible service',
|
||||
defaults: {
|
||||
name: 'S3',
|
||||
color: '#d05b4b',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 's3',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Bucket',
|
||||
value: 'bucket',
|
||||
},
|
||||
{
|
||||
name: 'File',
|
||||
value: 'file',
|
||||
},
|
||||
{
|
||||
name: 'Folder',
|
||||
value: 'folder',
|
||||
},
|
||||
],
|
||||
default: 'file',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
// BUCKET
|
||||
...bucketOperations,
|
||||
...bucketFields,
|
||||
// FOLDER
|
||||
...folderOperations,
|
||||
...folderFields,
|
||||
// UPLOAD
|
||||
...fileOperations,
|
||||
...fileFields,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const qs: IDataObject = {};
|
||||
const headers: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (resource === 'bucket') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html
|
||||
if (operation === 'create') {
|
||||
|
||||
let credentials;
|
||||
|
||||
try {
|
||||
credentials = this.getCredentials('s3');
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.bucketObjectLockEnabled) {
|
||||
headers['x-amz-bucket-object-lock-enabled'] = additionalFields.bucketObjectLockEnabled as boolean;
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWrite) {
|
||||
headers['x-amz-grant-write'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
let region = credentials!.region as string;
|
||||
|
||||
if (additionalFields.region) {
|
||||
region = additionalFields.region as string;
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
CreateBucketConfiguration: {
|
||||
'$': {
|
||||
xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/',
|
||||
},
|
||||
}
|
||||
};
|
||||
let data = '';
|
||||
// if credentials has the S3 defaul region (us-east-1) the body (XML) does not have to be sent.
|
||||
if (region !== 'us-east-1') {
|
||||
// @ts-ignore
|
||||
body.CreateBucketConfiguration.LocationConstraint = [region];
|
||||
const builder = new Builder();
|
||||
data = builder.buildObject(body);
|
||||
}
|
||||
responseData = await s3ApiRequestSOAP.call(this, `${name}`, 'PUT', '', data, qs, headers);
|
||||
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
if (returnAll) {
|
||||
responseData = await s3ApiRequestSOAPAllItems.call(this, 'ListAllMyBucketsResult.Buckets.Bucket', '', 'GET', '');
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await s3ApiRequestSOAPAllItems.call(this, 'ListAllMyBucketsResult.Buckets.Bucket', '', 'GET', '', '', qs);
|
||||
responseData = responseData.slice(0, qs.limit);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'search') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', 0) as IDataObject;
|
||||
|
||||
if (additionalFields.prefix) {
|
||||
qs['prefix'] = additionalFields.prefix as string;
|
||||
}
|
||||
|
||||
if (additionalFields.encodingType) {
|
||||
qs['encoding-type'] = additionalFields.encodingType as string;
|
||||
}
|
||||
|
||||
if (additionalFields.delmiter) {
|
||||
qs['delimiter'] = additionalFields.delmiter as string;
|
||||
}
|
||||
|
||||
if (additionalFields.fetchOwner) {
|
||||
qs['fetch-owner'] = additionalFields.fetchOwner as string;
|
||||
}
|
||||
|
||||
if (additionalFields.startAfter) {
|
||||
qs['start-after'] = additionalFields.startAfter as string;
|
||||
}
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
qs['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._ as string;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await s3ApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', bucketName, 'GET', '', '', qs, {}, {}, region);
|
||||
} else {
|
||||
qs['max-keys'] = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', qs, {}, {}, region);
|
||||
responseData = responseData.ListBucketResult.Contents;
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData);
|
||||
} else {
|
||||
returnData.push(responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'folder') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
|
||||
if (operation === 'create') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const folderName = this.getNodeParameter('folderName', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
let path = `/${folderName}/`;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
if (additionalFields.parentFolderKey) {
|
||||
path = `/${additionalFields.parentFolderKey}${folderName}/`;
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = (snakeCase(additionalFields.storageClass as string)).toUpperCase();
|
||||
}
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'PUT', path, '', qs, headers, {}, region);
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html
|
||||
if (operation === 'delete') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const folderKey = this.getNodeParameter('folderKey', i) as string;
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await s3ApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', bucketName, 'GET', '/', '', { 'list-type': 2, prefix: folderKey }, {}, {}, region);
|
||||
|
||||
// folder empty then just delete it
|
||||
if (responseData.length === 0) {
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'DELETE', `/${folderKey}`, '', qs, {}, {}, region);
|
||||
|
||||
responseData = { deleted: [{ 'Key': folderKey }] };
|
||||
|
||||
} else {
|
||||
// delete everything inside the folder
|
||||
const body: IDataObject = {
|
||||
Delete: {
|
||||
'$': {
|
||||
xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/',
|
||||
},
|
||||
Object: [],
|
||||
},
|
||||
};
|
||||
|
||||
for (const childObject of responseData) {
|
||||
//@ts-ignore
|
||||
(body.Delete.Object as IDataObject[]).push({
|
||||
Key: childObject.Key as string
|
||||
});
|
||||
}
|
||||
|
||||
const builder = new Builder();
|
||||
const data = builder.buildObject(body);
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(data).digest('base64');
|
||||
|
||||
headers['Content-Type'] = 'application/xml';
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'POST', '/', data, { delete: '' }, headers, {}, region);
|
||||
|
||||
responseData = { deleted: responseData.DeleteResult.Deleted };
|
||||
}
|
||||
returnData.push(responseData);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'getAll') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
const options = this.getNodeParameter('options', 0) as IDataObject;
|
||||
|
||||
if (options.folderKey) {
|
||||
qs['prefix'] = options.folderKey as string;
|
||||
}
|
||||
|
||||
if (options.fetchOwner) {
|
||||
qs['fetch-owner'] = options.fetchOwner as string;
|
||||
}
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await s3ApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', bucketName, 'GET', '', '', qs, {}, {}, region);
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await s3ApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', bucketName, 'GET', '', '', qs, {}, {}, region);
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
responseData = responseData.filter((e: IDataObject) => (e.Key as string).endsWith('/') && e.Size === '0' && e.Key !== options.folderKey);
|
||||
if (qs.limit) {
|
||||
responseData = responseData.splice(0, qs.limit as number);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'file') {
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html
|
||||
if (operation === 'copy') {
|
||||
const sourcePath = this.getNodeParameter('sourcePath', i) as string;
|
||||
const destinationPath = this.getNodeParameter('destinationPath', i) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
headers['x-amz-copy-source'] = sourcePath;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = (snakeCase(additionalFields.storageClass as string)).toUpperCase();
|
||||
}
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
if (additionalFields.lockLegalHold) {
|
||||
headers['x-amz-object-lock-legal-hold'] = (additionalFields.lockLegalHold as boolean) ? 'ON' : 'OFF';
|
||||
}
|
||||
if (additionalFields.lockMode) {
|
||||
headers['x-amz-object-lock-mode'] = (additionalFields.lockMode as string).toUpperCase();
|
||||
}
|
||||
if (additionalFields.lockRetainUntilDate) {
|
||||
headers['x-amz-object-lock-retain-until-date'] = additionalFields.lockRetainUntilDate as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryption) {
|
||||
headers['x-amz-server-side-encryption'] = additionalFields.serverSideEncryption as string;
|
||||
}
|
||||
if (additionalFields.encryptionAwsKmsKeyId) {
|
||||
headers['x-amz-server-side-encryption-aws-kms-key-id'] = additionalFields.encryptionAwsKmsKeyId as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryptionContext) {
|
||||
headers['x-amz-server-side-encryption-context'] = additionalFields.serverSideEncryptionContext as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerAlgorithm) {
|
||||
headers['x-amz-server-side-encryption-customer-algorithm'] = additionalFields.serversideEncryptionCustomerAlgorithm as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKey) {
|
||||
headers['x-amz-server-side-encryption-customer-key'] = additionalFields.serversideEncryptionCustomerKey as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKeyMD5) {
|
||||
headers['x-amz-server-side-encryption-customer-key-MD5'] = additionalFields.serversideEncryptionCustomerKeyMD5 as string;
|
||||
}
|
||||
if (additionalFields.taggingDirective) {
|
||||
headers['x-amz-tagging-directive'] = (additionalFields.taggingDirective as string).toUpperCase();
|
||||
}
|
||||
if (additionalFields.metadataDirective) {
|
||||
headers['x-amz-metadata-directive'] = (additionalFields.metadataDirective as string).toUpperCase();
|
||||
}
|
||||
|
||||
const destinationParts = destinationPath.split('/');
|
||||
|
||||
const bucketName = destinationParts[1];
|
||||
|
||||
const destination = `/${destinationParts.slice(2, destinationParts.length).join('/')}`;
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'PUT', destination, '', qs, headers, {}, region);
|
||||
returnData.push(responseData.CopyObjectResult);
|
||||
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
|
||||
if (operation === 'download') {
|
||||
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
|
||||
const fileKey = this.getNodeParameter('fileKey', i) as string;
|
||||
|
||||
const fileName = fileKey.split('/')[fileKey.split('/').length - 1];
|
||||
|
||||
if (fileKey.substring(fileKey.length - 1) === '/') {
|
||||
throw new Error('Downloding a whole directory is not yet supported, please provide a file key');
|
||||
}
|
||||
|
||||
let region = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
region = region.LocationConstraint._;
|
||||
|
||||
const response = await s3ApiRequestREST.call(this, bucketName, 'GET', `/${fileKey}`, '', qs, {}, { encoding: null, resolveWithFullResponse: true }, region);
|
||||
|
||||
let mimeType: string | undefined;
|
||||
if (response.headers['content-type']) {
|
||||
mimeType = response.headers['content-type'];
|
||||
}
|
||||
|
||||
const newItem: INodeExecutionData = {
|
||||
json: items[i].json,
|
||||
binary: {},
|
||||
};
|
||||
|
||||
if (items[i].binary !== undefined) {
|
||||
// Create a shallow copy of the binary data so that the old
|
||||
// data references which do not get changed still stay behind
|
||||
// but the incoming data does not get changed.
|
||||
Object.assign(newItem.binary, items[i].binary);
|
||||
}
|
||||
|
||||
items[i] = newItem;
|
||||
|
||||
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
|
||||
|
||||
const data = Buffer.from(response.body as string, 'utf8');
|
||||
|
||||
items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName, mimeType);
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html
|
||||
if (operation === 'delete') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
|
||||
const fileKey = this.getNodeParameter('fileKey', i) as string;
|
||||
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
|
||||
if (options.versionId) {
|
||||
qs.versionId = options.versionId as string;
|
||||
}
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'DELETE', `/${fileKey}`, '', qs, {}, {}, region);
|
||||
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html
|
||||
if (operation === 'getAll') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;
|
||||
const options = this.getNodeParameter('options', 0) as IDataObject;
|
||||
|
||||
if (options.folderKey) {
|
||||
qs['prefix'] = options.folderKey as string;
|
||||
}
|
||||
|
||||
if (options.fetchOwner) {
|
||||
qs['fetch-owner'] = options.fetchOwner as string;
|
||||
}
|
||||
|
||||
qs['delimiter'] = '/';
|
||||
|
||||
qs['list-type'] = 2;
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (returnAll) {
|
||||
responseData = await s3ApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', bucketName, 'GET', '', '', qs, {}, {}, region);
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', 0) as number;
|
||||
responseData = await s3ApiRequestSOAPAllItems.call(this, 'ListBucketResult.Contents', bucketName, 'GET', '', '', qs, {}, {}, region);
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
responseData = responseData.filter((e: IDataObject) => !(e.Key as string).endsWith('/') && e.Size !== '0');
|
||||
if (qs.limit) {
|
||||
responseData = responseData.splice(0, qs.limit as number);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html
|
||||
if (operation === 'upload') {
|
||||
const bucketName = this.getNodeParameter('bucketName', i) as string;
|
||||
const fileName = this.getNodeParameter('fileName', i) as string;
|
||||
const isBinaryData = this.getNodeParameter('binaryData', i) as boolean;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const tagsValues = (this.getNodeParameter('tagsUi', i) as IDataObject).tagsValues as IDataObject[];
|
||||
let path = '/';
|
||||
let body;
|
||||
|
||||
if (additionalFields.requesterPays) {
|
||||
headers['x-amz-request-payer'] = 'requester';
|
||||
}
|
||||
|
||||
if (additionalFields.parentFolderKey) {
|
||||
path = `/${additionalFields.parentFolderKey}/`;
|
||||
}
|
||||
if (additionalFields.storageClass) {
|
||||
headers['x-amz-storage-class'] = (snakeCase(additionalFields.storageClass as string)).toUpperCase();
|
||||
}
|
||||
if (additionalFields.acl) {
|
||||
headers['x-amz-acl'] = paramCase(additionalFields.acl as string);
|
||||
}
|
||||
if (additionalFields.grantFullControl) {
|
||||
headers['x-amz-grant-full-control'] = '';
|
||||
}
|
||||
if (additionalFields.grantRead) {
|
||||
headers['x-amz-grant-read'] = '';
|
||||
}
|
||||
if (additionalFields.grantReadAcp) {
|
||||
headers['x-amz-grant-read-acp'] = '';
|
||||
}
|
||||
if (additionalFields.grantWriteAcp) {
|
||||
headers['x-amz-grant-write-acp'] = '';
|
||||
}
|
||||
if (additionalFields.lockLegalHold) {
|
||||
headers['x-amz-object-lock-legal-hold'] = (additionalFields.lockLegalHold as boolean) ? 'ON' : 'OFF';
|
||||
}
|
||||
if (additionalFields.lockMode) {
|
||||
headers['x-amz-object-lock-mode'] = (additionalFields.lockMode as string).toUpperCase();
|
||||
}
|
||||
if (additionalFields.lockRetainUntilDate) {
|
||||
headers['x-amz-object-lock-retain-until-date'] = additionalFields.lockRetainUntilDate as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryption) {
|
||||
headers['x-amz-server-side-encryption'] = additionalFields.serverSideEncryption as string;
|
||||
}
|
||||
if (additionalFields.encryptionAwsKmsKeyId) {
|
||||
headers['x-amz-server-side-encryption-aws-kms-key-id'] = additionalFields.encryptionAwsKmsKeyId as string;
|
||||
}
|
||||
if (additionalFields.serverSideEncryptionContext) {
|
||||
headers['x-amz-server-side-encryption-context'] = additionalFields.serverSideEncryptionContext as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerAlgorithm) {
|
||||
headers['x-amz-server-side-encryption-customer-algorithm'] = additionalFields.serversideEncryptionCustomerAlgorithm as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKey) {
|
||||
headers['x-amz-server-side-encryption-customer-key'] = additionalFields.serversideEncryptionCustomerKey as string;
|
||||
}
|
||||
if (additionalFields.serversideEncryptionCustomerKeyMD5) {
|
||||
headers['x-amz-server-side-encryption-customer-key-MD5'] = additionalFields.serversideEncryptionCustomerKeyMD5 as string;
|
||||
}
|
||||
if (tagsValues) {
|
||||
const tags: string[] = [];
|
||||
tagsValues.forEach((o: IDataObject) => { tags.push(`${o.key}=${o.value}`); });
|
||||
headers['x-amz-tagging'] = tags.join('&');
|
||||
}
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'GET', '', '', { location: '' });
|
||||
|
||||
const region = responseData.LocationConstraint._;
|
||||
|
||||
if (isBinaryData) {
|
||||
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0) as string;
|
||||
|
||||
if (items[i].binary === undefined) {
|
||||
throw new Error('No binary data exists on item!');
|
||||
}
|
||||
|
||||
if ((items[i].binary as IBinaryKeyData)[binaryPropertyName] === undefined) {
|
||||
throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
|
||||
}
|
||||
|
||||
const binaryData = (items[i].binary as IBinaryKeyData)[binaryPropertyName];
|
||||
|
||||
body = Buffer.from(binaryData.data, BINARY_ENCODING) as Buffer;
|
||||
|
||||
headers['Content-Type'] = binaryData.mimeType;
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(body).digest('base64');
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'PUT', `${path}${fileName || binaryData.fileName}`, body, qs, headers, {}, region);
|
||||
|
||||
} else {
|
||||
|
||||
const fileContent = this.getNodeParameter('fileContent', i) as string;
|
||||
|
||||
body = Buffer.from(fileContent, 'utf8');
|
||||
|
||||
headers['Content-Type'] = 'text/html';
|
||||
|
||||
headers['Content-MD5'] = createHash('md5').update(fileContent).digest('base64');
|
||||
|
||||
responseData = await s3ApiRequestSOAP.call(this, bucketName, 'PUT', `${path}${fileName}`, body, qs, headers, {}, region);
|
||||
}
|
||||
returnData.push({ success: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'file' && operation === 'download') {
|
||||
// For file downloads the files get attached to the existing items
|
||||
return this.prepareOutputData(items);
|
||||
} else {
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
||||
}
|
BIN
packages/nodes-base/nodes/S3/s3.png
Normal file
BIN
packages/nodes-base/nodes/S3/s3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -1,21 +1,25 @@
|
|||
import { OptionsWithUri } from 'request';
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
export async function salesforceApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('salesforceOAuth2Api');
|
||||
const subdomain = ((credentials!.accessTokenUrl as string).match(/https:\/\/(.+).salesforce\.com/) || [])[1];
|
||||
const options: OptionsWithUri = {
|
||||
method,
|
||||
body: method === "GET" ? undefined : body,
|
||||
qs,
|
||||
uri: uri || `https://${subdomain}.salesforce.com/services/data/v39.0${resource}`,
|
||||
uri: `https://${subdomain}.salesforce.com/services/data/v39.0${uri || endpoint}`,
|
||||
json: true
|
||||
};
|
||||
try {
|
||||
|
@ -39,7 +43,7 @@ export async function salesforceApiRequestAllItems(this: IExecuteFunctions | ILo
|
|||
|
||||
do {
|
||||
responseData = await salesforceApiRequest.call(this, method, endpoint, body, query, uri);
|
||||
uri = responseData.nextRecordsUrl;
|
||||
uri = `${endpoint}/${responseData.nextRecordsUrl?.split('/')?.pop()}`;
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
} while (
|
||||
responseData.nextRecordsUrl !== undefined &&
|
||||
|
|
|
@ -15,63 +15,87 @@ import {
|
|||
accountFields,
|
||||
accountOperations,
|
||||
} from './AccountDescription';
|
||||
|
||||
import {
|
||||
IAccount,
|
||||
} from './AccountInterface';
|
||||
|
||||
import {
|
||||
attachmentFields,
|
||||
attachmentOperations,
|
||||
} from './AttachmentDescription';
|
||||
|
||||
import {
|
||||
IAttachment,
|
||||
} from './AttachmentInterface';
|
||||
|
||||
import {
|
||||
ICampaignMember,
|
||||
} from './CampaignMemberInterface';
|
||||
|
||||
import {
|
||||
caseFields,
|
||||
caseOperations,
|
||||
} from './CaseDescription';
|
||||
|
||||
import {
|
||||
ICase,
|
||||
ICaseComment,
|
||||
} from './CaseInterface';
|
||||
|
||||
import {
|
||||
contactFields,
|
||||
contactOperations,
|
||||
} from './ContactDescription';
|
||||
|
||||
import {
|
||||
IContact,
|
||||
} from './ContactInterface';
|
||||
|
||||
import {
|
||||
salesforceApiRequest,
|
||||
salesforceApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
leadFields,
|
||||
leadOperations,
|
||||
} from './LeadDescription';
|
||||
|
||||
import {
|
||||
ILead,
|
||||
} from './LeadInterface';
|
||||
|
||||
import {
|
||||
INote,
|
||||
} from './NoteInterface';
|
||||
|
||||
import {
|
||||
opportunityFields,
|
||||
opportunityOperations,
|
||||
} from './OpportunityDescription';
|
||||
|
||||
import {
|
||||
IOpportunity,
|
||||
} from './OpportunityInterface';
|
||||
|
||||
import {
|
||||
taskFields,
|
||||
taskOperations,
|
||||
} from './TaskDescription';
|
||||
|
||||
import {
|
||||
ITask,
|
||||
} from './TaskInterface';
|
||||
|
||||
import {
|
||||
userFields,
|
||||
userOperations,
|
||||
} from './UserDescription';
|
||||
|
||||
import {
|
||||
IUser,
|
||||
} from './UserInterface';
|
||||
|
||||
export class Salesforce implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -135,7 +159,11 @@ export class Salesforce implements INodeType {
|
|||
value: 'task',
|
||||
description: 'Represents a business activity such as making a phone call or other to-do items. In the user interface, and records are collectively referred to as activities.',
|
||||
},
|
||||
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
description: 'Represents a person, which is one user in system.',
|
||||
},
|
||||
],
|
||||
default: 'lead',
|
||||
description: 'Resource to consume.',
|
||||
|
@ -154,6 +182,8 @@ export class Salesforce implements INodeType {
|
|||
...taskFields,
|
||||
...attachmentOperations,
|
||||
...attachmentFields,
|
||||
...userOperations,
|
||||
...userFields,
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -1885,6 +1915,35 @@ export class Salesforce implements INodeType {
|
|||
responseData = await salesforceApiRequest.call(this, 'GET', '/sobjects/attachment');
|
||||
}
|
||||
}
|
||||
if (resource === 'user') {
|
||||
//https://developer.salesforce.com/docs/api-explorer/sobject/User/get-user-id
|
||||
if (operation === 'get') {
|
||||
const userId = this.getNodeParameter('userId', i) as string;
|
||||
responseData = await salesforceApiRequest.call(this, 'GET', `/sobjects/user/${userId}`);
|
||||
}
|
||||
//https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_query.htm
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
const fields = ['id,name,email'];
|
||||
if (options.fields) {
|
||||
// @ts-ignore
|
||||
fields.push(...options.fields.split(','));
|
||||
}
|
||||
try {
|
||||
if (returnAll) {
|
||||
qs.q = `SELECT ${fields.join(',')} FROM User`;
|
||||
responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs);
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.q = `SELECT ${fields.join(',')} FROM User Limit ${limit}`;
|
||||
responseData = await salesforceApiRequestAllItems.call(this, 'records', 'GET', '/query', {}, qs);
|
||||
}
|
||||
} catch(err) {
|
||||
throw new Error(`Salesforce Error: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
|
|
126
packages/nodes-base/nodes/Salesforce/UserDescription.ts
Normal file
126
packages/nodes-base/nodes/Salesforce/UserDescription.ts
Normal file
|
@ -0,0 +1,126 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const userOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a user',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all users',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.'
|
||||
}
|
||||
] as INodeProperties[];
|
||||
|
||||
export const userFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'userId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Id of user that needs to be fetched'
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
}
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.'
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.'
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Fields to include separated by ,'
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
10
packages/nodes-base/nodes/Salesforce/UserInterface.ts
Normal file
10
packages/nodes-base/nodes/Salesforce/UserInterface.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export interface IUser {
|
||||
Alias?: string;
|
||||
Department?: string;
|
||||
Division?: string;
|
||||
Email?: string;
|
||||
IsActive?: boolean;
|
||||
MobilePhone?: string;
|
||||
Title?: string;
|
||||
Username?: string;
|
||||
}
|
528
packages/nodes-base/nodes/Segment/GroupDescription.ts
Normal file
528
packages/nodes-base/nodes/Segment/GroupDescription.ts
Normal file
|
@ -0,0 +1,528 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const groupOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'group',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add',
|
||||
description: 'Add a user to a group',
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const groupFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* group:add */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'userId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'group',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Group ID',
|
||||
name: 'groupId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'group',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'A Group ID is the unique identifier which you recognize a group by in your own database',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Traits',
|
||||
name: 'traits',
|
||||
placeholder: 'Add Trait',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'group',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'traitsUi',
|
||||
displayName: 'Trait',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'email',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Email address of a user',
|
||||
},
|
||||
{
|
||||
displayName: 'First Name',
|
||||
name: 'firstname',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'First name of a user',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Name',
|
||||
name: 'lastname',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Last name of a user',
|
||||
},
|
||||
{
|
||||
displayName: 'Gender',
|
||||
name: 'gender',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Gender of a user',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone',
|
||||
name: 'phone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Phone number of a user',
|
||||
},
|
||||
{
|
||||
displayName: 'Username',
|
||||
name: 'username',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'User’s username',
|
||||
},
|
||||
{
|
||||
displayName: 'Website',
|
||||
name: 'website',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Website of a user',
|
||||
},
|
||||
{
|
||||
displayName: 'Age',
|
||||
name: 'age',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
description: 'Age of a user',
|
||||
},
|
||||
{
|
||||
displayName: 'Avatar',
|
||||
name: 'avatar',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL to an avatar image for the user',
|
||||
},
|
||||
{
|
||||
displayName: 'Birthday',
|
||||
name: 'birthday',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'User’s birthday',
|
||||
},
|
||||
{
|
||||
displayName: 'Created At',
|
||||
name: 'createdAt',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Date the user’s account was first created',
|
||||
},
|
||||
{
|
||||
displayName: 'Description',
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Description of the user',
|
||||
},
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Unique ID in your database for a user',
|
||||
},
|
||||
{
|
||||
displayName: 'Company',
|
||||
name: 'company',
|
||||
placeholder: 'Add Company',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'companyUi',
|
||||
displayName: 'Company',
|
||||
values: [
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Industry',
|
||||
name: 'industry',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Employee Count',
|
||||
name: 'employeeCount',
|
||||
type: 'number',
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
displayName: 'Plan',
|
||||
name: 'plan',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Address',
|
||||
name: 'address',
|
||||
placeholder: 'Add Address',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'addressUi',
|
||||
displayName: 'Address',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Street',
|
||||
name: 'street',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'City',
|
||||
name: 'city',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Postal Code',
|
||||
name: 'postalCode',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'country',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Context',
|
||||
name: 'context',
|
||||
placeholder: 'Add Context',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'group',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'contextUi',
|
||||
displayName: 'Context',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Active',
|
||||
name: 'active',
|
||||
type: 'boolean',
|
||||
default: '',
|
||||
description: 'Whether a user is active',
|
||||
},
|
||||
{
|
||||
displayName: 'IP',
|
||||
name: 'ip',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Current user’s IP address.',
|
||||
},
|
||||
{
|
||||
displayName: 'Locale',
|
||||
name: 'locate',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Locale string for the current user, for example en-US.',
|
||||
},
|
||||
{
|
||||
displayName: 'Page',
|
||||
name: 'page',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Dictionary of information about the current page in the browser, containing hash, path, referrer, search, title and url',
|
||||
},
|
||||
{
|
||||
displayName: 'Timezone',
|
||||
name: 'timezone',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Timezones are sent as tzdata strings to add user timezone information which might be stripped from the timestamp, for example America/New_York',
|
||||
},
|
||||
{
|
||||
displayName: 'App',
|
||||
name: 'app',
|
||||
placeholder: 'Add App',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'appUi',
|
||||
displayName: 'App',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Version',
|
||||
name: 'version',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Build',
|
||||
name: 'build',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Campaign',
|
||||
name: 'campaign',
|
||||
placeholder: 'Campaign App',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'campaignUi',
|
||||
displayName: 'Campaign',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Source',
|
||||
name: 'source',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Medium',
|
||||
name: 'medium',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Term',
|
||||
name: 'term',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Content',
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Device',
|
||||
name: 'device',
|
||||
placeholder: 'Add Device',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'deviceUi',
|
||||
displayName: 'Device',
|
||||
values: [
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Manufacturer',
|
||||
name: 'manufacturer',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Model',
|
||||
name: 'model',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Version',
|
||||
name: 'version',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Integration',
|
||||
name: 'integrations',
|
||||
placeholder: 'Add Integration',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'group',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'integrationsUi',
|
||||
displayName: 'Integration',
|
||||
values: [
|
||||
{
|
||||
displayName: 'All',
|
||||
name: 'all',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Salesforce',
|
||||
name: 'salesforce',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
|
@ -1,4 +1,6 @@
|
|||
import { INodeProperties } from 'n8n-workflow';
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const identifyOperations = [
|
||||
{
|
||||
|
|
|
@ -1,27 +1,41 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
segmentApiRequest,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
groupOperations,
|
||||
groupFields,
|
||||
} from './GroupDescription';
|
||||
|
||||
import {
|
||||
identifyFields,
|
||||
identifyOperations,
|
||||
} from './IdentifyDescription';
|
||||
|
||||
import {
|
||||
IIdentify,
|
||||
} from './IdentifyInterface';
|
||||
|
||||
import {
|
||||
trackOperations,
|
||||
trackFields,
|
||||
} from './TrackDescription';
|
||||
import { ITrack } from './TrackInterface';
|
||||
|
||||
import {
|
||||
ITrack, IGroup,
|
||||
} from './TrackInterface';
|
||||
|
||||
import * as uuid from 'uuid/v4';
|
||||
|
||||
export class Segment implements INodeType {
|
||||
|
@ -43,7 +57,7 @@ export class Segment implements INodeType {
|
|||
{
|
||||
name: 'segmentApi',
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
|
@ -51,10 +65,15 @@ export class Segment implements INodeType {
|
|||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Group',
|
||||
value: 'group',
|
||||
description: 'Group lets you associate an identified user with a group',
|
||||
},
|
||||
{
|
||||
name: 'Identify',
|
||||
value: 'identify',
|
||||
description: 'Identify lets you tie a user to their actions.'
|
||||
description: 'Identify lets you tie a user to their actions'
|
||||
},
|
||||
{
|
||||
name: 'Track',
|
||||
|
@ -65,6 +84,8 @@ export class Segment implements INodeType {
|
|||
default: 'identify',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
...groupOperations,
|
||||
...groupFields,
|
||||
...identifyOperations,
|
||||
...trackOperations,
|
||||
...identifyFields,
|
||||
|
@ -80,7 +101,224 @@ export class Segment implements INodeType {
|
|||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'group') {
|
||||
//https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#group
|
||||
if (operation === 'add') {
|
||||
const userId = this.getNodeParameter('userId', i) as string;
|
||||
const groupId = this.getNodeParameter('groupId', i) as string;
|
||||
const traits = (this.getNodeParameter('traits', i) as IDataObject).traitsUi as IDataObject;
|
||||
const context = (this.getNodeParameter('context', i) as IDataObject).contextUi as IDataObject;
|
||||
const integrations = (this.getNodeParameter('integrations', i) as IDataObject).integrationsUi as IDataObject;
|
||||
const body: IGroup = {
|
||||
groupId,
|
||||
traits: {
|
||||
company: {},
|
||||
address: {},
|
||||
},
|
||||
context: {
|
||||
app: {},
|
||||
campaign: {},
|
||||
device: {},
|
||||
},
|
||||
integrations: {},
|
||||
};
|
||||
if (userId) {
|
||||
body.userId = userId as string;
|
||||
} else {
|
||||
body.anonymousId = uuid();
|
||||
}
|
||||
if (traits) {
|
||||
if (traits.email) {
|
||||
body.traits!.email = traits.email as string;
|
||||
}
|
||||
if (traits.firstname) {
|
||||
body.traits!.firstname = traits.firstname as string;
|
||||
}
|
||||
if (traits.lastname) {
|
||||
body.traits!.lastname = traits.lastname as string;
|
||||
}
|
||||
if (traits.gender) {
|
||||
body.traits!.gender = traits.gender as string;
|
||||
}
|
||||
if (traits.phone) {
|
||||
body.traits!.phone = traits.phone as string;
|
||||
}
|
||||
if (traits.username) {
|
||||
body.traits!.username = traits.username as string;
|
||||
}
|
||||
if (traits.website) {
|
||||
body.traits!.website = traits.website as string;
|
||||
}
|
||||
if (traits.age) {
|
||||
body.traits!.age = traits.age as number;
|
||||
}
|
||||
if (traits.avatar) {
|
||||
body.traits!.avatar = traits.avatar as string;
|
||||
}
|
||||
if (traits.birthday) {
|
||||
body.traits!.birthday = traits.birthday as string;
|
||||
}
|
||||
if (traits.createdAt) {
|
||||
body.traits!.createdAt = traits.createdAt as string;
|
||||
}
|
||||
if (traits.description) {
|
||||
body.traits!.description = traits.description as string;
|
||||
}
|
||||
if (traits.id) {
|
||||
body.traits!.id = traits.id as string;
|
||||
}
|
||||
if (traits.company) {
|
||||
const company = (traits.company as IDataObject).companyUi as IDataObject;
|
||||
if (company) {
|
||||
if (company.id) {
|
||||
//@ts-ignore
|
||||
body.traits.company.id = company.id as string;
|
||||
}
|
||||
if (company.name) {
|
||||
//@ts-ignore
|
||||
body.traits.company.name = company.name as string;
|
||||
}
|
||||
if (company.industry) {
|
||||
//@ts-ignore
|
||||
body.traits.company.industry = company.industry as string;
|
||||
}
|
||||
if (company.employeeCount) {
|
||||
//@ts-ignore
|
||||
body.traits.company.employeeCount = company.employeeCount as number;
|
||||
}
|
||||
if (company.plan) {
|
||||
//@ts-ignore
|
||||
body.traits.company.plan = company.plan as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (traits.address) {
|
||||
const address = (traits.address as IDataObject).addressUi as IDataObject;
|
||||
if (address) {
|
||||
if (address.street) {
|
||||
//@ts-ignore
|
||||
body.traits.address.street = address.street as string;
|
||||
}
|
||||
if (address.city) {
|
||||
//@ts-ignore
|
||||
body.traits.address.city = address.city as string;
|
||||
}
|
||||
if (address.state) {
|
||||
//@ts-ignore
|
||||
body.traits.address.state = address.state as string;
|
||||
}
|
||||
if (address.postalCode) {
|
||||
//@ts-ignore
|
||||
body.traits.address.postalCode = address.postalCode as string;
|
||||
}
|
||||
if (address.country) {
|
||||
//@ts-ignore
|
||||
body.traits.address.country = address.country as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (context) {
|
||||
if (context.active) {
|
||||
body.context!.active = context.active as boolean;
|
||||
}
|
||||
if (context.ip) {
|
||||
body.context!.ip = context.ip as string;
|
||||
}
|
||||
if (context.locate) {
|
||||
body.context!.locate = context.locate as string;
|
||||
}
|
||||
if (context.page) {
|
||||
body.context!.page = context.page as string;
|
||||
}
|
||||
if (context.timezone) {
|
||||
body.context!.timezone = context.timezone as string;
|
||||
}
|
||||
if (context.timezone) {
|
||||
body.context!.timezone = context.timezone as string;
|
||||
}
|
||||
if (context.app) {
|
||||
const app = (context.app as IDataObject).appUi as IDataObject;
|
||||
if (app) {
|
||||
if (app.name) {
|
||||
//@ts-ignore
|
||||
body.context.app.name = app.name as string;
|
||||
}
|
||||
if (app.version) {
|
||||
//@ts-ignore
|
||||
body.context.app.version = app.version as string;
|
||||
}
|
||||
if (app.build) {
|
||||
//@ts-ignore
|
||||
body.context.app.build = app.build as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (context.campaign) {
|
||||
const campaign = (context.campaign as IDataObject).campaignUi as IDataObject;
|
||||
if (campaign) {
|
||||
if (campaign.name) {
|
||||
//@ts-ignore
|
||||
body.context.campaign.name = campaign.name as string;
|
||||
}
|
||||
if (campaign.source) {
|
||||
//@ts-ignore
|
||||
body.context.campaign.source = campaign.source as string;
|
||||
}
|
||||
if (campaign.medium) {
|
||||
//@ts-ignore
|
||||
body.context.campaign.medium = campaign.medium as string;
|
||||
}
|
||||
if (campaign.term) {
|
||||
//@ts-ignore
|
||||
body.context.campaign.term = campaign.term as string;
|
||||
}
|
||||
if (campaign.content) {
|
||||
//@ts-ignore
|
||||
body.context.campaign.content = campaign.content as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.device) {
|
||||
const device = (context.device as IDataObject).deviceUi as IDataObject;
|
||||
if (device) {
|
||||
if (device.id) {
|
||||
//@ts-ignore
|
||||
body.context.device.id = device.id as string;
|
||||
}
|
||||
if (device.manufacturer) {
|
||||
//@ts-ignore
|
||||
body.context.device.manufacturer = device.manufacturer as string;
|
||||
}
|
||||
if (device.model) {
|
||||
//@ts-ignore
|
||||
body.context.device.model = device.model as string;
|
||||
}
|
||||
if (device.type) {
|
||||
//@ts-ignore
|
||||
body.context.device.type = device.type as string;
|
||||
}
|
||||
if (device.version) {
|
||||
//@ts-ignore
|
||||
body.context.device.version = device.version as string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (integrations) {
|
||||
if (integrations.all) {
|
||||
body.integrations!.all = integrations.all as boolean;
|
||||
}
|
||||
if (integrations.salesforce) {
|
||||
body.integrations!.salesforce = integrations.salesforce as boolean;
|
||||
}
|
||||
}
|
||||
responseData = await segmentApiRequest.call(this, 'POST', '/group', body);
|
||||
}
|
||||
}
|
||||
if (resource === 'identify') {
|
||||
//https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#identify
|
||||
if (operation === 'create') {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { INodeProperties } from 'n8n-workflow';
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const trackOperations = [
|
||||
{
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { IDataObject } from "n8n-workflow";
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export interface ITrack {
|
||||
event?: string;
|
||||
|
@ -11,3 +13,7 @@ export interface ITrack {
|
|||
properties?: IDataObject;
|
||||
integrations?: IDataObject;
|
||||
}
|
||||
|
||||
export interface IGroup extends ITrack{
|
||||
groupId: string;
|
||||
}
|
||||
|
|
204
packages/nodes-base/nodes/SentryIo/EventDescription.ts
Normal file
204
packages/nodes-base/nodes/SentryIo/EventDescription.ts
Normal file
|
@ -0,0 +1,204 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const eventOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get event by ID',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all events',
|
||||
}
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const eventFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* event:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the events belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Project Slug',
|
||||
name: 'projectSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getProjects',
|
||||
loadOptionsDependsOn: [
|
||||
'organizationSlug',
|
||||
],
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the project the events belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Full',
|
||||
name: 'full',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'If this is set to true, then the event payload will include the full event body, including the stack trace',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* event:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the events belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Project Slug',
|
||||
name: 'projectSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getProjects',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the project the events belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Event ID',
|
||||
name: 'eventId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'event',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The id of the event to retrieve (either the numeric primary-key or the hexadecimal id as reported by the raven client).',
|
||||
},
|
||||
] as INodeProperties[];
|
104
packages/nodes-base/nodes/SentryIo/GenericFunctions.ts
Normal file
104
packages/nodes-base/nodes/SentryIo/GenericFunctions.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
import {
|
||||
OptionsWithUri
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function sentryIoApiRequest(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
|
||||
const authentication = this.getNodeParameter('authentication', 0);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: {},
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri ||`https://sentry.io${resource}`,
|
||||
json: true
|
||||
};
|
||||
if (!Object.keys(body).length) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
if (Object.keys(option).length !== 0) {
|
||||
Object.assign(options, option);
|
||||
}
|
||||
|
||||
if (options.qs.limit) {
|
||||
delete options.qs.limit;
|
||||
}
|
||||
|
||||
try {
|
||||
if (authentication === 'accessToken') {
|
||||
|
||||
const credentials = this.getCredentials('sentryIoApi');
|
||||
|
||||
options.headers = {
|
||||
Authorization: `Bearer ${credentials?.token}`,
|
||||
};
|
||||
|
||||
//@ts-ignore
|
||||
return this.helpers.request(options);
|
||||
|
||||
} else {
|
||||
return await this.helpers.requestOAuth2!.call(this, 'sentryIoOAuth2Api', options);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw new Error(`Sentry.io Error: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function sentryApiRequestAllItems(this: IHookFunctions | IExecuteFunctions| ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
let link;
|
||||
|
||||
let uri: string | undefined;
|
||||
|
||||
do {
|
||||
responseData = await sentryIoApiRequest.call(this, method, resource, body, query, uri, { resolveWithFullResponse: true });
|
||||
link = responseData.headers.link;
|
||||
uri = getNext(link);
|
||||
returnData.push.apply(returnData, responseData.body);
|
||||
if (query.limit && (query.limit >= returnData.length)) {
|
||||
return;
|
||||
}
|
||||
} while (
|
||||
hasMore(link)
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
function getNext(link: string) {
|
||||
if (link === undefined) {
|
||||
return;
|
||||
}
|
||||
const next = link.split(',')[1];
|
||||
if (next.includes('rel="next"')) {
|
||||
return next.split(';')[0].replace('<', '').replace('>','').trim();
|
||||
}
|
||||
}
|
||||
|
||||
function hasMore(link: string) {
|
||||
if (link === undefined) {
|
||||
return;
|
||||
}
|
||||
const next = link.split(',')[1];
|
||||
if (next.includes('rel="next"')) {
|
||||
return next.includes('results="true"');
|
||||
}
|
||||
}
|
20
packages/nodes-base/nodes/SentryIo/Interface.ts
Normal file
20
packages/nodes-base/nodes/SentryIo/Interface.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
export interface ICommit {
|
||||
id: string;
|
||||
repository?: string;
|
||||
message?: string;
|
||||
patch_set?: IPatchSet[];
|
||||
author_name?: string;
|
||||
author_email?: string;
|
||||
timestamp?: Date;
|
||||
}
|
||||
|
||||
export interface IPatchSet {
|
||||
path: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface IRef {
|
||||
commit: string;
|
||||
repository: string;
|
||||
previousCommit?: string;
|
||||
}
|
300
packages/nodes-base/nodes/SentryIo/IssueDescription.ts
Normal file
300
packages/nodes-base/nodes/SentryIo/IssueDescription.ts
Normal file
|
@ -0,0 +1,300 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const issueOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete an issue',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get issue by ID',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all issues',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update an issue',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const issueFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issue:get/delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue ID',
|
||||
name: 'issueId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '1234',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'ID of issue to get',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issue:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the issues belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Project Slug',
|
||||
name: 'projectSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getProjects',
|
||||
loadOptionsDependsOn: [
|
||||
'organizationSlug',
|
||||
],
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the project the issues belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Stats Period',
|
||||
name: 'statsPeriod',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'Time period of stats',
|
||||
options: [
|
||||
{
|
||||
name: '14 Days',
|
||||
value: '14d'
|
||||
},
|
||||
{
|
||||
name: '24 Hours',
|
||||
value: '24h'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
displayName: 'Short ID lookup',
|
||||
name: 'shortIdLookUp',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'If this is set to true then short IDs are looked up by this function as well. This can cause the return value of the function to return an event issue of a different project which is why this is an opt-in',
|
||||
},
|
||||
]
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* issue:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Issue ID',
|
||||
name: 'issueId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '1234',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'ID of issue to get',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'issue',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Assigned to',
|
||||
name: 'assignedTo',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The actor id (or username) of the user or team that should be assigned to this issue',
|
||||
},
|
||||
{
|
||||
displayName: 'Has Seen',
|
||||
name: 'hasSeen',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'In case this API call is invoked with a user context this allows changing of the flag that indicates if the user has seen the event',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Bookmarked',
|
||||
name: 'isBookmarked',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'In case this API call is invoked with a user context this allows changing of the bookmark flag',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Public',
|
||||
name: 'isPublic',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Sets the issue to public or private',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Subscribed',
|
||||
name: 'isSubscribed',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'The new status for the issue',
|
||||
options: [
|
||||
{
|
||||
name: 'Ignored',
|
||||
value: 'ignored'
|
||||
},
|
||||
{
|
||||
name: 'Resolved',
|
||||
value: 'resolved'
|
||||
},
|
||||
{
|
||||
name: 'Resolved Next Release',
|
||||
value: 'resolvedInNextRelease'
|
||||
},
|
||||
{
|
||||
name: 'Unresolved',
|
||||
value: 'unresolved'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
] as INodeProperties[];
|
205
packages/nodes-base/nodes/SentryIo/OrganizationDescription.ts
Normal file
205
packages/nodes-base/nodes/SentryIo/OrganizationDescription.ts
Normal file
|
@ -0,0 +1,205 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const organizationOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'organization',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create an organization',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get organization by slug',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all organizations',
|
||||
}
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const organizationFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* organization:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'organization',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'organization',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'organization',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Member',
|
||||
name: 'member',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Restrict results to organizations which you have membership',
|
||||
},
|
||||
{
|
||||
displayName: 'Owner',
|
||||
name: 'owner',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Restrict results to organizations which you are the owner',
|
||||
},
|
||||
]
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* organization:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'organization',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the team should be created for',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* organization:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'organization',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the team should be created for',
|
||||
},
|
||||
{
|
||||
displayName: 'Agree to Terms',
|
||||
name: 'agreeTerms',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'organization',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Signaling you agree to the applicable terms of service and privacy policy of Sentry.io',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'organization',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Slug',
|
||||
name: 'slug',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The unique URL slug for this organization. If this is not provided a slug is automatically generated based on the name',
|
||||
},
|
||||
]
|
||||
},
|
||||
] as INodeProperties[];
|
194
packages/nodes-base/nodes/SentryIo/ProjectDescription.ts
Normal file
194
packages/nodes-base/nodes/SentryIo/ProjectDescription.ts
Normal file
|
@ -0,0 +1,194 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const projectOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'project',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get project by ID',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all projects',
|
||||
}
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const projectFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* project:create/get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'project',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
'get',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the events belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Project Slug',
|
||||
name: 'projectSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getProjects',
|
||||
loadOptionsDependsOn: [
|
||||
'organizationSlug',
|
||||
],
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'project',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the project to retrieve',
|
||||
},
|
||||
{
|
||||
displayName: 'Team Slug',
|
||||
name: 'teamSlug',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'project',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
'update',
|
||||
'delete',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the team to create a new project for',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'project',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The name for the new project',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'project',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Slug',
|
||||
name: 'slug',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Optionally a slug for the new project. If it’s not provided a slug is generated from the name',
|
||||
},
|
||||
]
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* project:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'project',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'project',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return',
|
||||
},
|
||||
] as INodeProperties[];
|
429
packages/nodes-base/nodes/SentryIo/ReleaseDescription.ts
Normal file
429
packages/nodes-base/nodes/SentryIo/ReleaseDescription.ts
Normal file
|
@ -0,0 +1,429 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const releaseOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a release',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get release by version identifier',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all releases',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const releaseFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* release:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the releases belong to',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Query',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'This parameter can be used to create a “starts with” filter for the version',
|
||||
},
|
||||
]
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* release:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the release belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Version',
|
||||
name: 'version',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The version identifier of the release',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* release:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the release belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Version',
|
||||
name: 'version',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: ' a version identifier for this release. Can be a version number, a commit hash etc',
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'A URL that points to the release. This can be the path to an online interface to the sourcecode for instance',
|
||||
},
|
||||
{
|
||||
displayName: 'Projects',
|
||||
name: 'projects',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getProjects',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'A list of project slugs that are involved in this release',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'release',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Date released',
|
||||
name: 'dateReleased',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'an optional date that indicates when the release went live. If not provided the current time is assumed',
|
||||
},
|
||||
{
|
||||
displayName: 'Commits',
|
||||
name: 'commits',
|
||||
description: 'an optional list of commit data to be associated with the release',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'commitProperties',
|
||||
displayName: 'Commit Properties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Id',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'the sha of the commit',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
displayName: 'Author Email',
|
||||
name: 'authorEmail',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Authors email',
|
||||
},
|
||||
{
|
||||
displayName: 'Author Name',
|
||||
name: 'authorName',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of author',
|
||||
},
|
||||
{
|
||||
displayName: 'Message',
|
||||
name: 'message',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Message of commit',
|
||||
},
|
||||
{
|
||||
displayName: 'Patch Set',
|
||||
name: 'patchSet',
|
||||
description: 'A list of the files that have been changed in the commit. Specifying the patch_set is necessary to power suspect commits and suggested assignees',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'patchSetProperties',
|
||||
displayName: 'Patch Set Properties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Path',
|
||||
name: 'path',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'he path to the file. Both forward and backward slashes are supported',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
default: '',
|
||||
description: 'he types of changes that happend in that commit',
|
||||
options: [
|
||||
{
|
||||
name: 'Add',
|
||||
value: 'add'
|
||||
},
|
||||
{
|
||||
name: 'Modify',
|
||||
value: 'modify'
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete'
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Repository',
|
||||
name: 'repository',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Repository name',
|
||||
},
|
||||
{
|
||||
displayName: 'Timestamp',
|
||||
name: 'timestamp',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Timestamp of commit',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Refs',
|
||||
name: 'refs',
|
||||
description: 'an optional way to indicate the start and end commits for each repository included in a release',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'refProperties',
|
||||
displayName: 'Ref Properties',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Commit',
|
||||
name: 'commit',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'the head sha of the commit',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
displayName: 'Repository',
|
||||
name: 'repository',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Repository name',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
displayName: 'Previous Commit',
|
||||
name: 'previousCommit',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'the sha of the HEAD of the previous release',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
] as INodeProperties[];
|
558
packages/nodes-base/nodes/SentryIo/SentryIo.node.ts
Normal file
558
packages/nodes-base/nodes/SentryIo/SentryIo.node.ts
Normal file
|
@ -0,0 +1,558 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
ILoadOptionsFunctions,
|
||||
INodePropertyOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
eventOperations,
|
||||
eventFields,
|
||||
} from './EventDescription';
|
||||
|
||||
import {
|
||||
issueOperations,
|
||||
issueFields,
|
||||
} from './IssueDescription';
|
||||
|
||||
import {
|
||||
organizationFields,
|
||||
organizationOperations,
|
||||
} from './OrganizationDescription';
|
||||
|
||||
import {
|
||||
projectOperations,
|
||||
projectFields,
|
||||
} from './ProjectDescription';
|
||||
|
||||
import {
|
||||
releaseOperations,
|
||||
releaseFields,
|
||||
} from './ReleaseDescription';
|
||||
|
||||
import {
|
||||
teamOperations,
|
||||
teamFields,
|
||||
} from './TeamDescription';
|
||||
|
||||
import {
|
||||
sentryIoApiRequest,
|
||||
sentryApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
import { ICommit, IPatchSet, IRef } from './Interface';
|
||||
|
||||
export class SentryIo implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Sentry.io',
|
||||
name: 'sentryIo',
|
||||
icon: 'file:sentryio.png',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Sentry.io API',
|
||||
defaults: {
|
||||
name: 'Sentry.io',
|
||||
color: '#000000',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'sentryIoOAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
'oAuth2',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sentryIoApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: [
|
||||
'accessToken',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Access Token',
|
||||
value: 'accessToken',
|
||||
},
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'oAuth2',
|
||||
},
|
||||
],
|
||||
default: 'accessToken',
|
||||
description: 'The resource to operate on.',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Event',
|
||||
value: 'event',
|
||||
},
|
||||
{
|
||||
name: 'Issue',
|
||||
value: 'issue',
|
||||
},
|
||||
{
|
||||
name: 'Project',
|
||||
value: 'project',
|
||||
},
|
||||
{
|
||||
name: 'Release',
|
||||
value: 'release',
|
||||
},
|
||||
{
|
||||
name: 'Organization',
|
||||
value: 'organization',
|
||||
},
|
||||
{
|
||||
name: 'Team',
|
||||
value: 'team',
|
||||
},
|
||||
],
|
||||
default: 'event',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
|
||||
// EVENT
|
||||
...eventOperations,
|
||||
...eventFields,
|
||||
|
||||
// ISSUE
|
||||
...issueOperations,
|
||||
...issueFields,
|
||||
|
||||
// ORGANIZATION
|
||||
...organizationOperations,
|
||||
...organizationFields,
|
||||
|
||||
// PROJECT
|
||||
...projectOperations,
|
||||
...projectFields,
|
||||
|
||||
// RELEASE
|
||||
...releaseOperations,
|
||||
...releaseFields,
|
||||
|
||||
// TEAM
|
||||
...teamOperations,
|
||||
...teamFields
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all organizations so they can be displayed easily
|
||||
async getOrganizations(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const organizations = await sentryApiRequestAllItems.call(this, 'GET', `/api/0/organizations/`, {});
|
||||
|
||||
for (const organization of organizations) {
|
||||
returnData.push({
|
||||
name: organization.slug,
|
||||
value: organization.slug,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name < b.name) { return -1; }
|
||||
if (a.name > b.name) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
return returnData;
|
||||
},
|
||||
// Get all projects so can be displayed easily
|
||||
async getProjects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const projects = await sentryApiRequestAllItems.call(this, 'GET', `/api/0/projects/`, {});
|
||||
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug') as string;
|
||||
|
||||
for (const project of projects) {
|
||||
|
||||
if (organizationSlug !== project.organization.slug) {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
name: project.slug,
|
||||
value: project.slug,
|
||||
});
|
||||
}
|
||||
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name < b.name) { return -1; }
|
||||
if (a.name > b.name) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
let responseData;
|
||||
const qs: IDataObject = {};
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'event') {
|
||||
if (operation === 'getAll') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const projectSlug = this.getNodeParameter('projectSlug', i) as string;
|
||||
const full = this.getNodeParameter('full', i) as boolean;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
const endpoint = `/api/0/projects/${organizationSlug}/${projectSlug}/events/`;
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
}
|
||||
|
||||
qs.full = full;
|
||||
|
||||
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'get') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const projectSlug = this.getNodeParameter('projectSlug', i) as string;
|
||||
const eventId = this.getNodeParameter('eventId', i) as string;
|
||||
|
||||
const endpoint = `/api/0/projects/${organizationSlug}/${projectSlug}/events/${eventId}/`;
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
|
||||
}
|
||||
}
|
||||
if (resource === 'issue') {
|
||||
if (operation === 'getAll') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const projectSlug = this.getNodeParameter('projectSlug', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
const endpoint = `/api/0/projects/${organizationSlug}/${projectSlug}/issues/`;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.statsPeriod) {
|
||||
qs.statsPeriod = additionalFields.statsPeriod as string;
|
||||
}
|
||||
if (additionalFields.shortIdLookup) {
|
||||
qs.shortIdLookup = additionalFields.shortIdLookup as boolean;
|
||||
}
|
||||
if (additionalFields.query) {
|
||||
qs.query = additionalFields.query as string;
|
||||
}
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
}
|
||||
|
||||
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
|
||||
}
|
||||
if (operation === 'get') {
|
||||
const issueId = this.getNodeParameter('issueId', i) as string;
|
||||
const endpoint = `/api/0/issues/${issueId}/`;
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const issueId = this.getNodeParameter('issueId', i) as string;
|
||||
const endpoint = `/api/0/issues/${issueId}/`;
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'DELETE', endpoint, qs);
|
||||
|
||||
responseData = { success: true };
|
||||
}
|
||||
if (operation === 'update') {
|
||||
const issueId = this.getNodeParameter('issueId', i) as string;
|
||||
const endpoint = `/api/0/issues/${issueId}/`;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.status) {
|
||||
qs.status = additionalFields.status as string;
|
||||
}
|
||||
if (additionalFields.assignedTo) {
|
||||
qs.assignedTo = additionalFields.assignedTo as string;
|
||||
}
|
||||
if (additionalFields.hasSeen) {
|
||||
qs.hasSeen = additionalFields.hasSeen as boolean;
|
||||
}
|
||||
if (additionalFields.isBookmarked) {
|
||||
qs.isBookmarked = additionalFields.isBookmarked as boolean;
|
||||
}
|
||||
if (additionalFields.isSubscribed) {
|
||||
qs.isSubscribed = additionalFields.isSubscribed as boolean;
|
||||
}
|
||||
if (additionalFields.isPublic) {
|
||||
qs.isPublic = additionalFields.isPublic as boolean;
|
||||
}
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'PUT', endpoint, qs);
|
||||
}
|
||||
}
|
||||
if (resource === 'organization') {
|
||||
if (operation === 'get') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const endpoint = `/api/0/organizations/${organizationSlug}/`;
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const endpoint = `/api/0/organizations/`;
|
||||
|
||||
if (additionalFields.member) {
|
||||
qs.member = additionalFields.member as boolean;
|
||||
}
|
||||
if (additionalFields.owner) {
|
||||
qs.owner = additionalFields.owner as boolean;
|
||||
}
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
}
|
||||
|
||||
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
|
||||
|
||||
if (responseData === undefined) {
|
||||
responseData = [];
|
||||
}
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'create') {
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const agreeTerms = this.getNodeParameter('agreeTerms', i) as boolean;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const endpoint = `/api/0/organizations/`;
|
||||
|
||||
qs.name = name;
|
||||
qs.agreeTerms = agreeTerms;
|
||||
|
||||
if (additionalFields.slug) {
|
||||
qs.slug = additionalFields.slug as string;
|
||||
}
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'POST', endpoint, qs);
|
||||
}
|
||||
}
|
||||
if (resource === 'project') {
|
||||
if (operation === 'get') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const projectSlug = this.getNodeParameter('projectSlug', i) as string;
|
||||
const endpoint = `/api/0/projects/${organizationSlug}/${projectSlug}/`;
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const endpoint = `/api/0/projects/`;
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
}
|
||||
|
||||
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'release') {
|
||||
if (operation === 'get') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const version = this.getNodeParameter('version', i) as string;
|
||||
const endpoint = `/api/0/organizations/${organizationSlug}/releases/${version}/`;
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const endpoint = `/api/0/organizations/${organizationSlug}/releases/`;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (additionalFields.query) {
|
||||
qs.query = additionalFields.query as string;
|
||||
}
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
}
|
||||
|
||||
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'create') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const endpoint = `/api/0/organizations/${organizationSlug}/releases/`;
|
||||
const version = this.getNodeParameter('version', i) as string;
|
||||
const url = this.getNodeParameter('url', i) as string;
|
||||
const projects = this.getNodeParameter('projects', i) as string[];
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
if (additionalFields.dateReleased) {
|
||||
qs.dateReleased = additionalFields.dateReleased as string;
|
||||
}
|
||||
|
||||
qs.version = version;
|
||||
qs.url = url;
|
||||
qs.projects = projects;
|
||||
|
||||
if (additionalFields.commits) {
|
||||
const commits: ICommit[] = [];
|
||||
//@ts-ignore
|
||||
// tslint:disable-next-line: no-any
|
||||
additionalFields.commits.commitProperties.map((commit: any) => {
|
||||
const commitObject: ICommit = { id: commit.id };
|
||||
|
||||
if (commit.repository) {
|
||||
commitObject.repository = commit.repository;
|
||||
}
|
||||
if (commit.message) {
|
||||
commitObject.message = commit.message;
|
||||
}
|
||||
if (commit.patchSet && Array.isArray(commit.patchSet)) {
|
||||
commit.patchSet.patchSetProperties.map((patchSet: IPatchSet) => {
|
||||
commitObject.patch_set?.push(patchSet);
|
||||
});
|
||||
}
|
||||
if (commit.authorName) {
|
||||
commitObject.author_name = commit.authorName;
|
||||
}
|
||||
if (commit.authorEmail) {
|
||||
commitObject.author_email = commit.authorEmail;
|
||||
}
|
||||
if (commit.timestamp) {
|
||||
commitObject.timestamp = commit.timestamp;
|
||||
}
|
||||
|
||||
commits.push(commitObject);
|
||||
});
|
||||
|
||||
qs.commits = commits;
|
||||
}
|
||||
if (additionalFields.refs) {
|
||||
const refs: IRef[] = [];
|
||||
//@ts-ignore
|
||||
additionalFields.refs.refProperties.map((ref: IRef) => {
|
||||
refs.push(ref);
|
||||
});
|
||||
|
||||
qs.refs = refs;
|
||||
}
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'POST', endpoint, qs);
|
||||
}
|
||||
}
|
||||
if (resource === 'team') {
|
||||
if (operation === 'get') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const teamSlug = this.getNodeParameter('teamSlug', i) as string;
|
||||
const endpoint = `/api/0/teams/${organizationSlug}/${teamSlug}/`;
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'GET', endpoint, qs);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const endpoint = `/api/0/organizations/${organizationSlug}/teams/`;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
}
|
||||
|
||||
responseData = await sentryApiRequestAllItems.call(this, 'GET', endpoint, {}, qs);
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'create') {
|
||||
const organizationSlug = this.getNodeParameter('organizationSlug', i) as string;
|
||||
const name = this.getNodeParameter('name', i) as string;
|
||||
const endpoint = `/api/0/organizations/${organizationSlug}/teams/`;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
|
||||
qs.name = name;
|
||||
|
||||
if (additionalFields.slug) {
|
||||
qs.slug = additionalFields.slug;
|
||||
}
|
||||
|
||||
responseData = await sentryIoApiRequest.call(this, 'POST', endpoint, qs);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
returnData.push.apply(returnData, responseData as IDataObject[]);
|
||||
} else {
|
||||
returnData.push(responseData as IDataObject);
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
290
packages/nodes-base/nodes/SentryIo/TeamDescription.ts
Normal file
290
packages/nodes-base/nodes/SentryIo/TeamDescription.ts
Normal file
|
@ -0,0 +1,290 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const teamOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a new team',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get team by slug',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all teams',
|
||||
}
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const teamFields = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* team:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization for which the teams should be listed',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* team:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the team belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Team Slug',
|
||||
name: 'teamSlug',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the team to get',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* team:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the team belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The name of the team',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Slug',
|
||||
name: 'slug',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The optional slug for this team. If not provided it will be auto generated from the name',
|
||||
},
|
||||
]
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* team:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Organization Slug',
|
||||
name: 'organizationSlug',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getOrganizations',
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'update', 'delete'
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the organization the team belongs to',
|
||||
},
|
||||
{
|
||||
displayName: 'Team Slug',
|
||||
name: 'teamSlug',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'update', 'delete'
|
||||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The slug of the team to get',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'team',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Slug',
|
||||
name: 'slug',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The new slug of the team. Must be unique and available',
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The new name of the team',
|
||||
},
|
||||
]
|
||||
},
|
||||
] as INodeProperties[];
|
BIN
packages/nodes-base/nodes/SentryIo/sentryio.png
Normal file
BIN
packages/nodes-base/nodes/SentryIo/sentryio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
|
@ -2,12 +2,16 @@ import {
|
|||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import { OptionsWithUri } from 'request';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
// Interface in n8n
|
||||
export interface IMarkupKeyboard {
|
||||
|
@ -138,7 +142,7 @@ export function addAdditionalFields(this: IExecuteFunctions, body: IDataObject,
|
|||
* @param {object} body
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, endpoint: string, body: object, query?: IDataObject): Promise<any> { // tslint:disable-line:no-any
|
||||
export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, endpoint: string, body: object, query?: IDataObject, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
const credentials = this.getCredentials('telegramApi');
|
||||
|
||||
if (credentials === undefined) {
|
||||
|
@ -157,9 +161,22 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
|
|||
json: true,
|
||||
};
|
||||
|
||||
if (Object.keys(option).length > 0) {
|
||||
Object.assign(options, option);
|
||||
}
|
||||
|
||||
if (Object.keys(body).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
if (Object.keys(query).length === 0) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
|
||||
if (error.statusCode === 401) {
|
||||
// Return a clear error
|
||||
throw new Error('The Telegram credentials are not valid!');
|
||||
|
@ -175,3 +192,16 @@ export async function apiRequest(this: IHookFunctions | IExecuteFunctions | ILoa
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function getImageBySize(photos: IDataObject[], size: string): IDataObject | undefined {
|
||||
|
||||
const sizes = {
|
||||
'small': 0,
|
||||
'medium': 1,
|
||||
'large': 2,
|
||||
} as IDataObject;
|
||||
|
||||
const index = sizes[size] as number;
|
||||
|
||||
return photos[index];
|
||||
}
|
||||
|
|
12
packages/nodes-base/nodes/Telegram/IEvent.ts
Normal file
12
packages/nodes-base/nodes/Telegram/IEvent.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export interface IEvent {
|
||||
message?: {
|
||||
photo?: [
|
||||
{
|
||||
file_id: string,
|
||||
},
|
||||
],
|
||||
document?: {
|
||||
file_id: string;
|
||||
},
|
||||
};
|
||||
}
|
|
@ -146,6 +146,11 @@ export class Telegram implements INodeType {
|
|||
value: 'editMessageText',
|
||||
description: 'Edit a text message',
|
||||
},
|
||||
{
|
||||
name: 'Send Animation',
|
||||
value: 'sendAnimation',
|
||||
description: 'Send an animated file',
|
||||
},
|
||||
{
|
||||
name: 'Send Audio',
|
||||
value: 'sendAudio',
|
||||
|
@ -209,6 +214,7 @@ export class Telegram implements INodeType {
|
|||
'member',
|
||||
'setDescription',
|
||||
'setTitle',
|
||||
'sendAnimation',
|
||||
'sendAudio',
|
||||
'sendChatAction',
|
||||
'sendDocument',
|
||||
|
@ -513,6 +519,29 @@ export class Telegram implements INodeType {
|
|||
|
||||
|
||||
|
||||
// ----------------------------------
|
||||
// message:sendAnimation
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Animation',
|
||||
name: 'file',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'sendAnimation'
|
||||
],
|
||||
resource: [
|
||||
'message',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Animation to send. Pass a file_id to send an animation that exists on the Telegram servers (recommended)<br />or pass an HTTP URL for Telegram to get an animation from the Internet.',
|
||||
},
|
||||
|
||||
|
||||
|
||||
// ----------------------------------
|
||||
// message:sendAudio
|
||||
// ----------------------------------
|
||||
|
@ -811,7 +840,7 @@ export class Telegram implements INodeType {
|
|||
|
||||
|
||||
// ----------------------------------
|
||||
// message:editMessageText/sendAudio/sendMessage/sendPhoto/sendSticker/sendVideo
|
||||
// message:editMessageText/sendAnimation/sendAudio/sendMessage/sendPhoto/sendSticker/sendVideo
|
||||
// ----------------------------------
|
||||
|
||||
{
|
||||
|
@ -820,6 +849,7 @@ export class Telegram implements INodeType {
|
|||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'sendAnimation',
|
||||
'sendDocument',
|
||||
'sendMessage',
|
||||
'sendPhoto',
|
||||
|
@ -1147,6 +1177,7 @@ export class Telegram implements INodeType {
|
|||
show: {
|
||||
operation: [
|
||||
'editMessageText',
|
||||
'sendAnimation',
|
||||
'sendDocument',
|
||||
'sendMessage',
|
||||
'sendMediaGroup',
|
||||
|
@ -1171,6 +1202,7 @@ export class Telegram implements INodeType {
|
|||
displayOptions: {
|
||||
show: {
|
||||
'/operation': [
|
||||
'sendAnimation',
|
||||
'sendAudio',
|
||||
'sendDocument',
|
||||
'sendPhoto',
|
||||
|
@ -1220,6 +1252,7 @@ export class Telegram implements INodeType {
|
|||
displayOptions: {
|
||||
show: {
|
||||
'/operation': [
|
||||
'sendAnimation',
|
||||
'sendAudio',
|
||||
'sendVideo',
|
||||
],
|
||||
|
@ -1238,6 +1271,7 @@ export class Telegram implements INodeType {
|
|||
displayOptions: {
|
||||
show: {
|
||||
'/operation': [
|
||||
'sendAnimation',
|
||||
'sendVideo',
|
||||
],
|
||||
},
|
||||
|
@ -1263,6 +1297,7 @@ export class Telegram implements INodeType {
|
|||
show: {
|
||||
'/operation': [
|
||||
'editMessageText',
|
||||
'sendAnimation',
|
||||
'sendAudio',
|
||||
'sendMessage',
|
||||
'sendPhoto',
|
||||
|
@ -1325,6 +1360,7 @@ export class Telegram implements INodeType {
|
|||
displayOptions: {
|
||||
show: {
|
||||
'/operation': [
|
||||
'sendAnimation',
|
||||
'sendAudio',
|
||||
'sendDocument',
|
||||
'sendVideo',
|
||||
|
@ -1344,6 +1380,7 @@ export class Telegram implements INodeType {
|
|||
displayOptions: {
|
||||
show: {
|
||||
'/operation': [
|
||||
'sendAnimation',
|
||||
'sendVideo',
|
||||
],
|
||||
},
|
||||
|
@ -1469,6 +1506,21 @@ export class Telegram implements INodeType {
|
|||
// Add additional fields and replyMarkup
|
||||
addAdditionalFields.call(this, body, i);
|
||||
|
||||
|
||||
} else if (operation === 'sendAnimation') {
|
||||
// ----------------------------------
|
||||
// message:sendAnimation
|
||||
// ----------------------------------
|
||||
|
||||
endpoint = 'sendAnimation';
|
||||
|
||||
body.chat_id = this.getNodeParameter('chatId', i) as string;
|
||||
body.animation = this.getNodeParameter('file', i) as string;
|
||||
|
||||
// Add additional fields and replyMarkup
|
||||
addAdditionalFields.call(this, body, i);
|
||||
|
||||
|
||||
} else if (operation === 'sendAudio') {
|
||||
// ----------------------------------
|
||||
// message:sendAudio
|
||||
|
|
|
@ -4,15 +4,20 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
INodeTypeDescription,
|
||||
IDataObject,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
apiRequest,
|
||||
getImageBySize,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
IEvent,
|
||||
} from './IEvent';
|
||||
|
||||
export class TelegramTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -33,7 +38,7 @@ export class TelegramTrigger implements INodeType {
|
|||
{
|
||||
name: 'telegramApi',
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
],
|
||||
webhooks: [
|
||||
{
|
||||
|
@ -105,6 +110,52 @@ export class TelegramTrigger implements INodeType {
|
|||
default: [],
|
||||
description: 'The update types to listen to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Download Images/Files',
|
||||
name: 'download',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Telegram develiers the image in 3 sizes.<br>
|
||||
By default, just the larger image would be downloaded.<br>
|
||||
if you want to change the size set the field 'Image Size'`,
|
||||
},
|
||||
{
|
||||
displayName: 'Image Size',
|
||||
name: 'imageSize',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
download: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Small',
|
||||
value: 'small',
|
||||
},
|
||||
{
|
||||
name: 'Medium',
|
||||
value: 'medium',
|
||||
},
|
||||
{
|
||||
name: 'Large',
|
||||
value: 'large',
|
||||
},
|
||||
],
|
||||
default: 'large',
|
||||
description: 'The size of the image to be downloaded',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -112,6 +163,14 @@ export class TelegramTrigger implements INodeType {
|
|||
webhookMethods = {
|
||||
default: {
|
||||
async checkExists(this: IHookFunctions): Promise<boolean> {
|
||||
const endpoint = 'getWebhookInfo';
|
||||
const webhookReturnData = await apiRequest.call(this, 'POST', endpoint, {});
|
||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||
|
||||
if (webhookReturnData.result.url === webhookUrl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
async create(this: IHookFunctions): Promise<boolean> {
|
||||
|
@ -149,14 +208,74 @@ export class TelegramTrigger implements INodeType {
|
|||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const bodyData = this.getBodyData();
|
||||
|
||||
const credentials = this.getCredentials('telegramApi') as IDataObject;
|
||||
|
||||
const bodyData = this.getBodyData() as IEvent;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields') as IDataObject;
|
||||
|
||||
if (additionalFields.download === true) {
|
||||
|
||||
let imageSize = 'large';
|
||||
|
||||
if ((bodyData.message && bodyData.message.photo && Array.isArray(bodyData.message.photo) || bodyData.message?.document)) {
|
||||
|
||||
if (additionalFields.imageSize) {
|
||||
|
||||
imageSize = additionalFields.imageSize as string;
|
||||
|
||||
}
|
||||
|
||||
let fileId;
|
||||
|
||||
if (bodyData.message.photo) {
|
||||
|
||||
let image = getImageBySize(bodyData.message.photo as IDataObject[], imageSize) as IDataObject;
|
||||
|
||||
// When the image is sent from the desktop app telegram does not resize the image
|
||||
// So return the only image avaiable
|
||||
// Basically the Image Size parameter would work just when the images comes from the mobile app
|
||||
if (image === undefined) {
|
||||
image = bodyData.message.photo[0];
|
||||
}
|
||||
|
||||
fileId = image.file_id;
|
||||
|
||||
} else {
|
||||
|
||||
fileId = bodyData.message?.document?.file_id;
|
||||
}
|
||||
|
||||
const { result: { file_path } } = await apiRequest.call(this, 'GET', `getFile?file_id=${fileId}`, {});
|
||||
|
||||
const file = await apiRequest.call(this, 'GET', '', {}, {}, { json: false, encoding: null, uri: `https://api.telegram.org/file/bot${credentials.accessToken}/${file_path}`, resolveWithFullResponse: true });
|
||||
|
||||
const data = Buffer.from(file.body as string);
|
||||
|
||||
const fileName = file_path.split('/').pop();
|
||||
|
||||
const binaryData = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName);
|
||||
|
||||
return {
|
||||
workflowData: [
|
||||
[
|
||||
{
|
||||
json: bodyData as unknown as IDataObject,
|
||||
binary: {
|
||||
data: binaryData,
|
||||
},
|
||||
}
|
||||
]
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray([bodyData])
|
||||
this.helpers.returnJsonArray([bodyData as unknown as IDataObject])
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-nodes-base",
|
||||
"version": "0.74.1",
|
||||
"version": "0.75.0",
|
||||
"description": "Base nodes of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -52,6 +52,7 @@
|
|||
"dist/credentials/CopperApi.credentials.js",
|
||||
"dist/credentials/CalendlyApi.credentials.js",
|
||||
"dist/credentials/CustomerIoApi.credentials.js",
|
||||
"dist/credentials/S3.credentials.js",
|
||||
"dist/credentials/CrateDb.credentials.js",
|
||||
"dist/credentials/DisqusApi.credentials.js",
|
||||
"dist/credentials/DriftApi.credentials.js",
|
||||
|
@ -115,6 +116,7 @@
|
|||
"dist/credentials/MoceanApi.credentials.js",
|
||||
"dist/credentials/MondayComApi.credentials.js",
|
||||
"dist/credentials/MongoDb.credentials.js",
|
||||
"dist/credentials/Mqtt.credentials.js",
|
||||
"dist/credentials/Msg91Api.credentials.js",
|
||||
"dist/credentials/MySql.credentials.js",
|
||||
"dist/credentials/NextCloudApi.credentials.js",
|
||||
|
@ -137,6 +139,8 @@
|
|||
"dist/credentials/RundeckApi.credentials.js",
|
||||
"dist/credentials/ShopifyApi.credentials.js",
|
||||
"dist/credentials/SalesforceOAuth2Api.credentials.js",
|
||||
"dist/credentials/SentryIoApi.credentials.js",
|
||||
"dist/credentials/SentryIoOAuth2Api.credentials.js",
|
||||
"dist/credentials/SlackApi.credentials.js",
|
||||
"dist/credentials/SlackOAuth2Api.credentials.js",
|
||||
"dist/credentials/Sms77Api.credentials.js",
|
||||
|
@ -215,6 +219,7 @@
|
|||
"dist/nodes/CrateDb/CrateDb.node.js",
|
||||
"dist/nodes/Cron.node.js",
|
||||
"dist/nodes/Crypto.node.js",
|
||||
"dist/nodes/CustomerIo/CustomerIo.node.js",
|
||||
"dist/nodes/CustomerIo/CustomerIoTrigger.node.js",
|
||||
"dist/nodes/DateTime.node.js",
|
||||
"dist/nodes/Discord/Discord.node.js",
|
||||
|
@ -288,6 +293,7 @@
|
|||
"dist/nodes/Mocean/Mocean.node.js",
|
||||
"dist/nodes/MondayCom/MondayCom.node.js",
|
||||
"dist/nodes/MongoDb/MongoDb.node.js",
|
||||
"dist/nodes/MQTT/MqttTrigger.node.js",
|
||||
"dist/nodes/MoveBinaryData.node.js",
|
||||
"dist/nodes/Msg91/Msg91.node.js",
|
||||
"dist/nodes/MySql/MySql.node.js",
|
||||
|
@ -312,8 +318,10 @@
|
|||
"dist/nodes/Rocketchat/Rocketchat.node.js",
|
||||
"dist/nodes/RssFeedRead.node.js",
|
||||
"dist/nodes/Rundeck/Rundeck.node.js",
|
||||
"dist/nodes/S3/S3.node.js",
|
||||
"dist/nodes/Salesforce/Salesforce.node.js",
|
||||
"dist/nodes/Set.node.js",
|
||||
"dist/nodes/SentryIo/SentryIo.node.js",
|
||||
"dist/nodes/Shopify/Shopify.node.js",
|
||||
"dist/nodes/Shopify/ShopifyTrigger.node.js",
|
||||
"dist/nodes/Signl4/Signl4.node.js",
|
||||
|
@ -373,6 +381,7 @@
|
|||
"@types/mailparser": "^2.7.3",
|
||||
"@types/moment-timezone": "^0.5.12",
|
||||
"@types/mongodb": "^3.5.4",
|
||||
"@types/mqtt": "^2.5.0",
|
||||
"@types/mssql": "^6.0.2",
|
||||
"@types/node": "^14.0.27",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
|
@ -409,9 +418,10 @@
|
|||
"moment": "2.24.0",
|
||||
"moment-timezone": "^0.5.28",
|
||||
"mongodb": "^3.5.5",
|
||||
"mqtt": "^4.2.0",
|
||||
"mssql": "^6.2.0",
|
||||
"mysql2": "^2.0.1",
|
||||
"n8n-core": "~0.43.0",
|
||||
"n8n-core": "~0.44.0",
|
||||
"nodemailer": "^6.4.6",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"pg": "^8.3.0",
|
||||
|
|
Loading…
Reference in a new issue