🔀 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
# Got started with arguments
COMMAND=$1;
if [[ "$COMMAND" == "n8n" ]]; then
shift
exec su-exec node ./packages/cli/bin/n8n "$@"
else
exec su-exec node "$@"
fi
else
# Got started without arguments
exec su-exec node ./packages/cli/bin/n8n
# Got started without arguments
exec su-exec node ./packages/cli/bin/n8n
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)
- [Persist data](#persist-data)
- [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)
- [What does n8n mean and how do you pronounce it](#what-does-n8n-mean-and-how-do-you-pronounce-it)
- [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
[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

View file

@ -192,7 +192,7 @@ class LoadNodesAndCredentialsClass {
* @memberof N8nPackagesInformationClass
*/
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 type: string;

View file

@ -19,7 +19,16 @@
<div class="header">
<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
v-if="runMetadata"
placement="right"
@ -184,6 +193,11 @@ import {
ITableData,
} from '@/Interface';
import {
MAX_DISPLAY_DATA_SIZE,
MAX_DISPLAY_ITEMS_AUTO_ALL,
} from '@/constants';
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
import { genericHelpers } from '@/components/mixins/genericHelpers';
@ -211,8 +225,12 @@ export default mixins(
runIndex: 0,
showData: false,
outputIndex: 0,
maxDisplayItems: 25 as number | null,
binaryDataDisplayVisible: false,
binaryDataDisplayData: null as IBinaryDisplayData | null,
MAX_DISPLAY_DATA_SIZE,
MAX_DISPLAY_ITEMS_AUTO_ALL,
};
},
computed: {
@ -229,6 +247,9 @@ export default mixins(
const executionData: IRunExecutionData = this.workflowExecution.data;
return executionData.resultData.runData;
},
maxDisplayItemsOptions (): number[] {
return [25, 50, 100, 250, 500, 1000, this.dataCount].filter(option => option <= this.dataCount);
},
node (): INodeUi | null {
return this.$store.getters.activeNode;
},
@ -323,19 +344,27 @@ export default mixins(
return 0;
},
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)) {
return [];
}
if (this.maxDisplayItems !== null) {
inputData = inputData.slice(0, this.maxDisplayItems);
}
return this.convertToJson(inputData);
},
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) {
return undefined;
}
if (this.maxDisplayItems !== null) {
inputData = inputData.slice(0,this.maxDisplayItems);
}
return this.convertToTable(inputData);
},
binaryData (): IBinaryKeyData[] {
@ -450,9 +479,12 @@ export default mixins(
// Check how much data there is to display
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
this.showData = true;
}
@ -466,6 +498,7 @@ export default mixins(
node (newNode, oldNode) {
// Reset the selected output index every time another node gets selected
this.outputIndex = 0;
this.maxDisplayItems = 25;
this.refreshDataSize();
},
jsonData () {

View file

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

View file

@ -20,6 +20,7 @@
:id="'node-' + getNodeIndex(nodeData.name)"
:key="getNodeIndex(nodeData.name)"
:name="nodeData.name"
:isReadOnly="isReadOnly"
:instance="instance"
></node>
</div>
@ -102,6 +103,9 @@
<script lang="ts">
import Vue from 'vue';
import {
OverlaySpec,
} from 'jsplumb';
import { MessageBoxInputData } from 'element-ui/types/message-box';
import { jsPlumb, Endpoint, OnConnectionBindInfo } from 'jsplumb';
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID } from '@/constants';
@ -1013,21 +1017,9 @@ export default mixins(
}
},
initNodeView () {
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: [
const connectionOverlays: OverlaySpec[] = [];
if (this.isReadOnly === false) {
connectionOverlays.push.apply(connectionOverlays, [
[
'Arrow',
{
@ -1045,7 +1037,24 @@ export default mixins(
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',
});
@ -1100,6 +1109,10 @@ export default mixins(
info.connection.setConnector(['Straight']);
}
// @ts-ignore
info.connection.removeOverlay('drop-add-node');
if (this.isReadOnly === false) {
// Display the connection-delete button only on hover
let timer: NodeJS.Timeout | undefined;
info.connection.bind('mouseover', (connection: IConnection) => {
@ -1117,9 +1130,6 @@ export default mixins(
}, 500);
});
// @ts-ignore
info.connection.removeOverlay('drop-add-node');
// @ts-ignore
info.connection.addOverlay([
'Label',
@ -1135,6 +1145,7 @@ export default mixins(
},
},
]);
}
// Display input names if they exist on connection
const targetNodeTypeData: INodeTypeDescription = this.$store.getters.nodeType(targetNode.type);
@ -1329,6 +1340,7 @@ export default mixins(
// @ts-ignore
this.instance.connect({
uuids: uuid,
detachable: !this.isReadOnly,
});
} else {
// 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,
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,
password: credentials!.apiKey as string
},
uri: uri || `https://n8nio.agilecrm.com/dev/${endpoint}`,
uri: uri || `https://${credentials!.subdomain}.agilecrm.com/dev/${endpoint}`,
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
const baseUri = 'https://n8nio.agilecrm.com/dev/';
const credentials = this.getCredentials('agileCrmApi');
const baseUri = `https://${credentials!.subdomain}.agilecrm.com/dev/`;
const options: OptionsWithUri = {
method,
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..',
},
{
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 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
if (item.binary === undefined) {
return item;
@ -550,6 +626,24 @@ export class EditImage implements INodeType {
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) => {
gmInstance
.toBuffer((error: Error | null, buffer: Buffer) => {

View file

@ -1,13 +1,19 @@
import { OptionsWithUri } from 'request';
import {
OptionsWithUri,
} from 'request';
import {
IExecuteFunctions,
IExecuteSingleFunctions,
IHookFunctions,
ILoadOptionsFunctions,
IExecuteSingleFunctions
} 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');
if (credentials === undefined) {
@ -27,6 +33,7 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio
const options: OptionsWithUri = {
headers: headerWithAuthentication,
method,
qs,
uri: `https://${datacenter}.${host}${endpoint}`,
json: true,
};
@ -34,17 +41,34 @@ export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctio
if (Object.keys(body).length !== 0) {
options.body = body;
}
try {
return await this.helpers.request!(options);
} 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) {
throw errorMessage;
}
throw error.response.body;
}
export async function mailchimpApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, endpoint: string, method: string, propertyName: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];
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

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',
},
{
name: 'Members',
name: 'Member',
value: 'members',
description: 'Returns the members of a channel.',
description: 'Get a page of members for a channel.',
},
{
name: 'Restore',
@ -317,6 +317,23 @@ export class Mattermost implements INodeType {
},
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',
name: 'returnAll',
@ -950,6 +967,11 @@ export class Mattermost implements INodeType {
value: 'getByEmail',
description: 'Get a user by email',
},
{
name: 'Get By ID',
value: 'getById',
description: 'Get a user by id',
},
],
default: '',
description: 'The operation to perform.',
@ -1113,6 +1135,54 @@ export class Mattermost implements INodeType {
default: '',
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 requestMethod = 'POST';
let returnAll = false;
let userIds: string[] = [];
resource = this.getNodeParameter('resource', 0) as string;
operation = this.getNodeParameter('operation', 0) as string;
// For Post
let body: IDataObject;
@ -1256,9 +1330,6 @@ export class Mattermost implements INodeType {
body = {};
qs = {};
resource = this.getNodeParameter('resource', i) as string;
operation = this.getNodeParameter('operation', i) as string;
if (resource === 'channel') {
if (operation === 'create') {
// ----------------------------------
@ -1509,6 +1580,25 @@ export class Mattermost implements INodeType {
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 {
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);
} else {
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)) {
returnData.push.apply(returnData, responseData);

View file

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

View file

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

View file

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

View file

@ -33,11 +33,11 @@ export const streamOperations = [
value: 'getSubscribed',
description: 'Get subscribed streams.',
},
{
name: 'Update',
value: 'update',
description: 'Update a stream.',
},
// {
// name: 'Update',
// value: 'update',
// description: 'Update a stream.',
// },
],
default: 'create',
description: 'The operation to perform.',
@ -45,9 +45,9 @@ export const streamOperations = [
] as INodeProperties[];
export const streamFields = [
/* -------------------------------------------------------------------------- */
/* stream:create */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* stream:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Subscriptions',
name: 'subscriptions',
@ -131,9 +131,56 @@ export const streamFields = [
],
},
},
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',
name: 'additionalFields',
@ -186,7 +233,7 @@ export const streamFields = [
displayName: 'Principals',
name: 'principals',
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.',
typeOptions: {
multipleValues: true,
@ -234,9 +281,10 @@ export const streamFields = [
}
]
},
/* -------------------------------------------------------------------------- */
/* stream:get all */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* stream:get all */
/* -------------------------------------------------------------------------- */
{
displayName: 'Additional Fields',
name: 'additionalFields',
@ -291,9 +339,10 @@ export const streamFields = [
},
]
},
/* -------------------------------------------------------------------------- */
/* stream:get subscribed */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* stream:get subscribed */
/* -------------------------------------------------------------------------- */
{
displayName: 'Additional Fields',
name: 'additionalFields',
@ -320,9 +369,10 @@ export const streamFields = [
}
]
},
/* -------------------------------------------------------------------------- */
/* stream:update */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* stream:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'Stream ID',
name: 'streamId',
@ -466,9 +516,9 @@ export const streamFields = [
},
]
},
/* -------------------------------------------------------------------------- */
/* stream:delete */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* stream:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Stream ID',
name: 'streamId',

View file

@ -33,11 +33,11 @@ export const userOperations = [
value: 'getAll',
description: 'Get all users.',
},
{
name: 'Update',
value: 'update',
description: 'Update a user.',
},
// {
// name: 'Update',
// value: 'update',
// description: 'Update a user.',
// },
],
default: 'create',
description: 'The operation to perform.',
@ -45,9 +45,9 @@ export const userOperations = [
] as INodeProperties[];
export const userFields = [
/* -------------------------------------------------------------------------- */
/* user:create */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* user:create */
/* -------------------------------------------------------------------------- */
{
displayName: 'Email',
name: 'email',
@ -120,9 +120,10 @@ export const userFields = [
default: '',
description: 'The short name of the new user. Not user-visible.',
},
/* -------------------------------------------------------------------------- */
/* user:get / getAll */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* user:get / getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'User ID',
name: 'userId',
@ -174,9 +175,10 @@ export const userFields = [
},
]
},
/* -------------------------------------------------------------------------- */
/* user:update */
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* user:update */
/* -------------------------------------------------------------------------- */
{
displayName: 'User ID',
name: 'userId',
@ -237,7 +239,7 @@ export const userFields = [
displayName: 'Profile Data',
name: 'profileData',
type: 'fixedCollection',
default: '',
default: {},
description: 'A dictionary containing the to be updated custom profile field data for the user.',
typeOptions: {
multipleValues: true,
@ -268,10 +270,11 @@ export const userFields = [
},
]
},
/* -------------------------------------------------------------------------- */
/* user:deactivate */
/* -------------------------------------------------------------------------- */
{
/* -------------------------------------------------------------------------- */
/* user:deactivate */
/* -------------------------------------------------------------------------- */
{
displayName: 'User ID',
name: 'userId',
type: 'string',
@ -288,5 +291,5 @@ export const userFields = [
},
default: '',
description: 'The ID of user to deactivate.',
},
},
] 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}`;
}
}
@ -247,6 +247,7 @@ export class Zulip implements INodeType {
}
responseData = await zulipApiRequest.call(this, 'GET', `/streams`, body);
responseData = responseData.streams;
}
if (operation === 'getSubscribed') {
@ -257,10 +258,10 @@ export class Zulip implements INodeType {
}
responseData = await zulipApiRequest.call(this, 'GET', `/users/me/subscriptions`, body);
responseData = responseData.subscriptions;
}
if (operation === 'create') {
const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
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;
if (additionalFieldsJson !== '') {
if (validateJSON(additionalFieldsJson) !== undefined) {
Object.assign(body, JSON.parse(additionalFieldsJson));
} else {
throw new Error('Additional fields must be a valid JSON');
}
}
} else {
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const subscriptions = this.getNodeParameter('subscriptions', i) as IDataObject;
body.subscriptions = JSON.stringify(subscriptions.properties);
if (additionalFields.inviteOnly) {
body.invite_only = additionalFields.inviteOnly as boolean;
}
if (additionalFields.principals) {
const principals : string[] = [];
const principals: string[] = [];
//@ts-ignore
additionalFields.principals.properties.map((principal : IPrincipal) => {
additionalFields.principals.properties.map((principal: IPrincipal) => {
principals.push(principal.email);
});
body.principals = JSON.stringify(principals);
@ -368,7 +368,7 @@ export class Zulip implements INodeType {
}
if (resource === 'user') {
const body : IUser = {};
const body: IUser = {};
if (operation === 'get') {
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 = responseData.members;
}
if (operation === 'create') {
@ -434,19 +435,10 @@ export class Zulip implements INodeType {
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) {
returnData.push.apply(returnData, responseData.streams as IDataObject[]);
}
else if (responseData.subscriptions) {
returnData.push.apply(returnData, responseData.subscriptions as IDataObject[]);
}
else {
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
}