🔀 Merge branch 'master' into Improve-Zulip-Node

This commit is contained in:
Jan Oberhauser 2020-05-31 10:59:37 +02:00
commit e320ace9a0
21 changed files with 2064 additions and 732 deletions

View file

@ -8,9 +8,16 @@ fi
if [ "$#" -gt 0 ]; then if [ "$#" -gt 0 ]; then
# Got started with arguments # Got started with arguments
COMMAND=$1;
if [[ "$COMMAND" == "n8n" ]]; then
shift shift
exec su-exec node ./packages/cli/bin/n8n "$@" exec su-exec node ./packages/cli/bin/n8n "$@"
else
exec su-exec node "$@"
fi
else else
# Got started without arguments # Got started without arguments
exec su-exec node ./packages/cli/bin/n8n exec su-exec node ./packages/cli/bin/n8n
fi fi

View file

@ -17,6 +17,7 @@ n8n is a free and open [fair-code](http://faircode.io) licensed node based Workf
- [Securing n8n](#securing-n8n) - [Securing n8n](#securing-n8n)
- [Persist data](#persist-data) - [Persist data](#persist-data)
- [Passing Sensitive Data via File](#passing-sensitive-data-via-file) - [Passing Sensitive Data via File](#passing-sensitive-data-via-file)
- [Updating a Running docker-compose Instance](#updating-a-running-docker-compose-instance)
- [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt) - [Example Setup with Lets Encrypt](#example-setup-with-lets-encrypt)
- [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it) - [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it)
- [Support](#support) - [Support](#support)
@ -226,6 +227,18 @@ The following environment variables support file input:
A basic step by step example setup of n8n with docker-compose and Lets Encrypt is available on the A basic step by step example setup of n8n with docker-compose and Lets Encrypt is available on the
[Server Setup](https://docs.n8n.io/#/server-setup) page. [Server Setup](https://docs.n8n.io/#/server-setup) page.
## Updating a running docker-compose instance
```
# Pull down the latest version from dockerhub
docker pull n8nio/n8n
# Stop current setup
sudo docker-compose stop
# Delete it (will only delete the docker-containers, data is stored separately)
sudo docker-compose rm
# Then start it again
sudo docker-compose up -d
```
## Setting Timezone ## Setting Timezone

View file

@ -192,7 +192,7 @@ class LoadNodesAndCredentialsClass {
* @memberof N8nPackagesInformationClass * @memberof N8nPackagesInformationClass
*/ */
async loadDataFromDirectory(setPackageName: string, directory: string): Promise<void> { async loadDataFromDirectory(setPackageName: string, directory: string): Promise<void> {
const files = await glob(path.join(directory, '*\.@(node|credentials)\.js')); const files = await glob(path.join(directory, '**/*\.@(node|credentials)\.js'));
let fileName: string; let fileName: string;
let type: string; let type: string;

View file

@ -19,7 +19,16 @@
<div class="header"> <div class="header">
<div class="title-text"> <div class="title-text">
<strong>Results: {{ dataCount }}</strong>&nbsp; <strong v-if="dataCount < this.MAX_DISPLAY_ITEMS_AUTO_ALL && dataSize < MAX_DISPLAY_DATA_SIZE">
Results: {{ dataCount }}
</strong>
<strong v-else>Results:
<el-select v-model="maxDisplayItems" @click.stop>
<el-option v-for="option in maxDisplayItemsOptions" :label="option" :value="option" :key="option" />
</el-select>&nbsp;/
{{ dataCount }}
</strong>
&nbsp;
<el-popover <el-popover
v-if="runMetadata" v-if="runMetadata"
placement="right" placement="right"
@ -184,6 +193,11 @@ import {
ITableData, ITableData,
} from '@/Interface'; } from '@/Interface';
import {
MAX_DISPLAY_DATA_SIZE,
MAX_DISPLAY_ITEMS_AUTO_ALL,
} from '@/constants';
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue'; import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
import { genericHelpers } from '@/components/mixins/genericHelpers'; import { genericHelpers } from '@/components/mixins/genericHelpers';
@ -211,8 +225,12 @@ export default mixins(
runIndex: 0, runIndex: 0,
showData: false, showData: false,
outputIndex: 0, outputIndex: 0,
maxDisplayItems: 25 as number | null,
binaryDataDisplayVisible: false, binaryDataDisplayVisible: false,
binaryDataDisplayData: null as IBinaryDisplayData | null, binaryDataDisplayData: null as IBinaryDisplayData | null,
MAX_DISPLAY_DATA_SIZE,
MAX_DISPLAY_ITEMS_AUTO_ALL,
}; };
}, },
computed: { computed: {
@ -229,6 +247,9 @@ export default mixins(
const executionData: IRunExecutionData = this.workflowExecution.data; const executionData: IRunExecutionData = this.workflowExecution.data;
return executionData.resultData.runData; return executionData.resultData.runData;
}, },
maxDisplayItemsOptions (): number[] {
return [25, 50, 100, 250, 500, 1000, this.dataCount].filter(option => option <= this.dataCount);
},
node (): INodeUi | null { node (): INodeUi | null {
return this.$store.getters.activeNode; return this.$store.getters.activeNode;
}, },
@ -323,19 +344,27 @@ export default mixins(
return 0; return 0;
}, },
jsonData (): IDataObject[] { jsonData (): IDataObject[] {
const inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex); let inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex);
if (inputData.length === 0 || !Array.isArray(inputData)) { if (inputData.length === 0 || !Array.isArray(inputData)) {
return []; return [];
} }
if (this.maxDisplayItems !== null) {
inputData = inputData.slice(0, this.maxDisplayItems);
}
return this.convertToJson(inputData); return this.convertToJson(inputData);
}, },
tableData (): ITableData | undefined { tableData (): ITableData | undefined {
const inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex); let inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex);
if (inputData.length === 0) { if (inputData.length === 0) {
return undefined; return undefined;
} }
if (this.maxDisplayItems !== null) {
inputData = inputData.slice(0,this.maxDisplayItems);
}
return this.convertToTable(inputData); return this.convertToTable(inputData);
}, },
binaryData (): IBinaryKeyData[] { binaryData (): IBinaryKeyData[] {
@ -450,9 +479,12 @@ export default mixins(
// Check how much data there is to display // Check how much data there is to display
const inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex); const inputData = this.getNodeInputData(this.node, this.runIndex, this.outputIndex);
this.dataSize = JSON.stringify(inputData).length;
if (this.dataSize < 204800) { const jsonItems = inputData.slice(0, this.maxDisplayItems || inputData.length).map(item => item.json);
this.dataSize = JSON.stringify(jsonItems).length;
if (this.dataSize < this.MAX_DISPLAY_DATA_SIZE) {
// Data is reasonable small (< 200kb) so display it directly // Data is reasonable small (< 200kb) so display it directly
this.showData = true; this.showData = true;
} }
@ -466,6 +498,7 @@ export default mixins(
node (newNode, oldNode) { node (newNode, oldNode) {
// Reset the selected output index every time another node gets selected // Reset the selected output index every time another node gets selected
this.outputIndex = 0; this.outputIndex = 0;
this.maxDisplayItems = 25;
this.refreshDataSize(); this.refreshDataSize();
}, },
jsonData () { jsonData () {

View file

@ -65,6 +65,7 @@ export const nodeBase = mixins(nodeIndex).extend({
'name', 'name',
'nodeId', 'nodeId',
'instance', 'instance',
'isReadOnly',
], ],
methods: { methods: {
__addNode (node: INodeUi) { __addNode (node: INodeUi) {
@ -182,7 +183,7 @@ export const nodeBase = mixins(nodeIndex).extend({
endpoint: inputData.endpoint, endpoint: inputData.endpoint,
endpointStyle: inputData.endpointStyle, endpointStyle: inputData.endpointStyle,
isSource: false, isSource: false,
isTarget: true, isTarget: !this.isReadOnly,
parameters: { parameters: {
nodeIndex: this.nodeIndex, nodeIndex: this.nodeIndex,
type: inputName, type: inputName,
@ -246,7 +247,7 @@ export const nodeBase = mixins(nodeIndex).extend({
maxConnections: inputData.maxConnections, maxConnections: inputData.maxConnections,
endpoint: inputData.endpoint, endpoint: inputData.endpoint,
endpointStyle: inputData.endpointStyle, endpointStyle: inputData.endpointStyle,
isSource: true, isSource: !this.isReadOnly,
isTarget: false, isTarget: false,
parameters: { parameters: {
nodeIndex: this.nodeIndex, nodeIndex: this.nodeIndex,
@ -275,6 +276,7 @@ export const nodeBase = mixins(nodeIndex).extend({
this.instance.addEndpoint(this.nodeName, newEndpointData); this.instance.addEndpoint(this.nodeName, newEndpointData);
}); });
if (this.isReadOnly === false) {
// Make nodes draggable // Make nodes draggable
this.instance.draggable(this.nodeName, { this.instance.draggable(this.nodeName, {
grid: [10, 10], grid: [10, 10],
@ -289,7 +291,7 @@ export const nodeBase = mixins(nodeIndex).extend({
this.$store.commit('addActiveAction', 'dragActive'); this.$store.commit('addActiveAction', 'dragActive');
}, },
stop: (params: { e: MouseEvent}) => { stop: (params: { e: MouseEvent }) => {
if (this.$store.getters.isActionActive('dragActive')) { if (this.$store.getters.isActionActive('dragActive')) {
const moveNodes = this.$store.getters.getSelectedNodes.slice(); const moveNodes = this.$store.getters.getSelectedNodes.slice();
const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name); const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name);
@ -330,6 +332,7 @@ export const nodeBase = mixins(nodeIndex).extend({
}, },
filter: '.node-description, .node-description .node-name, .node-description .node-subtitle', filter: '.node-description, .node-description .node-name, .node-description .node-subtitle',
}); });
}
}, },
isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean { isCtrlKeyPressed (e: MouseEvent | KeyboardEvent): boolean {

View file

@ -1,2 +1,4 @@
export const MAX_DISPLAY_DATA_SIZE = 204800;
export const MAX_DISPLAY_ITEMS_AUTO_ALL = 250;
export const NODE_NAME_PREFIX = 'node-'; export const NODE_NAME_PREFIX = 'node-';
export const PLACEHOLDER_EMPTY_WORKFLOW_ID = '__EMPTY__'; export const PLACEHOLDER_EMPTY_WORKFLOW_ID = '__EMPTY__';

View file

@ -20,6 +20,7 @@
:id="'node-' + getNodeIndex(nodeData.name)" :id="'node-' + getNodeIndex(nodeData.name)"
:key="getNodeIndex(nodeData.name)" :key="getNodeIndex(nodeData.name)"
:name="nodeData.name" :name="nodeData.name"
:isReadOnly="isReadOnly"
:instance="instance" :instance="instance"
></node> ></node>
</div> </div>
@ -102,6 +103,9 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import {
OverlaySpec,
} from 'jsplumb';
import { MessageBoxInputData } from 'element-ui/types/message-box'; import { MessageBoxInputData } from 'element-ui/types/message-box';
import { jsPlumb, Endpoint, OnConnectionBindInfo } from 'jsplumb'; import { jsPlumb, Endpoint, OnConnectionBindInfo } from 'jsplumb';
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants'; import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
@ -1013,21 +1017,9 @@ export default mixins(
} }
}, },
initNodeView () { initNodeView () {
this.instance.importDefaults({ const connectionOverlays: OverlaySpec[] = [];
// notice the 'curviness' argument to this Bezier curve. if (this.isReadOnly === false) {
// the curves on this page are far smoother connectionOverlays.push.apply(connectionOverlays, [
// than the curves on the first demo, which use the default curviness value.
// Connector: ["Bezier", { curviness: 80 }],
Connector: ['Bezier', { curviness: 40 }],
// @ts-ignore
Endpoint: ['Dot', { radius: 5 }],
DragOptions: { cursor: 'pointer', zIndex: 5000 },
PaintStyle: { strokeWidth: 2, stroke: '#334455' },
EndpointStyle: { radius: 9, fill: '#acd', stroke: 'red' },
// EndpointStyle: {},
HoverPaintStyle: { stroke: '#ff6d5a', lineWidth: 4 },
EndpointHoverStyle: { fill: '#ff6d5a', stroke: '#acd' },
ConnectionOverlays: [
[ [
'Arrow', 'Arrow',
{ {
@ -1045,7 +1037,24 @@ export default mixins(
location: 0.5, location: 0.5,
}, },
], ],
], ]);
}
this.instance.importDefaults({
// notice the 'curviness' argument to this Bezier curve.
// the curves on this page are far smoother
// than the curves on the first demo, which use the default curviness value.
// Connector: ["Bezier", { curviness: 80 }],
Connector: ['Bezier', { curviness: 40 }],
// @ts-ignore
Endpoint: ['Dot', { radius: 5 }],
DragOptions: { cursor: 'pointer', zIndex: 5000 },
PaintStyle: { strokeWidth: 2, stroke: '#334455' },
EndpointStyle: { radius: 9, fill: '#acd', stroke: 'red' },
// EndpointStyle: {},
HoverPaintStyle: { stroke: '#ff6d5a', lineWidth: 4 },
EndpointHoverStyle: { fill: '#ff6d5a', stroke: '#acd' },
ConnectionOverlays: connectionOverlays,
Container: '#node-view', Container: '#node-view',
}); });
@ -1100,6 +1109,10 @@ export default mixins(
info.connection.setConnector(['Straight']); info.connection.setConnector(['Straight']);
} }
// @ts-ignore
info.connection.removeOverlay('drop-add-node');
if (this.isReadOnly === false) {
// Display the connection-delete button only on hover // Display the connection-delete button only on hover
let timer: NodeJS.Timeout | undefined; let timer: NodeJS.Timeout | undefined;
info.connection.bind('mouseover', (connection: IConnection) => { info.connection.bind('mouseover', (connection: IConnection) => {
@ -1117,9 +1130,6 @@ export default mixins(
}, 500); }, 500);
}); });
// @ts-ignore
info.connection.removeOverlay('drop-add-node');
// @ts-ignore // @ts-ignore
info.connection.addOverlay([ info.connection.addOverlay([
'Label', 'Label',
@ -1135,6 +1145,7 @@ export default mixins(
}, },
}, },
]); ]);
}
// Display input names if they exist on connection // Display input names if they exist on connection
const targetNodeTypeData: INodeTypeDescription = this.$store.getters.nodeType(targetNode.type); const targetNodeTypeData: INodeTypeDescription = this.$store.getters.nodeType(targetNode.type);
@ -1329,6 +1340,7 @@ export default mixins(
// @ts-ignore // @ts-ignore
this.instance.connect({ this.instance.connect({
uuids: uuid, uuids: uuid,
detachable: !this.isReadOnly,
}); });
} else { } else {
// When nodes get connected it gets saved automatically to the storage // When nodes get connected it gets saved automatically to the storage

View file

@ -19,5 +19,13 @@ export class AgileCrmApi implements ICredentialType {
type: 'string' as NodePropertyTypes, type: 'string' as NodePropertyTypes,
default: '', default: '',
}, },
{
displayName: 'Subdomain',
name: 'subdomain',
type: 'string' as NodePropertyTypes,
default: '',
placeholder: 'example',
description: 'If the domain is https://example.agilecrm.com "example" would have to be entered.',
},
]; ];
} }

View file

@ -27,7 +27,7 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction
username: credentials!.email as string, username: credentials!.email as string,
password: credentials!.apiKey as string password: credentials!.apiKey as string
}, },
uri: uri || `https://n8nio.agilecrm.com/dev/${endpoint}`, uri: uri || `https://${credentials!.subdomain}.agilecrm.com/dev/${endpoint}`,
json: true, json: true,
}; };
@ -45,8 +45,9 @@ export async function agileCrmApiRequest(this: IHookFunctions | IExecuteFunction
} }
export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method = 'PUT', endpoint?: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any export async function agileCrmApiRequestUpdate(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method = 'PUT', endpoint?: string, body: any = {}, query: IDataObject = {}, uri?: string): Promise<any> { // tslint:disable-line:no-any
const baseUri = 'https://n8nio.agilecrm.com/dev/';
const credentials = this.getCredentials('agileCrmApi'); const credentials = this.getCredentials('agileCrmApi');
const baseUri = `https://${credentials!.subdomain}.agilecrm.com/dev/`;
const options: OptionsWithUri = { const options: OptionsWithUri = {
method, method,
headers: { headers: {

View file

@ -432,6 +432,80 @@ export class EditImage implements INodeType {
}, },
description: 'The color to use for the background when image gets rotated by anything which is not a multiple of 90..', description: 'The color to use for the background when image gets rotated by anything which is not a multiple of 90..',
}, },
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
displayOptions: {
hide: {
operation: [
'information',
],
},
},
options: [
{
displayName: 'File Name',
name: 'fileName',
type: 'string',
default: '',
description: 'File name to set in binary data.',
},
{
displayName: 'Format',
name: 'format',
type: 'options',
options: [
{
name: 'bmp',
value: 'bmp',
},
{
name: 'gif',
value: 'gif',
},
{
name: 'jpeg',
value: 'jpeg',
},
{
name: 'png',
value: 'png',
},
{
name: 'tiff',
value: 'tiff',
},
],
default: 'jpeg',
description: 'Set the output image format.',
},
{
displayName: 'Quality',
name: 'quality',
type: 'number',
typeOptions: {
minValue: 0,
maxValue: 100,
},
default: 100,
displayOptions: {
show: {
format: [
'jpeg',
'png',
'tiff',
],
},
},
description: 'Sets the jpeg|png|tiff compression level from 0 to 100 (best).',
},
],
},
] ]
}; };
@ -442,6 +516,8 @@ export class EditImage implements INodeType {
const operation = this.getNodeParameter('operation', 0) as string; const operation = this.getNodeParameter('operation', 0) as string;
const dataPropertyName = this.getNodeParameter('dataPropertyName') as string; const dataPropertyName = this.getNodeParameter('dataPropertyName') as string;
const options = this.getNodeParameter('options', {}) as IDataObject;
// TODO: Later should make so that it sends directly a valid buffer and the buffer.from stuff is not needed anymore // TODO: Later should make so that it sends directly a valid buffer and the buffer.from stuff is not needed anymore
if (item.binary === undefined) { if (item.binary === undefined) {
return item; return item;
@ -550,6 +626,24 @@ export class EditImage implements INodeType {
Object.assign(newItem.binary, item.binary); Object.assign(newItem.binary, item.binary);
} }
if (options.quality !== undefined) {
gmInstance = gmInstance.quality(options.quality as number);
}
if (options.format !== undefined) {
gmInstance = gmInstance.setFormat(options.format as string);
newItem.binary![dataPropertyName as string].fileExtension = options.format as string;
newItem.binary![dataPropertyName as string].mimeType = `image/${options.format}`;
const fileName = newItem.binary![dataPropertyName as string].fileName;
if (fileName && fileName.includes('.')) {
newItem.binary![dataPropertyName as string].fileName = fileName.split('.').slice(0, -1).join('.') + '.' + options.format;
}
}
if (options.fileName !== undefined) {
newItem.binary![dataPropertyName as string].fileName = options.fileName as string;
}
return new Promise<INodeExecutionData>((resolve, reject) => { return new Promise<INodeExecutionData>((resolve, reject) => {
gmInstance gmInstance
.toBuffer((error: Error | null, buffer: Buffer) => { .toBuffer((error: Error | null, buffer: Buffer) => {

View file

@ -1,13 +1,19 @@
import { OptionsWithUri } from 'request'; import {
OptionsWithUri,
} from 'request';
import { import {
IExecuteFunctions, IExecuteFunctions,
IExecuteSingleFunctions,
IHookFunctions, IHookFunctions,
ILoadOptionsFunctions, ILoadOptionsFunctions,
IExecuteSingleFunctions
} from 'n8n-core'; } from 'n8n-core';
export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, headers?: object): Promise<any> { // tslint:disable-line:no-any import {
IDataObject,
} from 'n8n-workflow';
export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, qs: IDataObject = {} ,headers?: object): Promise<any> { // tslint:disable-line:no-any
const credentials = this.getCredentials('mailchimpApi'); const credentials = this.getCredentials('mailchimpApi');
if (credentials === undefined) { if (credentials === undefined) {
@ -27,6 +33,7 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio
const options: OptionsWithUri = { const options: OptionsWithUri = {
headers: headerWithAuthentication, headers: headerWithAuthentication,
method, method,
qs,
uri: `https://${datacenter}.${host}${endpoint}`, uri: `https://${datacenter}.${host}${endpoint}`,
json: true, json: true,
}; };
@ -34,17 +41,34 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio
if (Object.keys(body).length !== 0) { if (Object.keys(body).length !== 0) {
options.body = body; options.body = body;
} }
try { try {
return await this.helpers.request!(options); return await this.helpers.request!(options);
} catch (error) { } catch (error) {
const errorMessage = error.response.body.message || error.response.body.Message; if (error.response.body && error.response.body.detail) {
throw new Error(`Mailchimp Error response [${error.statusCode}]: ${error.response.body.detail}`);
}
throw error;
}
}
if (errorMessage !== undefined) { export async function mailchimpApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, endpoint: string, method: string, propertyName: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
throw errorMessage;
} const returnData: IDataObject[] = [];
throw error.response.body;
} let responseData;
query.offset = 0;
query.count = 500;
do {
responseData = await mailchimpApiRequest.call(this, endpoint, method, body, query);
returnData.push.apply(returnData, responseData[propertyName]);
query.offset += query.count;
} while (
responseData[propertyName] && responseData[propertyName].length !== 0
);
return returnData;
} }
export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any

File diff suppressed because it is too large Load diff

View file

@ -98,9 +98,9 @@ export class Mattermost implements INodeType {
description: 'Soft-deletes a channel', description: 'Soft-deletes a channel',
}, },
{ {
name: 'Members', name: 'Member',
value: 'members', value: 'members',
description: 'Returns the members of a channel.', description: 'Get a page of members for a channel.',
}, },
{ {
name: 'Restore', name: 'Restore',
@ -317,6 +317,23 @@ export class Mattermost implements INodeType {
}, },
description: 'The Mattermost Team.', description: 'The Mattermost Team.',
}, },
{
displayName: 'Resolve Data',
name: 'resolveData',
type: 'boolean',
displayOptions: {
show: {
resource: [
'channel',
],
operation: [
'members',
],
},
},
default: true,
description: 'By default the response only contain the ID of the user.<br />If this option gets activated it will resolve the user automatically.',
},
{ {
displayName: 'Return All', displayName: 'Return All',
name: 'returnAll', name: 'returnAll',
@ -950,6 +967,11 @@ export class Mattermost implements INodeType {
value: 'getByEmail', value: 'getByEmail',
description: 'Get a user by email', description: 'Get a user by email',
}, },
{
name: 'Get By ID',
value: 'getById',
description: 'Get a user by id',
},
], ],
default: '', default: '',
description: 'The operation to perform.', description: 'The operation to perform.',
@ -1113,6 +1135,54 @@ export class Mattermost implements INodeType {
default: '', default: '',
description: `User's email`, description: `User's email`,
}, },
// ----------------------------------
// user:getById
// ----------------------------------
{
displayName: 'User IDs',
name: 'userIds',
type: 'string',
required: true,
displayOptions: {
show: {
resource: [
'user',
],
operation: [
'getById',
],
},
},
default: '',
description: `User's ID`,
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: [
'user',
],
operation: [
'getById',
],
},
},
default: {},
options: [
{
displayName: 'Since',
name: 'since',
type: 'dateTime',
default: '',
description: 'Only return users that have been modified since the given Unix timestamp (in milliseconds).',
},
],
},
], ],
}; };
@ -1245,6 +1315,10 @@ export class Mattermost implements INodeType {
let resource: string; let resource: string;
let requestMethod = 'POST'; let requestMethod = 'POST';
let returnAll = false; let returnAll = false;
let userIds: string[] = [];
resource = this.getNodeParameter('resource', 0) as string;
operation = this.getNodeParameter('operation', 0) as string;
// For Post // For Post
let body: IDataObject; let body: IDataObject;
@ -1256,9 +1330,6 @@ export class Mattermost implements INodeType {
body = {}; body = {};
qs = {}; qs = {};
resource = this.getNodeParameter('resource', i) as string;
operation = this.getNodeParameter('operation', i) as string;
if (resource === 'channel') { if (resource === 'channel') {
if (operation === 'create') { if (operation === 'create') {
// ---------------------------------- // ----------------------------------
@ -1509,6 +1580,25 @@ export class Mattermost implements INodeType {
endpoint = `users/email/${email}`; endpoint = `users/email/${email}`;
} }
if (operation === 'getById') {
// ----------------------------------
// user:getById
// ----------------------------------
userIds = (this.getNodeParameter('userIds', i) as string).split(',') as string[];
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (additionalFields.since) {
qs.since = new Date(additionalFields.since as string).getTime();
}
requestMethod = 'POST';
endpoint = 'users/ids';
//@ts-ignore
body = userIds;
}
} }
else { else {
throw new Error(`The resource "${resource}" is not known!`); throw new Error(`The resource "${resource}" is not known!`);
@ -1519,6 +1609,18 @@ export class Mattermost implements INodeType {
responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs); responseData = await apiRequestAllItems.call(this, requestMethod, endpoint, body, qs);
} else { } else {
responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs); responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);
if (resource === 'channel' && operation === 'members') {
const resolveData = this.getNodeParameter('resolveData', i) as boolean;
if (resolveData) {
const userIds: string[] = [];
for (const data of responseData) {
userIds.push(data.user_id);
}
if (userIds.length > 0) {
responseData = await apiRequest.call(this, 'POST', 'users/ids', userIds , qs);
}
}
}
} }
if (Array.isArray(responseData)) { if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData); returnData.push.apply(returnData, responseData);

View file

@ -40,7 +40,7 @@ export class TrelloTrigger implements INodeType {
webhooks: [ webhooks: [
{ {
name: 'setup', name: 'setup',
httpMethod: 'GET', httpMethod: 'HEAD',
responseMode: 'onReceived', responseMode: 'onReceived',
path: 'webhook', path: 'webhook',
}, },

View file

@ -62,4 +62,3 @@ export function validateJSON(json: string | undefined): any { // tslint:disable-
} }
return result; return result;
} }

View file

@ -51,9 +51,9 @@ export const messageOperations = [
export const messageFields = [ export const messageFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* message:sendPrivate */ /* message:sendPrivate */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'To', displayName: 'To',
name: 'to', name: 'to',
@ -96,9 +96,9 @@ export const messageFields = [
}, },
description: 'The content of the message.', description: 'The content of the message.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* message:sendStream */ /* message:sendStream */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Stream', displayName: 'Stream',
name: 'stream', name: 'stream',
@ -163,9 +163,9 @@ export const messageFields = [
}, },
description: 'The content of the message.', description: 'The content of the message.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* message:update */ /* message:update */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Message ID', displayName: 'Message ID',
name: 'messageId', name: 'messageId',
@ -241,9 +241,9 @@ export const messageFields = [
}, },
] ]
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* message:get */ /* message:get */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Message ID', displayName: 'Message ID',
name: 'messageId', name: 'messageId',
@ -262,9 +262,9 @@ export const messageFields = [
}, },
description: 'Unique identifier for the message.', description: 'Unique identifier for the message.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* message:delete */ /* message:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Message ID', displayName: 'Message ID',
name: 'messageId', name: 'messageId',
@ -283,9 +283,9 @@ export const messageFields = [
}, },
description: 'Unique identifier for the message.', description: 'Unique identifier for the message.',
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* message:updateFile */ /* message:updateFile */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Binary Property', displayName: 'Binary Property',
name: 'dataBinaryProperty', name: 'dataBinaryProperty',

View file

@ -33,11 +33,11 @@ export const streamOperations = [
value: 'getSubscribed', value: 'getSubscribed',
description: 'Get subscribed streams.', description: 'Get subscribed streams.',
}, },
{ // {
name: 'Update', // name: 'Update',
value: 'update', // value: 'update',
description: 'Update a stream.', // description: 'Update a stream.',
}, // },
], ],
default: 'create', default: 'create',
description: 'The operation to perform.', description: 'The operation to perform.',
@ -45,9 +45,9 @@ export const streamOperations = [
] as INodeProperties[]; ] as INodeProperties[];
export const streamFields = [ export const streamFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* stream:create */ /* stream:create */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Subscriptions', displayName: 'Subscriptions',
name: 'subscriptions', name: 'subscriptions',
@ -131,9 +131,56 @@ export const streamFields = [
], ],
}, },
}, },
description: `JSON format parameters for stream creation.`, description: `JSON format parameters for stream creation.`,
}, },
{
displayName: 'Subscriptions',
name: 'subscriptions',
type: 'fixedCollection',
default: {},
displayOptions: {
show: {
resource: [
'stream',
],
operation: [
'create',
],
jsonParameters: [
false,
],
},
},
required: true,
description: 'A list of dictionaries containing the the key name and value specifying the name of the stream to subscribe. If the stream does not exist a new stream is created.',
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Subscription Properties',
name: 'properties',
values: [
{
displayName: 'Name',
name: 'name',
type: 'string',
required: true,
default: '',
description: 'Name of Subscription.',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
required: true,
default: '',
description: 'Description of Subscription.',
}
],
},
],
},
{ {
displayName: 'Additional Fields', displayName: 'Additional Fields',
name: 'additionalFields', name: 'additionalFields',
@ -186,7 +233,7 @@ export const streamFields = [
displayName: 'Principals', displayName: 'Principals',
name: 'principals', name: 'principals',
type: 'fixedCollection', type: 'fixedCollection',
default: '', default: {},
description: 'A list of email addresses of the users that will be subscribed/unsubscribed to the streams specified in the subscriptions argument. If not provided, then the requesting user/bot is subscribed.', description: 'A list of email addresses of the users that will be subscribed/unsubscribed to the streams specified in the subscriptions argument. If not provided, then the requesting user/bot is subscribed.',
typeOptions: { typeOptions: {
multipleValues: true, multipleValues: true,
@ -234,9 +281,10 @@ export const streamFields = [
} }
] ]
}, },
/* -------------------------------------------------------------------------- */
/* stream:get all */ /* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */ /* stream:get all */
/* -------------------------------------------------------------------------- */
{ {
displayName: 'Additional Fields', displayName: 'Additional Fields',
name: 'additionalFields', name: 'additionalFields',
@ -291,9 +339,10 @@ export const streamFields = [
}, },
] ]
}, },
/* -------------------------------------------------------------------------- */
/* stream:get subscribed */ /* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */ /* stream:get subscribed */
/* -------------------------------------------------------------------------- */
{ {
displayName: 'Additional Fields', displayName: 'Additional Fields',
name: 'additionalFields', name: 'additionalFields',
@ -320,9 +369,10 @@ export const streamFields = [
} }
] ]
}, },
/* -------------------------------------------------------------------------- */
/* stream:update */ /* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */ /* stream:update */
/* -------------------------------------------------------------------------- */
{ {
displayName: 'Stream ID', displayName: 'Stream ID',
name: 'streamId', name: 'streamId',
@ -466,9 +516,9 @@ export const streamFields = [
}, },
] ]
}, },
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* stream:delete */ /* stream:delete */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Stream ID', displayName: 'Stream ID',
name: 'streamId', name: 'streamId',

View file

@ -33,11 +33,11 @@ export const userOperations = [
value: 'getAll', value: 'getAll',
description: 'Get all users.', description: 'Get all users.',
}, },
{ // {
name: 'Update', // name: 'Update',
value: 'update', // value: 'update',
description: 'Update a user.', // description: 'Update a user.',
}, // },
], ],
default: 'create', default: 'create',
description: 'The operation to perform.', description: 'The operation to perform.',
@ -45,9 +45,9 @@ export const userOperations = [
] as INodeProperties[]; ] as INodeProperties[];
export const userFields = [ export const userFields = [
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* user:create */ /* user:create */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
{ {
displayName: 'Email', displayName: 'Email',
name: 'email', name: 'email',
@ -120,9 +120,10 @@ export const userFields = [
default: '', default: '',
description: 'The short name of the new user. Not user-visible.', description: 'The short name of the new user. Not user-visible.',
}, },
/* -------------------------------------------------------------------------- */
/* user:get / getAll */ /* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */ /* user:get / getAll */
/* -------------------------------------------------------------------------- */
{ {
displayName: 'User ID', displayName: 'User ID',
name: 'userId', name: 'userId',
@ -174,9 +175,10 @@ export const userFields = [
}, },
] ]
}, },
/* -------------------------------------------------------------------------- */
/* user:update */ /* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */ /* user:update */
/* -------------------------------------------------------------------------- */
{ {
displayName: 'User ID', displayName: 'User ID',
name: 'userId', name: 'userId',
@ -237,7 +239,7 @@ export const userFields = [
displayName: 'Profile Data', displayName: 'Profile Data',
name: 'profileData', name: 'profileData',
type: 'fixedCollection', type: 'fixedCollection',
default: '', default: {},
description: 'A dictionary containing the to be updated custom profile field data for the user.', description: 'A dictionary containing the to be updated custom profile field data for the user.',
typeOptions: { typeOptions: {
multipleValues: true, multipleValues: true,
@ -268,10 +270,11 @@ export const userFields = [
}, },
] ]
}, },
/* -------------------------------------------------------------------------- */
/* user:deactivate */ /* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */ /* user:deactivate */
{ /* -------------------------------------------------------------------------- */
{
displayName: 'User ID', displayName: 'User ID',
name: 'userId', name: 'userId',
type: 'string', type: 'string',
@ -288,5 +291,5 @@ export const userFields = [
}, },
default: '', default: '',
description: 'The ID of user to deactivate.', description: 'The ID of user to deactivate.',
}, },
] as INodeProperties[]; ] as INodeProperties[];

