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:
parent
d617f63ae9
commit
aa53c46367
@ -1179,6 +1179,7 @@ export async function getBase(
|
||||
executeWorkflow,
|
||||
restApiUrl: urlBaseWebhook + config.getEnv('endpoints.rest'),
|
||||
timezone,
|
||||
instanceBaseUrl: urlBaseWebhook,
|
||||
webhookBaseUrl,
|
||||
webhookWaitingBaseUrl,
|
||||
webhookTestBaseUrl,
|
||||
|
@ -2161,6 +2161,7 @@ const getCommonWorkflowFunctions = (
|
||||
getWorkflowStaticData: (type) => workflow.getStaticData(type, node),
|
||||
|
||||
getRestApiUrl: () => additionalData.restApiUrl,
|
||||
getInstanceBaseUrl: () => additionalData.instanceBaseUrl,
|
||||
getTimezone: () => getTimezone(workflow, additionalData),
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user