1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-10-08 02:28:57 +03:00

feat(Slack Node): Add option to include link to workflow in Slack node (#6611)

* feat(Slack Node): Add “automated by” message to Slack node’s post message

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* Pass instanceBaseUrl to node context

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* Move `includeLinkToWorkflow` to options

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

* keep "includeLinkToWorkflow" hidden

* Only append the message for version 2.1 and up

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>

---------

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
This commit is contained in:
OlegIvaniv 2023-07-10 15:03:21 +02:00 committed by GitHub
parent d617f63ae9
commit aa53c46367
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 27 deletions

View File

@ -1179,6 +1179,7 @@ export async function getBase(
executeWorkflow,
restApiUrl: urlBaseWebhook + config.getEnv('endpoints.rest'),
timezone,
instanceBaseUrl: urlBaseWebhook,
webhookBaseUrl,
webhookWaitingBaseUrl,
webhookTestBaseUrl,

View File

@ -2161,6 +2161,7 @@ const getCommonWorkflowFunctions = (
getWorkflowStaticData: (type) => workflow.getStaticData(type, node),
getRestApiUrl: () => additionalData.restApiUrl,
getInstanceBaseUrl: () => additionalData.instanceBaseUrl,
getTimezone: () => getTimezone(workflow, additionalData),
});

View File

@ -800,6 +800,7 @@ export default defineComponent({
this.updateNodeParameterIssues(node, nodeType);
this.updateNodeCredentialIssues(node);
this.$telemetry.trackNodeParametersValuesChange(nodeType.name, parameterData);
} else {
// A property on the node itself changed

View File

@ -2,11 +2,12 @@ import type _Vue from 'vue';
import type { ITelemetrySettings, ITelemetryTrackProperties, IDataObject } from 'n8n-workflow';
import type { Route } from 'vue-router';
import type { INodeCreateElement } from '@/Interface';
import type { INodeCreateElement, IUpdateInformation } from '@/Interface';
import type { IUserNodesPanelSession } from './telemetry.types';
import { useSettingsStore } from '@/stores/settings.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useTelemetryStore } from '@/stores/telemetry.store';
import { SLACK_NODE_TYPE } from '@/constants';
export class Telemetry {
private pageEventQueue: Array<{ route: Route }>;
@ -197,6 +198,23 @@ export class Telemetry {
}
}
// We currently do not support tracking directly from within node implementation
// so we are using this method as centralized way to track node parameters changes
trackNodeParametersValuesChange(nodeType: string, change: IUpdateInformation) {
if (this.rudderStack) {
switch (nodeType) {
case SLACK_NODE_TYPE:
if (change.name === 'parameters.includeLinkToWorkflow') {
this.track('User toggled n8n reference option');
}
break;
default:
break;
}
}
}
private resetNodesPanelSession() {
this.userNodesPanelSession.sessionId = `nodes_panel_session_${new Date().valueOf()}`;
this.userNodesPanelSession.data = {

View File

@ -14,12 +14,13 @@ export class Slack extends VersionedNodeType {
group: ['output'],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Slack API',
defaultVersion: 2,
defaultVersion: 2.1,
};
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
1: new SlackV1(baseDescription),
2: new SlackV2(baseDescription),
2.1: new SlackV2(baseDescription),
};
super(nodeVersions, baseDescription);

View File

@ -7,7 +7,7 @@ import type {
IOAuth2Options,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { NodeOperationError, jsonParse } from 'n8n-workflow';
import get from 'lodash/get';
@ -130,6 +130,64 @@ export async function slackApiRequestAllItems(
return returnData;
}
export function getMessageContent(
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
i: number,
) {
const nodeVersion = this.getNode().typeVersion;
const includeLinkToWorkflow = this.getNodeParameter(
'otherOptions.includeLinkToWorkflow',
i,
nodeVersion >= 2.1 ? true : false,
) as IDataObject;
const { id } = this.getWorkflow();
const automatedMessage = `_Automated with this <${this.getInstanceBaseUrl()}workflow/${id}|n8n workflow>_`;
const messageType = this.getNodeParameter('messageType', i) as string;
let content: IDataObject = {};
const text = this.getNodeParameter('text', i, '') as string;
switch (messageType) {
case 'text':
content = {
text: includeLinkToWorkflow ? `${text}\n${automatedMessage}` : text,
};
break;
case 'block':
content = jsonParse(this.getNodeParameter('blocksUi', i) as string);
if (includeLinkToWorkflow && Array.isArray(content.blocks)) {
content.blocks.push({
type: 'section',
text: {
type: 'mrkdwn',
text: automatedMessage,
},
});
}
if (text) {
content.text = text;
}
break;
case 'attachment':
content = { attachments: this.getNodeParameter('attachments', i) } as IDataObject;
if (includeLinkToWorkflow && Array.isArray(content.attachments)) {
content.attachments.push({
text: automatedMessage,
});
}
break;
default:
throw new NodeOperationError(
this.getNode(),
`The message type "${messageType}" is not known!`,
);
}
return content;
}
// tslint:disable-next-line:no-any
export function validateJSON(json: string | undefined): any {
let result;

View File

@ -557,6 +557,14 @@ export const messageFields: INodeProperties[] = [
description: 'Other options to set',
placeholder: 'Add options',
options: [
{
displayName: 'Include Link To Workflow',
name: 'includeLinkToWorkflow',
type: 'boolean',
default: true,
description:
'Whether to append a link to this workflow at the end of the message. This is helpful if you have many workflows sending Slack messages.',
},
{
displayName: 'Custom Bot Profile Photo',
name: 'botProfile',

View File

@ -24,7 +24,12 @@ import { fileFields, fileOperations } from './FileDescription';
import { reactionFields, reactionOperations } from './ReactionDescription';
import { userGroupFields, userGroupOperations } from './UserGroupDescription';
import { userFields, userOperations } from './UserDescription';
import { slackApiRequest, slackApiRequestAllItems, validateJSON } from './GenericFunctions';
import {
slackApiRequest,
slackApiRequestAllItems,
validateJSON,
getMessageContent,
} from './GenericFunctions';
import moment from 'moment';
@ -34,7 +39,7 @@ export class SlackV2 implements INodeType {
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
version: 2,
version: [2, 2.1],
defaults: {
name: 'Slack',
},
@ -747,7 +752,6 @@ export class SlackV2 implements INodeType {
//https://api.slack.com/methods/chat.postMessage
if (operation === 'post') {
const select = this.getNodeParameter('select', i) as string;
const messageType = this.getNodeParameter('messageType', i) as string;
let target =
select === 'channel'
? (this.getNodeParameter('channelId', i, undefined, {
@ -764,27 +768,8 @@ export class SlackV2 implements INodeType {
target = target.slice(0, 1) === '@' ? target : `@${target}`;
}
const { sendAsUser } = this.getNodeParameter('otherOptions', i) as IDataObject;
let content: IDataObject = {};
const text = this.getNodeParameter('text', i, '') as string;
switch (messageType) {
case 'text':
content = { text };
break;
case 'block':
content = JSON.parse(this.getNodeParameter('blocksUi', i) as string);
if (text) {
content.text = text;
}
break;
case 'attachment':
content = { attachments: this.getNodeParameter('attachments', i) } as IDataObject;
break;
default:
throw new NodeOperationError(
this.getNode(),
`The message type "${messageType}" is not known!`,
);
}
const content = getMessageContent.call(this, i);
const body: IDataObject = {
channel: target,
...content,

View File

@ -730,6 +730,7 @@ export interface FunctionsBase {
getWorkflowStaticData(type: string): IDataObject;
getTimezone(): string;
getRestApiUrl(): string;
getInstanceBaseUrl(): string;
getMode?: () => WorkflowExecuteMode;
getActivationMode?: () => WorkflowActivateMode;
@ -1736,6 +1737,7 @@ export interface IWorkflowExecuteAdditionalData {
httpResponse?: express.Response;
httpRequest?: express.Request;
restApiUrl: string;
instanceBaseUrl: string;
setExecutionStatus?: (status: ExecutionStatus) => void;
sendMessageToUI?: (source: string, message: any) => void;
timezone: string;