mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
🔀 Merge branch 'master' into oauth-support
This commit is contained in:
commit
90fe5113a0
|
@ -60,12 +60,12 @@ export DB_POSTGRESDB_SCHEMA=n8n
|
|||
n8n start
|
||||
```
|
||||
|
||||
## MySQL
|
||||
## MySQL / MariaDB
|
||||
|
||||
The compatibility with MySQL was tested, even so, it is advisable to observe the operation of the application with this DB, as it is a new option, recently added. If you spot any problems, feel free to submit a PR.
|
||||
The compatibility with MySQL/MariaDB was tested, even so, it is advisable to observe the operation of the application with this DB, as it is a new option, recently added. If you spot any problems, feel free to submit a PR.
|
||||
|
||||
To use MySQL as database you can provide the following environment variables:
|
||||
- `DB_TYPE=mysqldb`
|
||||
- `DB_TYPE=mysqldb` or `DB_TYPE=mariadb`
|
||||
- `DB_MYSQLDB_DATABASE` (default: 'n8n')
|
||||
- `DB_MYSQLDB_HOST` (default: 'localhost')
|
||||
- `DB_MYSQLDB_PORT` (default: 3306)
|
||||
|
|
|
@ -48,11 +48,14 @@ the value would be: "My name is: Jim"
|
|||
The following special variables are available:
|
||||
|
||||
- **$binary**: Incoming binary data of a node
|
||||
- **$data**: Incoming JSON data of a node
|
||||
- **$evaluateExpression**: Evaluates a string as expression
|
||||
- **$env**: Environment variables
|
||||
- **$node**: Data of other nodes (output-data, parameters)
|
||||
- **$items**: Environment variables
|
||||
- **$json**: Incoming JSON data of a node
|
||||
- **$node**: Data of other nodes (binary, context, json, parameter, runIndex)
|
||||
- **$parameters**: Parameters of the current node
|
||||
- **$runIndex**: The current run index (first time node gets executed it is 0, second time 1, ...)
|
||||
- **$workflow**: Returns workflow metadata like: active, id, name
|
||||
|
||||
Normally it is not needed to write the JavaScript variables manually as they can be simply selected with the help of the Expression Editor.
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ return newItems;
|
|||
```
|
||||
|
||||
|
||||
#### Method: $item(index)
|
||||
#### Method: $item(index: number, runIndex?: number)
|
||||
|
||||
With `$item` it is possible to access the data of parent nodes. That can be the item data but also
|
||||
the parameters. It expects as input an index of the item the data should be returned for. This is
|
||||
|
@ -71,6 +71,12 @@ emails at once to different people. Instead, the same person would receive multi
|
|||
|
||||
The index is 0 based. So `$item(0)` will return the first item, `$item(1)` the second one, ...
|
||||
|
||||
By default will the item of the last run of the node be returned. So if the referenced node did run
|
||||
3x (its last runIndex is 2) and the current node runs the first time (its runIndex is 0) will the
|
||||
data of runIndex 2 of the referenced node be returned.
|
||||
|
||||
For more information about what data can be accessed via $node check [here](#variable-node).
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
|
@ -88,18 +94,76 @@ const channel = $item(9).$node["Slack"].parameter["channel"];
|
|||
```
|
||||
|
||||
|
||||
#### Variable: $node
|
||||
#### Method: $items(nodeName?: string, outputIndex?: number, runIndex?: number)
|
||||
|
||||
Works exactly like `$item` with the difference that it will always return the data of the first item.
|
||||
Gives access to all the items of current or parent nodes. If no parameters get supplied
|
||||
it returns all the items of the current node.
|
||||
If a node-name is given, it returns the items the node did output on it`s first output
|
||||
(index: 0, most nodes only have one output, exceptions are IF and Switch-Node) on
|
||||
its last run.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
const myNumber = $node["Set"].json['myNumber'];
|
||||
// Returns all the items of the current node and current run
|
||||
const allItems = $items();
|
||||
|
||||
const channel = $node["Slack"].parameter["channel"];
|
||||
// Returns all items the node "IF" outputs (index: 0 which is Output "true" of its most recent run)
|
||||
const allItems = $items("IF");
|
||||
|
||||
// Returns all items the node "IF" outputs (index: 0 which is Output "true" of the same run as current node)
|
||||
const allItems = $items("IF", 0, $runIndex);
|
||||
|
||||
// Returns all items the node "IF" outputs (index: 1 which is Output "false" of run 0 which is the first run)
|
||||
const allItems = $items("IF", 1, 0);
|
||||
```
|
||||
|
||||
|
||||
#### Method: evaluateExpression(expression: string, itemIndex: number)
|
||||
#### Variable: $node
|
||||
|
||||
Works exactly like `$item` with the difference that it will always return the data of the first item and
|
||||
the last run of the node.
|
||||
|
||||
```typescript
|
||||
// Returns the fileName of binary property "data" of Node "HTTP Request"
|
||||
const fileName = $node["HTTP Request"].binary["data"]["fileName"]}}
|
||||
|
||||
// Returns the context data "noItemsLeft" of Node "SplitInBatches"
|
||||
const noItemsLeft = $node["SplitInBatches"].context["noItemsLeft"];
|
||||
|
||||
// Returns the value of the JSON data property "myNumber" of Node "Set"
|
||||
const myNumber = $node["Set"].json['myNumber'];
|
||||
|
||||
// Returns the value of the parameter "channel" of Node "Slack"
|
||||
const channel = $node["Slack"].parameter["channel"];
|
||||
|
||||
// Returns the index of the last run of Node "HTTP Request"
|
||||
const runIndex = $node["HTTP Request"].runIndex}}
|
||||
```
|
||||
|
||||
|
||||
#### Variable: $runIndex
|
||||
|
||||
Contains the index of the current run of the node.
|
||||
|
||||
```typescript
|
||||
// Returns all items the node "IF" outputs (index: 0 which is Output "true" of the same run as current node)
|
||||
const allItems = $items("IF", 0, $runIndex);
|
||||
```
|
||||
|
||||
|
||||
#### Variable: $workflow
|
||||
|
||||
Gives information about the current workflow.
|
||||
|
||||
```typescript
|
||||
const isActive = $workflow.active;
|
||||
const workflowId = $workflow.id;
|
||||
const workflowName = $workflow.name;
|
||||
```
|
||||
|
||||
|
||||
#### Method: $evaluateExpression(expression: string, itemIndex: number)
|
||||
|
||||
Evaluates a given string as expression.
|
||||
If no `itemIndex` is provided it uses by default in the Function-Node the data of item 0 and
|
||||
|
@ -108,8 +172,8 @@ in the Function Item-Node the data of the current item.
|
|||
Example:
|
||||
|
||||
```javascript
|
||||
items[0].json.variable1 = evaluateExpression('{{1+2}}');
|
||||
items[0].json.variable2 = evaluateExpression($node["Set"].json["myExpression"], 1);
|
||||
items[0].json.variable1 = $evaluateExpression('{{1+2}}');
|
||||
items[0].json.variable2 = $evaluateExpression($node["Set"].json["myExpression"], 1);
|
||||
|
||||
return items;
|
||||
```
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
This list shows all the versions which include breaking changes and how to upgrade
|
||||
|
||||
|
||||
## ???
|
||||
|
||||
### What changed?
|
||||
|
@ -30,6 +31,23 @@ it has to get changed to:
|
|||
```
|
||||
|
||||
|
||||
## 0.62.0
|
||||
|
||||
### What changed?
|
||||
|
||||
The function "evaluateExpression(...)" got renamed to "$evaluateExpression()"
|
||||
in Function and FunctionItem Nodes to simplify code and to normalize function
|
||||
names.
|
||||
|
||||
### When is action necessary?
|
||||
|
||||
If "evaluateExpression(...)" gets used in any Function or FunctionItem Node.
|
||||
|
||||
### How to upgrade:
|
||||
|
||||
Simply replace the "evaluateExpression(...)" with "$evaluateExpression(...)".
|
||||
|
||||
|
||||
## 0.52.0
|
||||
|
||||
### What changed?
|
||||
|
|
|
@ -8,7 +8,7 @@ const config = convict({
|
|||
database: {
|
||||
type: {
|
||||
doc: 'Type of database to use',
|
||||
format: ['sqlite', 'mongodb', 'mysqldb', 'postgresdb'],
|
||||
format: ['sqlite', 'mariadb', 'mongodb', 'mysqldb', 'postgresdb'],
|
||||
default: 'sqlite',
|
||||
env: 'DB_TYPE'
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.60.0",
|
||||
"version": "0.62.1",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -97,10 +97,10 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"mongodb": "^3.2.3",
|
||||
"mysql2": "^2.0.1",
|
||||
"n8n-core": "~0.29.0",
|
||||
"n8n-editor-ui": "~0.40.0",
|
||||
"n8n-nodes-base": "~0.55.0",
|
||||
"n8n-workflow": "~0.26.0",
|
||||
"n8n-core": "~0.31.0",
|
||||
"n8n-editor-ui": "~0.42.0",
|
||||
"n8n-nodes-base": "~0.57.1",
|
||||
"n8n-workflow": "~0.28.0",
|
||||
"open": "^7.0.0",
|
||||
"pg": "^7.11.0",
|
||||
"request-promise-native": "^1.0.7",
|
||||
|
|
|
@ -63,11 +63,12 @@ export async function init(synchronize?: boolean): Promise<IDatabaseCollections>
|
|||
};
|
||||
break;
|
||||
|
||||
case 'mariadb':
|
||||
case 'mysqldb':
|
||||
dbNotExistError = 'does not exist';
|
||||
entities = MySQLDb;
|
||||
connectionOptions = {
|
||||
type: 'mysql',
|
||||
type: dbType === 'mysqldb' ? 'mysql' : 'mariadb',
|
||||
database: await GenericHelpers.getConfigValue('database.mysqldb.database') as string,
|
||||
entityPrefix: await GenericHelpers.getConfigValue('database.tablePrefix') as string,
|
||||
host: await GenericHelpers.getConfigValue('database.mysqldb.host') as string,
|
||||
|
|
|
@ -91,7 +91,7 @@ export interface ICredentialsDecryptedResponse extends ICredentialsDecryptedDb {
|
|||
id: string;
|
||||
}
|
||||
|
||||
export type DatabaseType = 'mongodb' | 'postgresdb' | 'mysqldb' | 'sqlite';
|
||||
export type DatabaseType = 'mariadb' | 'mongodb' | 'postgresdb' | 'mysqldb' | 'sqlite';
|
||||
export type SaveExecutionDataType = 'all' | 'none';
|
||||
|
||||
export interface IExecutionBase {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.29.0",
|
||||
"version": "0.31.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"crypto-js": "3.1.9-1",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mmmagic": "^0.5.2",
|
||||
"n8n-workflow": "~0.26.0",
|
||||
"n8n-workflow": "~0.28.0",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"request-promise-native": "^1.0.7"
|
||||
},
|
||||
|
|
|
@ -596,6 +596,17 @@ export class WorkflowExecute {
|
|||
this.runExecutionData.resultData.lastNodeExecuted = executionData.node.name;
|
||||
nodeSuccessData = await workflow.runNode(executionData.node, executionData.data, this.runExecutionData, runIndex, this.additionalData, NodeExecuteFunctions, this.mode);
|
||||
|
||||
if (nodeSuccessData === null || nodeSuccessData[0][0] === undefined) {
|
||||
if (executionData.node.alwaysOutputData === true) {
|
||||
nodeSuccessData = nodeSuccessData || [];
|
||||
nodeSuccessData[0] = [
|
||||
{
|
||||
json: {},
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeSuccessData === null) {
|
||||
// If null gets returned it means that the node did succeed
|
||||
// but did not have any data. So the branch should end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.40.0",
|
||||
"version": "0.42.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -64,7 +64,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.26.0",
|
||||
"n8n-workflow": "~0.28.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"prismjs": "^1.17.1",
|
||||
"quill": "^2.0.0-dev.3",
|
||||
|
|
|
@ -141,6 +141,7 @@ export default mixins(
|
|||
nodeColor: null,
|
||||
nodeValues: {
|
||||
color: '#ff0000',
|
||||
alwaysOutputData: false,
|
||||
continueOnFail: false,
|
||||
retryOnFail: false,
|
||||
maxTries: 3,
|
||||
|
@ -169,6 +170,14 @@ export default mixins(
|
|||
noDataExpression: true,
|
||||
description: 'The color of the node in the flow.',
|
||||
},
|
||||
{
|
||||
displayName: 'Always Output Data',
|
||||
name: 'alwaysOutputData',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
noDataExpression: true,
|
||||
description: 'If activated and the node does not have any data for the first output,<br />it returns an empty item anyway. Be careful setting this on<br />IF-Nodes as it could easily cause an infinite loop.',
|
||||
},
|
||||
{
|
||||
displayName: 'Retry On Fail',
|
||||
name: 'retryOnFail',
|
||||
|
@ -419,6 +428,11 @@ export default mixins(
|
|||
Vue.set(this.nodeValues, 'notes', this.node.notes);
|
||||
}
|
||||
|
||||
if (this.node.alwaysOutputData) {
|
||||
foundNodeSettings.push('alwaysOutputData');
|
||||
Vue.set(this.nodeValues, 'alwaysOutputData', this.node.alwaysOutputData);
|
||||
}
|
||||
|
||||
if (this.node.continueOnFail) {
|
||||
foundNodeSettings.push('continueOnFail');
|
||||
Vue.set(this.nodeValues, 'continueOnFail', this.node.continueOnFail);
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
formOperations
|
||||
} from './FormDescription';
|
||||
import { submitForm } from './FormFunctions';
|
||||
import { createDataFromParameters } from './GenericFunctions';
|
||||
import {
|
||||
singletonFields,
|
||||
singletonOperations,
|
||||
|
@ -56,7 +57,7 @@ export class Cockpit implements INodeType {
|
|||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
default: 'collections',
|
||||
default: 'collection',
|
||||
description: 'Resource to consume.',
|
||||
options: [
|
||||
{
|
||||
|
@ -74,7 +75,6 @@ export class Cockpit implements INodeType {
|
|||
],
|
||||
},
|
||||
|
||||
|
||||
...collectionOperations,
|
||||
...collectionFields,
|
||||
...formOperations,
|
||||
|
@ -84,7 +84,6 @@ export class Cockpit implements INodeType {
|
|||
],
|
||||
};
|
||||
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getCollections(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
|
@ -123,34 +122,37 @@ export class Cockpit implements INodeType {
|
|||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'collection') {
|
||||
const collectionName = this.getNodeParameter('collection', i) as string;
|
||||
|
||||
if (operation === 'create') {
|
||||
const data = this.getNodeParameter('data', i) as IDataObject;
|
||||
const data = createDataFromParameters.call(this, i);
|
||||
|
||||
responseData = await createCollectionEntry.call(this, collectionName, data);
|
||||
} else if (operation === 'getAll') {
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
|
||||
if (returnAll !== true) {
|
||||
if (!returnAll) {
|
||||
options.limit = this.getNodeParameter('limit', i) as number;
|
||||
}
|
||||
|
||||
responseData = await getAllCollectionEntries.call(this, collectionName, options);
|
||||
} else if (operation === 'update') {
|
||||
const id = this.getNodeParameter('id', i) as string;
|
||||
const data = this.getNodeParameter('data', i) as IDataObject;
|
||||
const data = createDataFromParameters.call(this, i);
|
||||
|
||||
responseData = await createCollectionEntry.call(this, collectionName, data, id);
|
||||
}
|
||||
} else if (resource === 'form') {
|
||||
const formName = this.getNodeParameter('form', i) as string;
|
||||
|
||||
if (operation === 'submit') {
|
||||
const form = this.getNodeParameter('form', i) as IDataObject;
|
||||
const form = createDataFromParameters.call(this, i);
|
||||
|
||||
responseData = await submitForm.call(this, formName, form);
|
||||
}
|
||||
} else if (resource === 'singleton') {
|
||||
const singletonName = this.getNodeParameter('singleton', i) as string;
|
||||
|
||||
if (operation === 'get') {
|
||||
responseData = await getSingleton.call(this, singletonName);
|
||||
}
|
||||
|
|
|
@ -14,17 +14,17 @@ export const collectionOperations = [
|
|||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create an entry',
|
||||
name: 'Create an Entry',
|
||||
value: 'create',
|
||||
description: 'Create a collection entry',
|
||||
},
|
||||
{
|
||||
name: 'Get all entries',
|
||||
name: 'Get all Entries',
|
||||
value: 'getAll',
|
||||
description: 'Get all collection entries',
|
||||
},
|
||||
{
|
||||
name: 'Update an entry',
|
||||
name: 'Update an Entry',
|
||||
value: 'update',
|
||||
description: 'Update a collection entries',
|
||||
},
|
||||
|
@ -54,29 +54,6 @@ export const collectionFields = [
|
|||
description: 'Name of the collection to operate on.'
|
||||
},
|
||||
|
||||
// Collection:entry:create
|
||||
{
|
||||
displayName: 'Data',
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
required: true,
|
||||
default: '',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'collection',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The data to create.',
|
||||
},
|
||||
|
||||
// Collection:entry:getAll
|
||||
{
|
||||
displayName: 'Return All',
|
||||
|
@ -139,22 +116,24 @@ export const collectionFields = [
|
|||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'json',
|
||||
type: 'string',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
description: 'Fields to get.',
|
||||
placeholder: '_id,name',
|
||||
description: 'Comma separated list of fields to get.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filter',
|
||||
displayName: 'Filter Query',
|
||||
name: 'filter',
|
||||
type: 'json',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
description: 'Filter result by fields.',
|
||||
placeholder: '{"name": "Jim"}',
|
||||
description: 'Filter query in <a href="https://jeroen.github.io/mongolite/query-data.html" target="_blank">Mongolite format</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Language',
|
||||
|
@ -186,11 +165,12 @@ export const collectionFields = [
|
|||
description: 'Skip number of entries.',
|
||||
},
|
||||
{
|
||||
displayName: 'Sort',
|
||||
displayName: 'Sort Query',
|
||||
name: 'sort',
|
||||
type: 'json',
|
||||
default: '',
|
||||
description: 'Sort result by fields.',
|
||||
placeholder: '{"price": -1}',
|
||||
description: 'Sort query in <a href="https://jeroen.github.io/mongolite/query-data.html" target="_blank">Mongolite format</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -214,25 +194,95 @@ export const collectionFields = [
|
|||
},
|
||||
description: 'The entry ID.',
|
||||
},
|
||||
|
||||
// Collection:entry:create
|
||||
// Collection:entry:update
|
||||
{
|
||||
displayName: 'Data',
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
required: true,
|
||||
default: '',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
displayName: 'JSON Data fields',
|
||||
name: 'jsonDataFields',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'collection',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
'update',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The data to update.',
|
||||
description: 'If new entry fields should be set via the value-key pair UI or JSON.',
|
||||
},
|
||||
{
|
||||
displayName: 'Entry Data',
|
||||
name: 'dataFieldsJson',
|
||||
type: 'json',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
jsonDataFields: [
|
||||
true,
|
||||
],
|
||||
resource: [
|
||||
'collection',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
'update',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'Entry data to send as JSON.',
|
||||
},
|
||||
{
|
||||
displayName: 'Entry Data',
|
||||
name: 'dataFieldsUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
jsonDataFields: [
|
||||
false,
|
||||
],
|
||||
resource: [
|
||||
'collection',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
'update',
|
||||
]
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the field.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value of the field.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description: 'Entry data to send.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
|
|
@ -9,7 +9,7 @@ import { cockpitApiRequest } from './GenericFunctions';
|
|||
|
||||
export async function createCollectionEntry(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, data: IDataObject, id?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
const body: ICollection = {
|
||||
data: JSON.parse(data.toString())
|
||||
data,
|
||||
};
|
||||
|
||||
if (id) {
|
||||
|
@ -27,7 +27,16 @@ export async function getAllCollectionEntries(this: IExecuteFunctions | IExecute
|
|||
const body: ICollection = {};
|
||||
|
||||
if (options.fields) {
|
||||
body.fields = JSON.parse(options.fields.toString());
|
||||
const fields = (options.fields as string).split(',').map(field => field.trim() );
|
||||
|
||||
const bodyFields = {
|
||||
_id: false,
|
||||
} as IDataObject;
|
||||
for (const field of fields) {
|
||||
bodyFields[field] = true;
|
||||
}
|
||||
|
||||
body.fields = bodyFields;
|
||||
}
|
||||
|
||||
if (options.filter) {
|
||||
|
|
|
@ -14,7 +14,7 @@ export const formOperations = [
|
|||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Submit a form',
|
||||
name: 'Submit a Form',
|
||||
value: 'submit',
|
||||
description: 'Store submission of a form',
|
||||
},
|
||||
|
@ -44,21 +44,88 @@ export const formFields = [
|
|||
|
||||
// Form:submit
|
||||
{
|
||||
displayName: 'Form data',
|
||||
name: 'form',
|
||||
displayName: 'JSON Data fields',
|
||||
name: 'jsonDataFields',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'submit',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'If form fields should be set via the value-key pair UI or JSON.',
|
||||
},
|
||||
{
|
||||
displayName: 'Form Data',
|
||||
name: 'dataFieldsJson',
|
||||
type: 'json',
|
||||
required: true,
|
||||
default: '',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
jsonDataFields: [
|
||||
true,
|
||||
],
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'submit',
|
||||
]
|
||||
},
|
||||
},
|
||||
description: 'The data to save.',
|
||||
description: 'Form data to send as JSON.',
|
||||
},
|
||||
{
|
||||
displayName: 'Form Data',
|
||||
name: 'dataFieldsUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
jsonDataFields: [
|
||||
false,
|
||||
],
|
||||
resource: [
|
||||
'form',
|
||||
],
|
||||
operation: [
|
||||
'submit',
|
||||
]
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'field',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the field.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value of the field.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description: 'Form data to send.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
|
|
@ -9,7 +9,7 @@ import { cockpitApiRequest } from './GenericFunctions';
|
|||
|
||||
export async function submitForm(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, resourceName: string, form: IDataObject) {
|
||||
const body: IForm = {
|
||||
form: JSON.parse(form.toString())
|
||||
form
|
||||
};
|
||||
|
||||
return cockpitApiRequest.call(this, 'post', `/forms/submit/${resourceName}`, body);
|
||||
|
|
|
@ -44,3 +44,26 @@ export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingle
|
|||
throw new Error(`Cockpit error [${error.statusCode}]: ` + errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
export function createDataFromParameters(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, itemIndex: number): IDataObject {
|
||||
const dataFieldsAreJson = this.getNodeParameter('jsonDataFields', itemIndex) as boolean;
|
||||
|
||||
if (dataFieldsAreJson) {
|
||||
// Parameters are defined as JSON
|
||||
return JSON.parse(this.getNodeParameter('dataFieldsJson', itemIndex, {}) as string);
|
||||
}
|
||||
|
||||
// Parameters are defined in UI
|
||||
const uiDataFields = this.getNodeParameter('dataFieldsUi', itemIndex, {}) as IDataObject;
|
||||
const unpacked: IDataObject = {};
|
||||
|
||||
if (uiDataFields.field === undefined) {
|
||||
return unpacked;
|
||||
}
|
||||
|
||||
for (const field of uiDataFields!.field as IDataObject[]) {
|
||||
unpacked[field!.name as string] = field!.value;
|
||||
}
|
||||
|
||||
return unpacked;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export const singletonOperations = [
|
|||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Gets a singleton',
|
||||
description: 'Gets a Singleton',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
|
|
|
@ -47,9 +47,6 @@ export class Function implements INodeType {
|
|||
|
||||
// Define the global objects for the custom function
|
||||
const sandbox = {
|
||||
evaluateExpression: (expression: string, itemIndex = 0) => {
|
||||
return this.evaluateExpression(expression, itemIndex);
|
||||
},
|
||||
getNodeParameter: this.getNodeParameter,
|
||||
getWorkflowStaticData: this.getWorkflowStaticData,
|
||||
helpers: this.helpers,
|
||||
|
|
|
@ -48,9 +48,6 @@ export class FunctionItem implements INodeType {
|
|||
|
||||
// Define the global objects for the custom function
|
||||
const sandbox = {
|
||||
evaluateExpression: (expression: string, itemIndex: number | undefined) => {
|
||||
return this.evaluateExpression(expression, itemIndex);
|
||||
},
|
||||
getBinaryData: (): IBinaryKeyData | undefined => {
|
||||
return item.binary;
|
||||
},
|
||||
|
|
|
@ -713,12 +713,8 @@ export class HttpRequest implements INodeType {
|
|||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// @ts-ignore
|
||||
requestOptions[optionData.name] = JSON.parse(tempValue as string);
|
||||
} catch (error) {
|
||||
throw new Error(`${optionData.name} must be a valid JSON`);
|
||||
}
|
||||
// @ts-ignore
|
||||
requestOptions[optionData.name] = tempValue;
|
||||
|
||||
// @ts-ignore
|
||||
if (typeof requestOptions[optionData.name] !== 'object' && options.bodyContentType !== 'raw') {
|
||||
|
|
|
@ -7,14 +7,15 @@ import {
|
|||
import { OptionsWithUri } from 'request';
|
||||
import { IDataObject } from 'n8n-workflow';
|
||||
|
||||
|
||||
export interface IAttachment {
|
||||
export interface IAttachment {
|
||||
fields: {
|
||||
item?: object[];
|
||||
};
|
||||
actions: {
|
||||
item?: object[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make an API request to Telegram
|
||||
*
|
||||
|
|
|
@ -448,78 +448,181 @@ export class Mattermost implements INodeType {
|
|||
placeholder: 'Add attachment item',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fallback Text',
|
||||
name: 'fallback',
|
||||
type: 'string',
|
||||
displayName: 'Actions',
|
||||
name: 'actions',
|
||||
placeholder: 'Add Actions',
|
||||
description: 'Actions to add to message. More information can be found <a href="https://docs.mattermost.com/developer/interactive-messages.html" target="_blank">here</a>',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
multipleValues: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Required plain-text summary of the attachment.',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Text to send.',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Title of the message.',
|
||||
},
|
||||
{
|
||||
displayName: 'Title Link',
|
||||
name: 'title_link',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Link of the title.',
|
||||
},
|
||||
{
|
||||
displayName: 'Color',
|
||||
name: 'color',
|
||||
type: 'color',
|
||||
default: '#ff0000',
|
||||
description: 'Color of the line left of text.',
|
||||
},
|
||||
{
|
||||
displayName: 'Pretext',
|
||||
name: 'pretext',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Text which appears before the message block.',
|
||||
},
|
||||
{
|
||||
displayName: 'Author Name',
|
||||
name: 'author_name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name that should appear.',
|
||||
},
|
||||
{
|
||||
displayName: 'Author Link',
|
||||
name: 'author_link',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Link for the author.',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Item',
|
||||
name: 'item',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Button',
|
||||
value: 'button',
|
||||
},
|
||||
{
|
||||
name: 'Select',
|
||||
value: 'select',
|
||||
},
|
||||
],
|
||||
default: 'button',
|
||||
description: 'The type of the action.',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Source',
|
||||
name: 'data_source',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'select'
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Channels',
|
||||
value: 'channels',
|
||||
},
|
||||
{
|
||||
name: 'Custom',
|
||||
value: 'custom',
|
||||
},
|
||||
{
|
||||
name: 'Users',
|
||||
value: 'users',
|
||||
},
|
||||
|
||||
],
|
||||
default: 'custom',
|
||||
description: 'The type of the action.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
placeholder: 'Add Option',
|
||||
description: 'Adds a new option to select field.',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
data_source: [
|
||||
'custom'
|
||||
],
|
||||
type: [
|
||||
'select'
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'option',
|
||||
displayName: 'Option',
|
||||
default: {},
|
||||
values: [
|
||||
{
|
||||
displayName: 'Option Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Text of the option.',
|
||||
},
|
||||
{
|
||||
displayName: 'Option Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value of the option.',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the Action.',
|
||||
},
|
||||
{
|
||||
displayName: 'Integration',
|
||||
name: 'integration',
|
||||
placeholder: 'Add Integration',
|
||||
description: 'Integration to add to message.',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Item',
|
||||
name: 'item',
|
||||
default: {},
|
||||
values: [
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'URL of the Integration.',
|
||||
},
|
||||
{
|
||||
displayName: 'Context',
|
||||
name: 'context',
|
||||
placeholder: 'Add Context to Integration',
|
||||
description: 'Adds a Context values set.',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
name: 'property',
|
||||
displayName: 'Property',
|
||||
default: {},
|
||||
values: [
|
||||
{
|
||||
displayName: 'Property Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Name of the property to set.',
|
||||
},
|
||||
{
|
||||
displayName: 'Property Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Value of the property to set.',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Author Icon',
|
||||
|
@ -532,44 +635,38 @@ export class Mattermost implements INodeType {
|
|||
description: 'Icon which should appear for the user.',
|
||||
},
|
||||
{
|
||||
displayName: 'Image URL',
|
||||
name: 'image_url',
|
||||
displayName: 'Author Link',
|
||||
name: 'author_link',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'URL of image.',
|
||||
description: 'Link for the author.',
|
||||
},
|
||||
{
|
||||
displayName: 'Thumbnail URL',
|
||||
name: 'thumb_url',
|
||||
displayName: 'Author Name',
|
||||
name: 'author_name',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'URL of thumbnail.',
|
||||
description: 'Name that should appear.',
|
||||
},
|
||||
{
|
||||
displayName: 'Footer',
|
||||
name: 'footer',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Text of footer to add.',
|
||||
displayName: 'Color',
|
||||
name: 'color',
|
||||
type: 'color',
|
||||
default: '#ff0000',
|
||||
description: 'Color of the line left of text.',
|
||||
},
|
||||
{
|
||||
displayName: 'Footer Icon',
|
||||
name: 'footer_icon',
|
||||
displayName: 'Fallback Text',
|
||||
name: 'fallback',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Icon which should appear next to footer.',
|
||||
description: 'Required plain-text summary of the attachment.',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
|
@ -610,7 +707,87 @@ export class Mattermost implements INodeType {
|
|||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Footer',
|
||||
name: 'footer',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Text of footer to add.',
|
||||
},
|
||||
{
|
||||
displayName: 'Footer Icon',
|
||||
name: 'footer_icon',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Icon which should appear next to footer.',
|
||||
},
|
||||
{
|
||||
displayName: 'Image URL',
|
||||
name: 'image_url',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'URL of image.',
|
||||
},
|
||||
{
|
||||
displayName: 'Pretext',
|
||||
name: 'pretext',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Text which appears before the message block.',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Text to send.',
|
||||
},
|
||||
{
|
||||
displayName: 'Thumbnail URL',
|
||||
name: 'thumb_url',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'URL of thumbnail.',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Title of the message.',
|
||||
},
|
||||
{
|
||||
displayName: 'Title Link',
|
||||
name: 'title_link',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
},
|
||||
default: '',
|
||||
description: 'Link of the title.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -890,6 +1067,47 @@ export class Mattermost implements INodeType {
|
|||
}
|
||||
}
|
||||
}
|
||||
for (const attachment of attachments) {
|
||||
if (attachment.actions !== undefined) {
|
||||
if (attachment.actions.item !== undefined) {
|
||||
// Move the field-content up
|
||||
// @ts-ignore
|
||||
attachment.actions = attachment.actions.item;
|
||||
} else {
|
||||
// If it does not have any items set remove it
|
||||
delete attachment.actions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const attachment of attachments) {
|
||||
if (Array.isArray(attachment.actions)) {
|
||||
for (const attaction of attachment.actions) {
|
||||
|
||||
if (attaction.type === 'button') {
|
||||
delete attaction.type;
|
||||
}
|
||||
if (attaction.data_source === 'custom') {
|
||||
delete attaction.data_source;
|
||||
}
|
||||
if (attaction.options) {
|
||||
attaction.options = attaction.options.option;
|
||||
}
|
||||
|
||||
if (attaction.integration.item !== undefined) {
|
||||
attaction.integration = attaction.integration.item;
|
||||
if (Array.isArray(attaction.integration.context.property)) {
|
||||
const tmpcontex = {};
|
||||
for (const attactionintegprop of attaction.integration.context.property) {
|
||||
Object.assign(tmpcontex, { [attactionintegprop.name]: attactionintegprop.value });
|
||||
}
|
||||
delete attaction.integration.context;
|
||||
attaction.integration.context = tmpcontex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.props = {
|
||||
attachments,
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
function flattenObject (data: IDataObject) {
|
||||
const returnData: IDataObject = {};
|
||||
for (const key1 of Object.keys(data)) {
|
||||
if ((typeof data[key1]) === 'object') {
|
||||
if (data[key1] !== null && (typeof data[key1]) === 'object') {
|
||||
const flatObject = flattenObject(data[key1] as IDataObject);
|
||||
for (const key2 in flatObject) {
|
||||
if (flatObject[key2] === undefined) {
|
||||
|
@ -133,6 +133,11 @@ export class SpreadsheetFile implements INodeType {
|
|||
value: 'xls',
|
||||
description: 'Excel',
|
||||
},
|
||||
{
|
||||
name: 'XLSX',
|
||||
value: 'xlsx',
|
||||
description: 'Excel',
|
||||
},
|
||||
],
|
||||
default: 'xls',
|
||||
displayOptions: {
|
||||
|
@ -236,6 +241,7 @@ export class SpreadsheetFile implements INodeType {
|
|||
'/fileFormat': [
|
||||
'ods',
|
||||
'xls',
|
||||
'xlsx',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -337,7 +343,9 @@ export class SpreadsheetFile implements INodeType {
|
|||
} else if (fileFormat === 'ods') {
|
||||
wopts.bookType = 'ods';
|
||||
} else if (fileFormat === 'xls') {
|
||||
wopts.bookType = 'xlml';
|
||||
wopts.bookType = 'xls';
|
||||
} else if (fileFormat === 'xlsx') {
|
||||
wopts.bookType = 'xlsx';
|
||||
}
|
||||
|
||||
// Convert the data in the correct format
|
||||
|
|
|
@ -62,6 +62,9 @@ export async function zendeskApiRequestAllItems(this: IHookFunctions | IExecuteF
|
|||
responseData = await zendeskApiRequest.call(this, method, resource, body, query, uri);
|
||||
uri = responseData.next_page;
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
if (query.limit && query.limit <= returnData.length) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
responseData.next_page !== undefined &&
|
||||
responseData.next_page !== null
|
||||
|
@ -69,3 +72,13 @@ export async function zendeskApiRequestAllItems(this: IHookFunctions | IExecuteF
|
|||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ export const ticketOperations = [
|
|||
description: 'Create a ticket',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a ticket',
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a ticket',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
|
@ -36,9 +36,9 @@ export const ticketOperations = [
|
|||
description: 'Get all tickets',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a ticket',
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a ticket',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
|
@ -81,7 +81,7 @@ export const ticketFields = [
|
|||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticket'
|
||||
'ticket',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
|
@ -103,9 +103,47 @@ export const ticketFields = [
|
|||
operation: [
|
||||
'create',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
placeholder: 'Add Custom Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Field',
|
||||
name: 'customFieldsValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCustomFields',
|
||||
},
|
||||
default: '',
|
||||
description: 'Custom field ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Custom field Value.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'External ID',
|
||||
name: 'externalId',
|
||||
|
@ -113,20 +151,6 @@ export const ticketFields = [
|
|||
default: '',
|
||||
description: 'An id you can use to link Zendesk Support tickets to local records',
|
||||
},
|
||||
{
|
||||
displayName: 'Subject',
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The value of the subject field for this ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Recipient',
|
||||
name: 'recipient',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The original recipient e-mail address of the ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Group',
|
||||
name: 'group',
|
||||
|
@ -137,6 +161,49 @@ export const ticketFields = [
|
|||
default: '',
|
||||
description: 'The group this ticket is assigned to',
|
||||
},
|
||||
{
|
||||
displayName: 'Recipient',
|
||||
name: 'recipient',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The original recipient e-mail address of the ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
name: 'New',
|
||||
value: 'new',
|
||||
},
|
||||
{
|
||||
name: 'Pending',
|
||||
value: 'pending',
|
||||
},
|
||||
{
|
||||
name: 'Solved',
|
||||
value: 'solved',
|
||||
},
|
||||
{
|
||||
name: 'Closed',
|
||||
value: 'closed',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The state of the ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Subject',
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The value of the subject field for this ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
|
@ -172,40 +239,11 @@ export const ticketFields = [
|
|||
default: '',
|
||||
description: 'The type of this ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
name: 'New',
|
||||
value: 'new',
|
||||
},
|
||||
{
|
||||
name: 'Pending',
|
||||
value: 'pending',
|
||||
},
|
||||
{
|
||||
name: 'Solved',
|
||||
value: 'solved',
|
||||
},
|
||||
{
|
||||
name: 'Closed',
|
||||
value: 'closed',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The state of the ticket',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: ' Custom Fields',
|
||||
name: 'customFieldsJson',
|
||||
displayName: ' Additional Fields',
|
||||
name: 'additionalFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
|
@ -224,14 +262,14 @@ export const ticketFields = [
|
|||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: `Array of customs fields <a href="https://developer.zendesk.com/rest_api/docs/support/tickets#setting-custom-field-values" target="_blank">Details</a>`,
|
||||
description: `Object of values to set as described <a href="https://developer.zendesk.com/rest_api/docs/support/tickets" target="_blank">here</a>.`,
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'ID',
|
||||
displayName: 'Ticket ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
|
@ -279,9 +317,47 @@ export const ticketFields = [
|
|||
operation: [
|
||||
'update',
|
||||
],
|
||||
jsonParameters: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
placeholder: 'Add Custom Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Custom Field',
|
||||
name: 'customFieldsValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'ID',
|
||||
name: 'id',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCustomFields',
|
||||
},
|
||||
default: '',
|
||||
description: 'Custom field ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Custom field Value.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'External ID',
|
||||
name: 'externalId',
|
||||
|
@ -289,20 +365,6 @@ export const ticketFields = [
|
|||
default: '',
|
||||
description: 'An id you can use to link Zendesk Support tickets to local records',
|
||||
},
|
||||
{
|
||||
displayName: 'Subject',
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The value of the subject field for this ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Recipient',
|
||||
name: 'recipient',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The original recipient e-mail address of the ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Group',
|
||||
name: 'group',
|
||||
|
@ -313,6 +375,49 @@ export const ticketFields = [
|
|||
default: '',
|
||||
description: 'The group this ticket is assigned to',
|
||||
},
|
||||
{
|
||||
displayName: 'Recipient',
|
||||
name: 'recipient',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The original recipient e-mail address of the ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
name: 'New',
|
||||
value: 'new',
|
||||
},
|
||||
{
|
||||
name: 'Pending',
|
||||
value: 'pending',
|
||||
},
|
||||
{
|
||||
name: 'Solved',
|
||||
value: 'solved',
|
||||
},
|
||||
{
|
||||
name: 'Closed',
|
||||
value: 'closed',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The state of the ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Subject',
|
||||
name: 'subject',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The value of the subject field for this ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Tags',
|
||||
name: 'tags',
|
||||
|
@ -348,40 +453,11 @@ export const ticketFields = [
|
|||
default: '',
|
||||
description: 'The type of this ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
name: 'New',
|
||||
value: 'new',
|
||||
},
|
||||
{
|
||||
name: 'Pending',
|
||||
value: 'pending',
|
||||
},
|
||||
{
|
||||
name: 'Solved',
|
||||
value: 'solved',
|
||||
},
|
||||
{
|
||||
name: 'Closed',
|
||||
value: 'closed',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The state of the ticket',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: ' Custom Fields',
|
||||
name: 'customFieldsJson',
|
||||
displayName: ' Update Fields',
|
||||
name: 'updateFieldsJson',
|
||||
type: 'json',
|
||||
typeOptions: {
|
||||
alwaysOpenEditWindow: true,
|
||||
|
@ -400,14 +476,14 @@ export const ticketFields = [
|
|||
],
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
description: `Array of customs fields <a href='https://developer.zendesk.com/rest_api/docs/support/tickets#setting-custom-field-values'>Details</a>`,
|
||||
description: `Object of values to update as described <a href="https://developer.zendesk.com/rest_api/docs/support/tickets" target="_blank">here</a>.`,
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticket:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'ID',
|
||||
displayName: 'Ticket ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
|
@ -485,35 +561,6 @@ export const ticketFields = [
|
|||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
name: 'New',
|
||||
value: 'new',
|
||||
},
|
||||
{
|
||||
name: 'Pending',
|
||||
value: 'pending',
|
||||
},
|
||||
{
|
||||
name: 'Solved',
|
||||
value: 'solved',
|
||||
},
|
||||
{
|
||||
name: 'Closed',
|
||||
value: 'closed',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The state of the ticket',
|
||||
},
|
||||
{
|
||||
displayName: 'Sort By',
|
||||
name: 'sortBy',
|
||||
|
@ -559,7 +606,36 @@ export const ticketFields = [
|
|||
],
|
||||
default: 'desc',
|
||||
description: 'Sort order',
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
name: 'New',
|
||||
value: 'new',
|
||||
},
|
||||
{
|
||||
name: 'Pending',
|
||||
value: 'pending',
|
||||
},
|
||||
{
|
||||
name: 'Solved',
|
||||
value: 'solved',
|
||||
},
|
||||
{
|
||||
name: 'Closed',
|
||||
value: 'closed',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The state of the ticket',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
@ -567,7 +643,7 @@ export const ticketFields = [
|
|||
/* ticket:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'ID',
|
||||
displayName: 'Ticket ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
default: '',
|
||||
|
|
|
@ -54,4 +54,49 @@ export const ticketFieldFields = [
|
|||
},
|
||||
description: 'ticketField ID',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ticketField:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'ticketField',
|
||||
],
|
||||
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: [
|
||||
'ticketField',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 100,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
validateJSON,
|
||||
zendeskApiRequest,
|
||||
zendeskApiRequestAllItems,
|
||||
} from './GenericFunctions';
|
||||
|
@ -30,6 +31,7 @@ import {
|
|||
ITicket,
|
||||
IComment,
|
||||
} from './TicketInterface';
|
||||
import { response } from 'express';
|
||||
|
||||
export class Zendesk implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
|
@ -83,6 +85,33 @@ export class Zendesk implements INodeType {
|
|||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the custom fields to display them to user so that he can
|
||||
// select them easily
|
||||
async getCustomFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const customFields = [
|
||||
'text',
|
||||
'textarea',
|
||||
'date',
|
||||
'integer',
|
||||
'decimal',
|
||||
'regexp',
|
||||
'multiselect',
|
||||
'tagger',
|
||||
];
|
||||
const fields = await zendeskApiRequestAllItems.call(this, 'ticket_fields', 'GET', '/ticket_fields');
|
||||
for (const field of fields) {
|
||||
if (customFields.includes(field.type)) {
|
||||
const fieldName = field.title;
|
||||
const fieldId = field.id;
|
||||
returnData.push({
|
||||
name: fieldName,
|
||||
value: fieldId,
|
||||
});
|
||||
}
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
// Get all the groups to display them to user so that he can
|
||||
// select them easily
|
||||
async getGroups(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
|
@ -131,42 +160,54 @@ export class Zendesk implements INodeType {
|
|||
if (operation === 'create') {
|
||||
const description = this.getNodeParameter('description', i) as string;
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
||||
const comment: IComment = {
|
||||
body: description,
|
||||
};
|
||||
const body: ITicket = {
|
||||
comment,
|
||||
};
|
||||
if (additionalFields.type) {
|
||||
body.type = additionalFields.type as string;
|
||||
}
|
||||
if (additionalFields.externalId) {
|
||||
body.external_id = additionalFields.externalId as string;
|
||||
}
|
||||
if (additionalFields.subject) {
|
||||
body.subject = additionalFields.subject as string;
|
||||
}
|
||||
if (additionalFields.status) {
|
||||
body.status = additionalFields.status as string;
|
||||
}
|
||||
if (additionalFields.recipient) {
|
||||
body.recipient = additionalFields.recipient as string;
|
||||
}
|
||||
if (additionalFields.group) {
|
||||
body.group = additionalFields.group as string;
|
||||
}
|
||||
if (additionalFields.tags) {
|
||||
body.tags = additionalFields.tags as string[];
|
||||
}
|
||||
if (jsonParameters) {
|
||||
const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string;
|
||||
try {
|
||||
JSON.parse(customFieldsJson);
|
||||
} catch(err) {
|
||||
throw new Error('Custom fields must be a valid JSON');
|
||||
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.type) {
|
||||
body.type = additionalFields.type as string;
|
||||
}
|
||||
if (additionalFields.externalId) {
|
||||
body.external_id = additionalFields.externalId as string;
|
||||
}
|
||||
if (additionalFields.subject) {
|
||||
body.subject = additionalFields.subject as string;
|
||||
}
|
||||
if (additionalFields.status) {
|
||||
body.status = additionalFields.status as string;
|
||||
}
|
||||
if (additionalFields.recipient) {
|
||||
body.recipient = additionalFields.recipient as string;
|
||||
}
|
||||
if (additionalFields.group) {
|
||||
body.group = additionalFields.group as string;
|
||||
}
|
||||
if (additionalFields.tags) {
|
||||
body.tags = additionalFields.tags as string[];
|
||||
}
|
||||
if (additionalFields.customFieldsUi) {
|
||||
body.custom_fields = (additionalFields.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
|
||||
}
|
||||
body.custom_fields = JSON.parse(customFieldsJson);
|
||||
}
|
||||
responseData = await zendeskApiRequest.call(this, 'POST', '/tickets', { ticket: body });
|
||||
responseData = responseData.ticket;
|
||||
|
@ -175,37 +216,50 @@ export class Zendesk implements INodeType {
|
|||
if (operation === 'update') {
|
||||
const ticketId = this.getNodeParameter('id', i) as string;
|
||||
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
const body: ITicket = {};
|
||||
if (updateFields.type) {
|
||||
body.type = updateFields.type as string;
|
||||
}
|
||||
if (updateFields.externalId) {
|
||||
body.external_id = updateFields.externalId as string;
|
||||
}
|
||||
if (updateFields.subject) {
|
||||
body.subject = updateFields.subject as string;
|
||||
}
|
||||
if (updateFields.status) {
|
||||
body.status = updateFields.status as string;
|
||||
}
|
||||
if (updateFields.recipient) {
|
||||
body.recipient = updateFields.recipient as string;
|
||||
}
|
||||
if (updateFields.group) {
|
||||
body.group = updateFields.group as string;
|
||||
}
|
||||
if (updateFields.tags) {
|
||||
body.tags = updateFields.tags as string[];
|
||||
}
|
||||
|
||||
if (jsonParameters) {
|
||||
const customFieldsJson = this.getNodeParameter('customFieldsJson', i) as string;
|
||||
try {
|
||||
JSON.parse(customFieldsJson);
|
||||
} catch(err) {
|
||||
throw new Error('Custom fields must be a valid JSON');
|
||||
const updateFieldsJson = this.getNodeParameter('updateFieldsJson', i) as string;
|
||||
|
||||
if (updateFieldsJson !== '' ) {
|
||||
|
||||
if (validateJSON(updateFieldsJson) !== undefined) {
|
||||
|
||||
Object.assign(body, JSON.parse(updateFieldsJson));
|
||||
|
||||
} else {
|
||||
throw new Error('Additional fields must be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
|
||||
|
||||
if (updateFields.type) {
|
||||
body.type = updateFields.type as string;
|
||||
}
|
||||
if (updateFields.externalId) {
|
||||
body.external_id = updateFields.externalId as string;
|
||||
}
|
||||
if (updateFields.subject) {
|
||||
body.subject = updateFields.subject as string;
|
||||
}
|
||||
if (updateFields.status) {
|
||||
body.status = updateFields.status as string;
|
||||
}
|
||||
if (updateFields.recipient) {
|
||||
body.recipient = updateFields.recipient as string;
|
||||
}
|
||||
if (updateFields.group) {
|
||||
body.group = updateFields.group as string;
|
||||
}
|
||||
if (updateFields.tags) {
|
||||
body.tags = updateFields.tags as string[];
|
||||
}
|
||||
if (updateFields.customFieldsUi) {
|
||||
body.custom_fields = (updateFields.customFieldsUi as IDataObject).customFieldsValues as IDataObject[];
|
||||
}
|
||||
body.custom_fields = JSON.parse(customFieldsJson);
|
||||
}
|
||||
responseData = await zendeskApiRequest.call(this, 'PUT', `/tickets/${ticketId}`, { ticket: body });
|
||||
responseData = responseData.ticket;
|
||||
|
@ -259,8 +313,15 @@ export class Zendesk implements INodeType {
|
|||
}
|
||||
//https://developer.zendesk.com/rest_api/docs/support/ticket_fields#list-ticket-fields
|
||||
if (operation === 'getAll') {
|
||||
responseData = await zendeskApiRequest.call(this, 'GET', '/ticket_fields', {}, qs);
|
||||
responseData = responseData.ticket_fields;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
if (returnAll) {
|
||||
responseData = await zendeskApiRequestAllItems.call(this, 'ticket_fields', 'GET', '/ticket_fields', {}, qs);
|
||||
} else {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
responseData = await zendeskApiRequestAllItems.call(this, 'ticket_fields', 'GET', '/ticket_fields', {}, qs);
|
||||
responseData = responseData.slice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(responseData)) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-nodes-base",
|
||||
"version": "0.55.0",
|
||||
"version": "0.57.1",
|
||||
"description": "Base nodes of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -287,7 +287,7 @@
|
|||
"@types/xml2js": "^0.4.3",
|
||||
"gulp": "^4.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"n8n-workflow": "~0.26.0",
|
||||
"n8n-workflow": "~0.28.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.17.0",
|
||||
"typescript": "~3.7.4"
|
||||
|
@ -310,7 +310,7 @@
|
|||
"moment-timezone": "0.5.28",
|
||||
"mongodb": "^3.3.2",
|
||||
"mysql2": "^2.0.1",
|
||||
"n8n-core": "~0.29.0",
|
||||
"n8n-core": "~0.31.0",
|
||||
"nodemailer": "^5.1.1",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"pg-promise": "^9.0.3",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-workflow",
|
||||
"version": "0.26.0",
|
||||
"version": "0.28.0",
|
||||
"description": "Workflow base code of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
|
|
@ -206,7 +206,7 @@ export interface IExecuteFunctions {
|
|||
getWorkflowStaticData(type: string): IDataObject;
|
||||
getRestApiUrl(): string;
|
||||
getTimezone(): string;
|
||||
getWorkflow(workflow: Workflow): IWorkflowMetadata;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
|
@ -225,7 +225,7 @@ export interface IExecuteSingleFunctions {
|
|||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getRestApiUrl(): string;
|
||||
getTimezone(): string;
|
||||
getWorkflow(workflow: Workflow): IWorkflowMetadata;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
getWorkflowDataProxy(): IWorkflowDataProxyData;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
|
@ -260,7 +260,7 @@ export interface IHookFunctions {
|
|||
getTimezone(): string;
|
||||
getWebhookDescription(name: string): IWebhookDescription | undefined;
|
||||
getWebhookName(): string;
|
||||
getWorkflow(workflow: Workflow): IWorkflowMetadata;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
|
@ -275,7 +275,7 @@ export interface IPollFunctions {
|
|||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getRestApiUrl(): string;
|
||||
getTimezone(): string;
|
||||
getWorkflow(workflow: Workflow): IWorkflowMetadata;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
|
@ -290,7 +290,7 @@ export interface ITriggerFunctions {
|
|||
getNodeParameter(parameterName: string, fallbackValue?: any): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object; //tslint:disable-line:no-any
|
||||
getRestApiUrl(): string;
|
||||
getTimezone(): string;
|
||||
getWorkflow(workflow: Workflow): IWorkflowMetadata;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
|
@ -311,7 +311,7 @@ export interface IWebhookFunctions {
|
|||
getTimezone(): string;
|
||||
getWebhookName(): string;
|
||||
getWorkflowStaticData(type: string): IDataObject;
|
||||
getWorkflow(workflow: Workflow): IWorkflowMetadata;
|
||||
getWorkflow(): IWorkflowMetadata;
|
||||
prepareOutputData(outputData: INodeExecutionData[], outputIndex?: number): Promise<INodeExecutionData[][]>;
|
||||
helpers: {
|
||||
[key: string]: (...args: any[]) => any //tslint:disable-line:no-any
|
||||
|
@ -331,6 +331,7 @@ export interface INode {
|
|||
retryOnFail?: boolean;
|
||||
maxTries?: number;
|
||||
waitBetweenTries?: number;
|
||||
alwaysOutputData?: boolean;
|
||||
continueOnFail?: boolean;
|
||||
parameters: INodeParameters;
|
||||
credentials?: INodeCredentials;
|
||||
|
@ -573,6 +574,7 @@ export interface IWorkflowDataProxyData {
|
|||
$env: any; // tslint:disable-line:no-any
|
||||
$evaluateExpression: any; // tslint:disable-line:no-any
|
||||
$item: any; // tslint:disable-line:no-any
|
||||
$items: any; // tslint:disable-line:no-any
|
||||
$json: any; // tslint:disable-line:no-any
|
||||
$node: any; // tslint:disable-line:no-any
|
||||
$parameter: any; // tslint:disable-line:no-any
|
||||
|
|
|
@ -899,14 +899,13 @@ export class Workflow {
|
|||
// Generate a data proxy which allows to query workflow data
|
||||
const dataProxy = new WorkflowDataProxy(this, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData);
|
||||
const data = dataProxy.getDataProxy();
|
||||
data.$evaluateExpression = (expression: string) => {
|
||||
return this.resolveSimpleParameterValue('=' + expression, runExecutionData, runIndex, itemIndex, activeNodeName, connectionInputData, returnObjectAsString);
|
||||
};
|
||||
|
||||
// Execute the expression
|
||||
try {
|
||||
const returnValue = tmpl.tmpl(parameterValue, data);
|
||||
if (returnValue !== null && typeof returnValue === 'object') {
|
||||
if (typeof returnValue === 'function') {
|
||||
throw new Error('Expression resolved to a function. Please add "()"');
|
||||
} else if (returnValue !== null && typeof returnValue === 'object') {
|
||||
if (returnObjectAsString === true) {
|
||||
return this.convertObjectValueToString(returnValue);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
export class WorkflowDataProxy {
|
||||
private workflow: Workflow;
|
||||
private runExecutionData: IRunExecutionData | null;
|
||||
private defaultReturnRunIndex: number;
|
||||
private runIndex: number;
|
||||
private itemIndex: number;
|
||||
private activeNodeName: string;
|
||||
|
@ -19,9 +20,10 @@ export class WorkflowDataProxy {
|
|||
|
||||
|
||||
|
||||
constructor(workflow: Workflow, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[]) {
|
||||
constructor(workflow: Workflow, runExecutionData: IRunExecutionData | null, runIndex: number, itemIndex: number, activeNodeName: string, connectionInputData: INodeExecutionData[], defaultReturnRunIndex = -1) {
|
||||
this.workflow = workflow;
|
||||
this.runExecutionData = runExecutionData;
|
||||
this.defaultReturnRunIndex = defaultReturnRunIndex;
|
||||
this.runIndex = runIndex;
|
||||
this.itemIndex = itemIndex;
|
||||
this.activeNodeName = activeNodeName;
|
||||
|
@ -104,6 +106,80 @@ export class WorkflowDataProxy {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the node ExecutionData
|
||||
*
|
||||
* @private
|
||||
* @param {string} nodeName The name of the node query data from
|
||||
* @param {boolean} [shortSyntax=false] If short syntax got used
|
||||
* @param {number} [outputIndex] The index of the output, if not given the first one gets used
|
||||
* @param {number} [runIndex] The index of the run, if not given the current one does get used
|
||||
* @returns {INodeExecutionData[]}
|
||||
* @memberof WorkflowDataProxy
|
||||
*/
|
||||
private getNodeExecutionData(nodeName: string, shortSyntax = false, outputIndex?: number, runIndex?: number): INodeExecutionData[] {
|
||||
const that = this;
|
||||
|
||||
let executionData: INodeExecutionData[];
|
||||
if (shortSyntax === false) {
|
||||
// Long syntax got used to return data from node in path
|
||||
|
||||
if (that.runExecutionData === null) {
|
||||
throw new Error(`Workflow did not run so do not have any execution-data.`);
|
||||
}
|
||||
|
||||
if (!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName)) {
|
||||
throw new Error(`No execution data found for node "${nodeName}"`);
|
||||
}
|
||||
|
||||
runIndex = runIndex === undefined ? that.defaultReturnRunIndex : runIndex;
|
||||
runIndex = runIndex === -1 ? (that.runExecutionData.resultData.runData[nodeName].length -1) : runIndex;
|
||||
|
||||
if (that.runExecutionData.resultData.runData[nodeName].length < runIndex) {
|
||||
throw new Error(`No execution data found for run "${runIndex}" of node "${nodeName}"`);
|
||||
}
|
||||
|
||||
const taskData = that.runExecutionData.resultData.runData[nodeName][runIndex].data!;
|
||||
|
||||
if (taskData.main === null || !taskData.main.length || taskData.main[0] === null) {
|
||||
// throw new Error(`No data found for item-index: "${itemIndex}"`);
|
||||
throw new Error(`No data found from "main" input.`);
|
||||
}
|
||||
|
||||
// Check from which output to read the data.
|
||||
// Depends on how the nodes are connected.
|
||||
// (example "IF" node. If node is connected to "true" or to "false" output)
|
||||
if (outputIndex === undefined) {
|
||||
const outputIndex = that.workflow.getNodeConnectionOutputIndex(that.activeNodeName, nodeName, 'main');
|
||||
|
||||
if (outputIndex === undefined) {
|
||||
throw new Error(`The node "${that.activeNodeName}" is not connected with node "${nodeName}" so no data can get returned from it.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (outputIndex === undefined) {
|
||||
outputIndex = 0;
|
||||
}
|
||||
|
||||
if (taskData.main.length < outputIndex) {
|
||||
throw new Error(`No data found from "main" input with index "${outputIndex}" via which node is connected with.`);
|
||||
}
|
||||
|
||||
executionData = taskData.main[outputIndex] as INodeExecutionData[];
|
||||
} else {
|
||||
// Short syntax got used to return data from active node
|
||||
|
||||
// TODO: Here have to generate connection Input data for the current node by itself
|
||||
// Data needed:
|
||||
// #- the run-index
|
||||
// - node which did send data (has to be the one from last recent execution)
|
||||
// - later also the name of the input and its index (currently not needed as it is always "main" and index "0")
|
||||
executionData = that.connectionInputData;
|
||||
}
|
||||
|
||||
return executionData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a proxy which allows to query data of a given node
|
||||
|
@ -128,53 +204,7 @@ export class WorkflowDataProxy {
|
|||
name = name.toString();
|
||||
|
||||
if (['binary', 'data', 'json'].includes(name)) {
|
||||
let executionData: INodeExecutionData[];
|
||||
if (shortSyntax === false) {
|
||||
// Long syntax got used to return data from node in path
|
||||
|
||||
if (that.runExecutionData === null) {
|
||||
throw new Error(`Workflow did not run so do not have any execution-data.`);
|
||||
}
|
||||
|
||||
if (!that.runExecutionData.resultData.runData.hasOwnProperty(nodeName)) {
|
||||
throw new Error(`No execution data found for node "${nodeName}"`);
|
||||
}
|
||||
|
||||
if (that.runExecutionData.resultData.runData[nodeName].length < that.runIndex) {
|
||||
throw new Error(`No execution data found for run "${that.runIndex}" of node "${nodeName}"`);
|
||||
}
|
||||
|
||||
const taskData = that.runExecutionData.resultData.runData[nodeName][that.runIndex].data!;
|
||||
|
||||
if (taskData.main === null || !taskData.main.length || taskData.main[0] === null) {
|
||||
// throw new Error(`No data found for item-index: "${itemIndex}"`);
|
||||
throw new Error(`No data found from "main" input.`);
|
||||
}
|
||||
|
||||
// Check from which output to read the data.
|
||||
// Depends on how the nodes are connected.
|
||||
// (example "IF" node. If node is connected to "true" or to "false" output)
|
||||
const outputIndex = that.workflow.getNodeConnectionOutputIndex(that.activeNodeName, nodeName, 'main');
|
||||
|
||||
if (outputIndex === undefined) {
|
||||
throw new Error(`The node "${that.activeNodeName}" is not connected with node "${nodeName}" so no data can get returned from it.`);
|
||||
}
|
||||
|
||||
if (taskData.main.length < outputIndex) {
|
||||
throw new Error(`No data found from "main" input with index "${outputIndex}" via which node is connected with.`);
|
||||
}
|
||||
|
||||
executionData = taskData.main[outputIndex] as INodeExecutionData[];
|
||||
} else {
|
||||
// Short syntax got used to return data from active node
|
||||
|
||||
// TODO: Here have to generate connection Input data for the current node by itself
|
||||
// Data needed:
|
||||
// #- the run-index
|
||||
// - node which did send data (has to be the one from last recent execution)
|
||||
// - later also the name of the input and its index (currently not needed as it is always "main" and index "0")
|
||||
executionData = that.connectionInputData;
|
||||
}
|
||||
const executionData = that.getNodeExecutionData(nodeName, shortSyntax, undefined);
|
||||
|
||||
if (executionData.length <= that.itemIndex) {
|
||||
throw new Error(`No data found for item-index: "${that.itemIndex}"`);
|
||||
|
@ -214,6 +244,11 @@ export class WorkflowDataProxy {
|
|||
} else if (name === 'parameter') {
|
||||
// Get node parameter data
|
||||
return that.nodeParameterGetter(nodeName);
|
||||
} else if (name === 'runIndex') {
|
||||
if (that.runExecutionData === null || !that.runExecutionData.resultData.runData[nodeName]) {
|
||||
return -1;
|
||||
}
|
||||
return that.runExecutionData.resultData.runData[nodeName].length - 1;
|
||||
}
|
||||
|
||||
return Reflect.get(target, name, receiver);
|
||||
|
@ -300,14 +335,32 @@ export class WorkflowDataProxy {
|
|||
$binary: {}, // Placeholder
|
||||
$data: {}, // Placeholder
|
||||
$env: this.envGetter(),
|
||||
$evaluateExpression: (expression: string) => { }, // Placeholder
|
||||
$item: (itemIndex: number) => {
|
||||
const dataProxy = new WorkflowDataProxy(this.workflow, this.runExecutionData, this.runIndex, itemIndex, this.activeNodeName, this.connectionInputData);
|
||||
$evaluateExpression: (expression: string, itemIndex?: number) => {
|
||||
itemIndex = itemIndex || that.itemIndex;
|
||||
return that.workflow.getParameterValue('=' + expression, that.runExecutionData, that.runIndex, itemIndex, that.activeNodeName, that.connectionInputData);
|
||||
},
|
||||
$item: (itemIndex: number, runIndex?: number) => {
|
||||
const defaultReturnRunIndex = runIndex === undefined ? -1 : runIndex;
|
||||
const dataProxy = new WorkflowDataProxy(this.workflow, this.runExecutionData, this.runIndex, itemIndex, this.activeNodeName, this.connectionInputData, defaultReturnRunIndex);
|
||||
return dataProxy.getDataProxy();
|
||||
},
|
||||
$items: (nodeName?: string, outputIndex?: number, runIndex?: number) => {
|
||||
let executionData: INodeExecutionData[];
|
||||
|
||||
if (nodeName === undefined) {
|
||||
executionData = that.connectionInputData;
|
||||
} else {
|
||||
outputIndex = outputIndex || 0;
|
||||
runIndex = runIndex === undefined ? -1 : runIndex;
|
||||
executionData = that.getNodeExecutionData(nodeName, false, outputIndex, runIndex);
|
||||
}
|
||||
|
||||
return executionData;
|
||||
},
|
||||
$json: {}, // Placeholder
|
||||
$node: this.nodeGetter(),
|
||||
$parameter: this.nodeParameterGetter(this.activeNodeName),
|
||||
$runIndex: this.runIndex,
|
||||
$workflow: this.workflowGetter(),
|
||||
};
|
||||
|
||||
|
|
|
@ -936,6 +936,29 @@ describe('Workflow', () => {
|
|||
value1: 'default-value1',
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'return resolved value when referencing another property with expression on another node (long "$node["{NODE}"].parameter" syntax)',
|
||||
input: {
|
||||
Node1: {
|
||||
parameters: {
|
||||
value1: 'valueNode1',
|
||||
}
|
||||
},
|
||||
Node2: {
|
||||
parameters: {
|
||||
value1: '={{$node["Node1"].parameter.value1}}a',
|
||||
},
|
||||
},
|
||||
Node3: {
|
||||
parameters: {
|
||||
value1: '={{$node["Node2"].parameter.value1}}b',
|
||||
},
|
||||
}
|
||||
},
|
||||
output: {
|
||||
value1: 'valueNode1ab',
|
||||
},
|
||||
},
|
||||
// TODO: Make that this test does not fail!
|
||||
// {
|
||||
// description: 'return resolved value when short "data" syntax got used in expression on paramter of not active node which got referenced by active one',
|
||||
|
@ -1203,11 +1226,12 @@ describe('Workflow', () => {
|
|||
{
|
||||
startTime: 1,
|
||||
executionTime: 1,
|
||||
// @ts-ignore
|
||||
data: {
|
||||
main: [
|
||||
[
|
||||
{}
|
||||
{
|
||||
json: {},
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue