From ff7f0a5de5370be475c718985d6da1c1b9c9e24a Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 15 Mar 2020 15:51:49 +0100 Subject: [PATCH] :zap: Fix some issues with Slack-Node --- packages/cli/BREAKING-CHANGES.md | 28 +++++++ packages/cli/src/Server.ts | 2 +- packages/core/src/NodeExecuteFunctions.ts | 18 ++--- .../credentials/SlackOAuth2Api.credentials.ts | 2 +- .../nodes/Slack/ChannelDescription.ts | 12 ++- .../nodes/Slack/GenericFunctions.ts | 4 +- .../nodes/Slack/MessageDescription.ts | 23 ++++-- packages/nodes-base/nodes/Slack/Slack.node.ts | 79 ++++++++++++------- .../nodes-base/nodes/Slack/StarDescription.ts | 12 ++- 9 files changed, 126 insertions(+), 54 deletions(-) diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index 3effaa08ee..33e5118859 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -2,6 +2,34 @@ This list shows all the versions which include breaking changes and how to upgrade +## ??? + +### What changed? + +To make it easier to use the data which the Slack-Node outputs we no longer return the whole +object the Slack-API returns if the only other property is `"ok": true`. In this case it returns +now directly the data under "channel". + +### When is action necessary? + +When you currently use the Slack-Node with Operations Channel -> Create and you use +any of the data the node outputs. + +### How to upgrade: + +All values that get referenced which were before under the property "channel" are now on the main level. +This means that these expressions have to get adjusted. + +Meaning if the expression used before was: +``` +{{ $node["Slack"].data["channel"]["id"] }} +``` +it has to get changed to: +``` +{{ $node["Slack"].data["id"] }} +``` + + ## 0.37.0 ### What changed? diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 91c318086f..9f4e9e77bc 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -203,7 +203,7 @@ class App { }); } - jwt.verify(token, getKey, {}, (err: Error) => { + jwt.verify(token, getKey, {}, (err: Error, decoded: object) => { if (err) return ResponseHelper.jwtAuthAuthorizationError(res, "Invalid token"); next(); diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index f73eb2f068..80d5d0d135 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -115,7 +115,7 @@ export async function prepareBinaryData(binaryData: Buffer, filePath?: string, m * @param {IWorkflowExecuteAdditionalData} additionalData * @returns */ -export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, tokenType?: string | undefined, property?: string) { +export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, node: INode, additionalData: IWorkflowExecuteAdditionalData, tokenType?: string, property?: string) { const credentials = this.getCredentials(credentialsType) as ICredentialDataDecryptedObject; if (credentials === undefined) { @@ -134,7 +134,7 @@ export function requestOAuth(this: IAllExecuteFunctions, credentialsType: string const oauthTokenData = credentials.oauthTokenData as clientOAuth2.Data; - const token = oAuthClient.createToken(get(oauthTokenData, property as string) || oauthTokenData.accessToken, oauthTokenData.refresToken, tokenType || oauthTokenData.tokenType, oauthTokenData); + const token = oAuthClient.createToken(get(oauthTokenData, property as string) || oauthTokenData.accessToken, oauthTokenData.refreshToken, tokenType || oauthTokenData.tokenType, oauthTokenData); // Signs the request by adding authorization headers or query parameters depending // on the token-type used. const newRequestOptions = token.sign(requestOptions as clientOAuth2.RequestObject); @@ -412,7 +412,7 @@ export function getExecutePollFunctions(workflow: Workflow, node: INode, additio helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise { // tslint:disable-line:no-any + requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); }, returnJsonArray, @@ -466,7 +466,7 @@ export function getExecuteTriggerFunctions(workflow: Workflow, node: INode, addi helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise { // tslint:disable-line:no-any + requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); }, returnJsonArray, @@ -547,7 +547,7 @@ export function getExecuteFunctions(workflow: Workflow, runExecutionData: IRunEx helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise { // tslint:disable-line:no-any + requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); }, returnJsonArray, @@ -629,7 +629,7 @@ export function getExecuteSingleFunctions(workflow: Workflow, runExecutionData: helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined, property: string = ''): Promise { // tslint:disable-line:no-any + requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); }, }, @@ -679,7 +679,7 @@ export function getLoadOptionsFunctions(workflow: Workflow, node: INode, additio }, helpers: { request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise { // tslint:disable-line:no-any + requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); }, }, @@ -737,7 +737,7 @@ export function getExecuteHookFunctions(workflow: Workflow, node: INode, additio }, helpers: { request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise { // tslint:disable-line:no-any + requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); }, }, @@ -822,7 +822,7 @@ export function getExecuteWebhookFunctions(workflow: Workflow, node: INode, addi helpers: { prepareBinaryData, request: requestPromise, - requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType: string | undefined = undefined, property: string = ''): Promise { // tslint:disable-line:no-any + requestOAuth(this: IAllExecuteFunctions, credentialsType: string, requestOptions: OptionsWithUri | requestPromise.RequestPromiseOptions, tokenType?: string, property?: string): Promise { // tslint:disable-line:no-any return requestOAuth.call(this, credentialsType, requestOptions, node, additionalData, tokenType, property); }, returnJsonArray, diff --git a/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts b/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts index 6c5284ee44..b56699fe68 100644 --- a/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/SlackOAuth2Api.credentials.ts @@ -12,7 +12,7 @@ const userScopes = [ 'files:write', 'stars:read', 'stars:write', -] +]; export class SlackOAuth2Api implements ICredentialType { diff --git a/packages/nodes-base/nodes/Slack/ChannelDescription.ts b/packages/nodes-base/nodes/Slack/ChannelDescription.ts index cda5824105..8e279bbeab 100644 --- a/packages/nodes-base/nodes/Slack/ChannelDescription.ts +++ b/packages/nodes-base/nodes/Slack/ChannelDescription.ts @@ -234,7 +234,10 @@ export const channelFields = [ { displayName: 'User ID', name: 'userId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, default: '', displayOptions: { show: { @@ -321,9 +324,12 @@ export const channelFields = [ description: 'The name of the channel to create.', }, { - displayName: 'User ID', + displayName: 'User', name: 'userId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, displayOptions: { show: { operation: [ diff --git a/packages/nodes-base/nodes/Slack/GenericFunctions.ts b/packages/nodes-base/nodes/Slack/GenericFunctions.ts index b73de1cac5..ff4769cb8e 100644 --- a/packages/nodes-base/nodes/Slack/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Slack/GenericFunctions.ts @@ -60,7 +60,7 @@ export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFu } } -export async function salckApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any +export async function slackApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string ,method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise { // tslint:disable-line:no-any const returnData: IDataObject[] = []; let responseData; query.page = 1; @@ -73,7 +73,7 @@ export async function salckApiRequestAllItems(this: IExecuteFunctions | ILoadOpt } while ( (responseData.response_metadata !== undefined && responseData.response_metadata.mext_cursor !== undefined && - responseData.response_metadata.next_cursor !== "" && + responseData.response_metadata.next_cursor !== '' && responseData.response_metadata.next_cursor !== null) || (responseData.paging !== undefined && responseData.paging.pages !== undefined && diff --git a/packages/nodes-base/nodes/Slack/MessageDescription.ts b/packages/nodes-base/nodes/Slack/MessageDescription.ts index 8c582503e5..8b077e6bab 100644 --- a/packages/nodes-base/nodes/Slack/MessageDescription.ts +++ b/packages/nodes-base/nodes/Slack/MessageDescription.ts @@ -43,7 +43,7 @@ export const messageFields = [ displayOptions: { show: { operation: [ - 'post' + 'post', ], resource: [ 'message', @@ -64,7 +64,7 @@ export const messageFields = [ displayOptions: { show: { operation: [ - 'post' + 'post', ], resource: [ 'message', @@ -80,6 +80,9 @@ export const messageFields = [ default: false, displayOptions: { show: { + authentication: [ + 'accessToken', + ], operation: [ 'post' ], @@ -98,10 +101,10 @@ export const messageFields = [ displayOptions: { show: { as_user: [ - false + false, ], operation: [ - 'post' + 'post', ], resource: [ 'message', @@ -121,7 +124,7 @@ export const messageFields = [ displayOptions: { show: { operation: [ - 'post' + 'post', ], resource: [ 'message', @@ -411,9 +414,12 @@ export const messageFields = [ /* message:update */ /* ----------------------------------------------------------------------- */ { - displayName: 'Channel ID', + displayName: 'Channel', name: 'channelId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + }, required: true, default: '', displayOptions: { @@ -471,6 +477,9 @@ export const messageFields = [ default: false, displayOptions: { show: { + authentication: [ + 'accessToken', + ], operation: [ 'update' ], diff --git a/packages/nodes-base/nodes/Slack/Slack.node.ts b/packages/nodes-base/nodes/Slack/Slack.node.ts index 1afeb4ebd0..2b2061d4e3 100644 --- a/packages/nodes-base/nodes/Slack/Slack.node.ts +++ b/packages/nodes-base/nodes/Slack/Slack.node.ts @@ -11,24 +11,24 @@ import { INodePropertyOptions, } from 'n8n-workflow'; import { - channelOperations, channelFields, + channelOperations, } from './ChannelDescription'; import { - messageOperations, messageFields, + messageOperations, } from './MessageDescription'; import { - starOperations, starFields, + starOperations, } from './StarDescription'; import { - fileOperations, fileFields, + fileOperations, } from './FileDescription'; import { slackApiRequest, - salckApiRequestAllItems, + slackApiRequestAllItems, } from './GenericFunctions'; import { IAttachment, @@ -133,7 +133,7 @@ export class Slack implements INodeType { // select them easily async getUsers(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - const users = await salckApiRequestAllItems.call(this, 'members', 'GET', '/users.list'); + const users = await slackApiRequestAllItems.call(this, 'members', 'GET', '/users.list'); for (const user of users) { const userName = user.name; const userId = user.id; @@ -142,13 +142,20 @@ export class Slack implements INodeType { value: userId, }); } + + returnData.sort((a, b) => { + if (a.name < b.name) { return -1; } + if (a.name > b.name) { return 1; } + return 0; + }); + return returnData; }, // Get all the users to display them to user so that he can // select them easily async getChannels(this: ILoadOptionsFunctions): Promise { const returnData: INodePropertyOptions[] = []; - const channels = await salckApiRequestAllItems.call(this, 'channels', 'GET', '/conversations.list'); + const channels = await slackApiRequestAllItems.call(this, 'channels', 'GET', '/conversations.list'); for (const channel of channels) { const channelName = channel.name; const channelId = channel.id; @@ -157,6 +164,13 @@ export class Slack implements INodeType { value: channelId, }); } + + returnData.sort((a, b) => { + if (a.name < b.name) { return -1; } + if (a.name > b.name) { return 1; } + return 0; + }); + return returnData; }, } @@ -168,10 +182,12 @@ export class Slack implements INodeType { const length = items.length as unknown as number; let qs: IDataObject; let responseData; + const authentication = this.getNodeParameter('authentication', 0) as string; + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + for (let i = 0; i < length; i++) { qs = {}; - const resource = this.getNodeParameter('resource', 0) as string; - const operation = this.getNodeParameter('operation', 0) as string; if (resource === 'channel') { //https://api.slack.com/methods/conversations.archive if (operation === 'archive') { @@ -203,6 +219,7 @@ export class Slack implements INodeType { body.user_ids = (additionalFields.users as string[]).join(','); } responseData = await slackApiRequest.call(this, 'POST', '/conversations.create', body, qs); + responseData = responseData.channel; } //https://api.slack.com/methods/conversations.kick if (operation === 'kick') { @@ -241,7 +258,7 @@ export class Slack implements INodeType { qs.exclude_archived = filters.excludeArchived as boolean; } if (returnAll === true) { - responseData = await salckApiRequestAllItems.call(this, 'channels', 'GET', '/conversations.list', {}, qs); + responseData = await slackApiRequestAllItems.call(this, 'channels', 'GET', '/conversations.list', {}, qs); } else { qs.limit = this.getNodeParameter('limit', i) as number; responseData = await slackApiRequest.call(this, 'GET', '/conversations.list', {}, qs); @@ -264,7 +281,7 @@ export class Slack implements INodeType { qs.oldest = filters.oldest as string; } if (returnAll === true) { - responseData = await salckApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.history', {}, qs); + responseData = await slackApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.history', {}, qs); } else { qs.limit = this.getNodeParameter('limit', i) as number; responseData = await slackApiRequest.call(this, 'GET', '/conversations.history', {}, qs); @@ -335,7 +352,7 @@ export class Slack implements INodeType { qs.oldest = filters.oldest as string; } if (returnAll === true) { - responseData = await salckApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.replies', {}, qs); + responseData = await slackApiRequestAllItems.call(this, 'messages', 'GET', '/conversations.replies', {}, qs); } else { qs.limit = this.getNodeParameter('limit', i) as number; responseData = await slackApiRequest.call(this, 'GET', '/conversations.replies', {}, qs); @@ -379,15 +396,18 @@ export class Slack implements INodeType { const channel = this.getNodeParameter('channel', i) as string; const text = this.getNodeParameter('text', i) as string; const attachments = this.getNodeParameter('attachments', i, []) as unknown as IAttachment[]; - const as_user = this.getNodeParameter('as_user', i) as boolean; const body: IDataObject = { - channel: channel, + channel, text, - as_user, }; - if (as_user === false) { + + if (authentication === 'accessToken') { + body.as_user = this.getNodeParameter('as_user', i) as boolean; + } + if (body.as_user === false) { body.username = this.getNodeParameter('username', i) as string; } + // The node does save the fields data differently than the API // expects so fix the data befre we send the request for (const attachment of attachments) { @@ -411,17 +431,20 @@ export class Slack implements INodeType { } //https://api.slack.com/methods/chat.update if (operation === 'update') { - const channel = this.getNodeParameter('channel', i) as string; + const channel = this.getNodeParameter('channelId', i) as string; const text = this.getNodeParameter('text', i) as string; const ts = this.getNodeParameter('ts', i) as string; - const as_user = this.getNodeParameter('as_user', i) as boolean; const attachments = this.getNodeParameter('attachments', i, []) as unknown as IAttachment[]; const body: IDataObject = { channel, text, ts, - as_user, }; + + if (authentication === 'accessToken') { + body.as_user = this.getNodeParameter('as_user', i) as boolean; + } + // The node does save the fields data differently than the API // expects so fix the data befre we send the request for (const attachment of attachments) { @@ -485,7 +508,7 @@ export class Slack implements INodeType { if (operation === 'getAll') { const returnAll = this.getNodeParameter('returnAll', i) as boolean; if (returnAll === true) { - responseData = await salckApiRequestAllItems.call(this, 'items', 'GET', '/stars.list', {}, qs); + responseData = await slackApiRequestAllItems.call(this, 'items', 'GET', '/stars.list', {}, qs); } else { qs.limit = this.getNodeParameter('limit', i) as number; responseData = await slackApiRequest.call(this, 'GET', '/stars.list', {}, qs); @@ -522,15 +545,15 @@ export class Slack implements INodeType { throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`); } body.file = { + //@ts-ignore + value: Buffer.from(items[i].binary[binaryPropertyName].data, BINARY_ENCODING), + options: { //@ts-ignore - value: Buffer.from(items[i].binary[binaryPropertyName].data, BINARY_ENCODING), - options: { - //@ts-ignore - filename: items[i].binary[binaryPropertyName].fileName, - //@ts-ignore - contentType: items[i].binary[binaryPropertyName].mimeType, - } + filename: items[i].binary[binaryPropertyName].fileName, + //@ts-ignore + contentType: items[i].binary[binaryPropertyName].mimeType, } + }; responseData = await slackApiRequest.call(this, 'POST', '/files.upload', {}, qs, { 'Content-Type': 'multipart/form-data' }, { formData: body }); responseData = responseData.file; } else { @@ -563,7 +586,7 @@ export class Slack implements INodeType { qs.user = filters.userId as string; } if (returnAll === true) { - responseData = await salckApiRequestAllItems.call(this, 'files', 'GET', '/files.list', {}, qs); + responseData = await slackApiRequestAllItems.call(this, 'files', 'GET', '/files.list', {}, qs); } else { qs.count = this.getNodeParameter('limit', i) as number; responseData = await slackApiRequest.call(this, 'GET', '/files.list', {}, qs); diff --git a/packages/nodes-base/nodes/Slack/StarDescription.ts b/packages/nodes-base/nodes/Slack/StarDescription.ts index 39174b6a87..282025b1ea 100644 --- a/packages/nodes-base/nodes/Slack/StarDescription.ts +++ b/packages/nodes-base/nodes/Slack/StarDescription.ts @@ -26,7 +26,7 @@ export const starOperations = [ { name: 'Get All', value: 'getAll', - description: 'Get all stars for a user.', + description: 'Get all stars of autenticated user.', }, ], default: 'add', @@ -60,7 +60,10 @@ export const starFields = [ { displayName: 'Channel ID', name: 'channelId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + }, default: '', description: 'Channel to add star to, or channel where the message to add star to was posted (used with timestamp).', }, @@ -111,7 +114,10 @@ export const starFields = [ { displayName: 'Channel ID', name: 'channelId', - type: 'string', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getChannels', + }, default: '', description: 'Channel to add star to, or channel where the message to add star to was posted (used with timestamp).', },