View file

@ -219,7 +219,7 @@ export class Zulip implements INodeType {
} }
} }
}; };
responseData = await zulipApiRequest.call(this, 'POST', '/user_uploads', {}, {}, undefined, { formData } ); responseData = await zulipApiRequest.call(this, 'POST', '/user_uploads', {}, {}, undefined, { formData });
responseData.uri = `${credentials!.url}${responseData.uri}`; responseData.uri = `${credentials!.url}${responseData.uri}`;
} }
} }
@ -247,6 +247,7 @@ export class Zulip implements INodeType {
} }
responseData = await zulipApiRequest.call(this, 'GET', `/streams`, body); responseData = await zulipApiRequest.call(this, 'GET', `/streams`, body);
responseData = responseData.streams;
} }
if (operation === 'getSubscribed') { if (operation === 'getSubscribed') {
@ -257,10 +258,10 @@ export class Zulip implements INodeType {
} }
responseData = await zulipApiRequest.call(this, 'GET', `/users/me/subscriptions`, body); responseData = await zulipApiRequest.call(this, 'GET', `/users/me/subscriptions`, body);
responseData = responseData.subscriptions;
} }
if (operation === 'create') { if (operation === 'create') {
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean; const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
const subscriptions = this.getNodeParameter('subscriptions', i) as IDataObject[]; const subscriptions = this.getNodeParameter('subscriptions', i) as IDataObject[];
@ -271,27 +272,26 @@ export class Zulip implements INodeType {
const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string; const additionalFieldsJson = this.getNodeParameter('additionalFieldsJson', i) as string;
if (additionalFieldsJson !== '') { if (additionalFieldsJson !== '') {
if (validateJSON(additionalFieldsJson) !== undefined) { if (validateJSON(additionalFieldsJson) !== undefined) {
Object.assign(body, JSON.parse(additionalFieldsJson)); Object.assign(body, JSON.parse(additionalFieldsJson));
} else { } else {
throw new Error('Additional fields must be a valid JSON'); throw new Error('Additional fields must be a valid JSON');
} }
} }
} else { } else {
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const subscriptions = this.getNodeParameter('subscriptions', i) as IDataObject;
body.subscriptions = JSON.stringify(subscriptions.properties);
if (additionalFields.inviteOnly) { if (additionalFields.inviteOnly) {
body.invite_only = additionalFields.inviteOnly as boolean; body.invite_only = additionalFields.inviteOnly as boolean;
} }
if (additionalFields.principals) { if (additionalFields.principals) {
const principals : string[] = []; const principals: string[] = [];
//@ts-ignore //@ts-ignore
additionalFields.principals.properties.map((principal : IPrincipal) => { additionalFields.principals.properties.map((principal: IPrincipal) => {
principals.push(principal.email); principals.push(principal.email);
}); });
body.principals = JSON.stringify(principals); body.principals = JSON.stringify(principals);
@ -368,7 +368,7 @@ export class Zulip implements INodeType {
} }
if (resource === 'user') { if (resource === 'user') {
const body : IUser = {}; const body: IUser = {};
if (operation === 'get') { if (operation === 'get') {
const userId = this.getNodeParameter('userId', i) as string; const userId = this.getNodeParameter('userId', i) as string;
@ -396,6 +396,7 @@ export class Zulip implements INodeType {
} }
responseData = await zulipApiRequest.call(this, 'GET', `/users`, body); responseData = await zulipApiRequest.call(this, 'GET', `/users`, body);
responseData = responseData.members;
} }
if (operation === 'create') { if (operation === 'create') {
@ -434,19 +435,10 @@ export class Zulip implements INodeType {
responseData = await zulipApiRequest.call(this, 'DELETE', `/users/${userId}`, body); responseData = await zulipApiRequest.call(this, 'DELETE', `/users/${userId}`, body);
} }
} }
// Specific checks because API returns multiple objects within 1 object with each key name
if (responseData.members) {
returnData.push.apply(returnData, responseData.members as IDataObject[]);
}
else if (responseData.streams) { if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData.streams as IDataObject[]); returnData.push.apply(returnData, responseData as IDataObject[]);
} } else {
else if (responseData.subscriptions) {
returnData.push.apply(returnData, responseData.subscriptions as IDataObject[]);
}
else {
returnData.push(responseData as IDataObject); returnData.push(responseData as IDataObject);
} }
} }