mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(Microsoft Teams Node): New operation sendAndWait (#12964)
This commit is contained in:
parent
d41ca832dc
commit
e92556260f
|
@ -84,7 +84,7 @@ describe('Test DiscordV2, message => sendAndWait', () => {
|
|||
{
|
||||
color: 5814783,
|
||||
description:
|
||||
'my message\n\n_This message was sent automatically with _[n8n](https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.telegram_instanceId)',
|
||||
'my message\n\n_This message was sent automatically with _[n8n](https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.discord_instanceId)',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -11,7 +11,7 @@ import type {
|
|||
import { jsonParse, NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { getSendAndWaitConfig } from '../../../../utils/sendAndWait/utils';
|
||||
import { capitalize } from '../../../../utils/utilities';
|
||||
import { capitalize, createUtmCampaignLink } from '../../../../utils/utilities';
|
||||
import { discordApiMultiPartRequest, discordApiRequest } from '../transport';
|
||||
|
||||
export const createSimplifyFunction =
|
||||
|
@ -395,9 +395,7 @@ export function createSendAndWaitMessageBody(context: IExecuteFunctions) {
|
|||
|
||||
const instanceId = context.getInstanceId();
|
||||
const attributionText = 'This message was sent automatically with ';
|
||||
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||
'n8n-nodes-base.telegram',
|
||||
)}${instanceId ? '_' + instanceId : ''}`;
|
||||
const link = createUtmCampaignLink('n8n-nodes-base.discord', instanceId);
|
||||
const description = `${config.message}\n\n_${attributionText}_[n8n](${link})`;
|
||||
|
||||
const body = {
|
||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
|||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
import { createUtmCampaignLink, updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { fromEmailProperty, toEmailProperty } from './descriptions';
|
||||
import { configureTransport, type EmailSendOptions } from './utils';
|
||||
|
@ -218,9 +218,7 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
|||
|
||||
if (appendAttribution) {
|
||||
const attributionText = 'This email was sent automatically with ';
|
||||
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||
'n8n-nodes-base.emailSend',
|
||||
)}${instanceId ? '_' + instanceId : ''}`;
|
||||
const link = createUtmCampaignLink('n8n-nodes-base.emailSend', instanceId);
|
||||
if (emailFormat === 'html' || (emailFormat === 'both' && mailOptions.html)) {
|
||||
mailOptions.html = `
|
||||
${mailOptions.html}
|
||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
|||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import { getSendAndWaitConfig } from '../../../utils/sendAndWait/utils';
|
||||
import { createUtmCampaignLink } from '../../../utils/utilities';
|
||||
import { getGoogleAccessToken } from '../GenericFunctions';
|
||||
|
||||
async function googleServiceAccountApiRequest(
|
||||
|
@ -163,9 +164,7 @@ export function createSendAndWaitMessageBody(context: IExecuteFunctions) {
|
|||
|
||||
const instanceId = context.getInstanceId();
|
||||
const attributionText = '_This_ _message_ _was_ _sent_ _automatically_ _with_';
|
||||
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||
'n8n-nodes-base.telegram',
|
||||
)}${instanceId ? '_' + instanceId : ''}`;
|
||||
const link = createUtmCampaignLink('n8n-nodes-base.googleChat', instanceId);
|
||||
const attribution = `${attributionText} _<${link}|n8n>_`;
|
||||
|
||||
const buttons: string[] = config.options.map(
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('Test GoogleChat, message => sendAndWait', () => {
|
|||
expect(mockExecuteFunctions.putExecutionToWait).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(genericFunctions.googleApiRequest).toHaveBeenCalledWith('POST', '/v1/spaceID/messages', {
|
||||
text: 'my message\n\n\n*<http://localhost/waiting-webhook/nodeID?approved=true|Approve>*\n\n_This_ _message_ _was_ _sent_ _automatically_ _with_ _<https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.telegram_instanceId|n8n>_',
|
||||
text: 'my message\n\n\n*<http://localhost/waiting-webhook/nodeID?approved=true|Approve>*\n\n_This_ _message_ _was_ _sent_ _automatically_ _with_ _<https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.googleChat_instanceId|n8n>_',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
|||
import MailComposer from 'nodemailer/lib/mail-composer';
|
||||
|
||||
import type { IEmail } from '../../../utils/sendAndWait/interfaces';
|
||||
import { escapeHtml } from '../../../utils/utilities';
|
||||
import { createUtmCampaignLink, escapeHtml } from '../../../utils/utilities';
|
||||
import { getGoogleAccessToken } from '../GenericFunctions';
|
||||
|
||||
export interface IAttachments {
|
||||
|
@ -433,9 +433,7 @@ export function prepareEmailBody(
|
|||
|
||||
if (appendAttribution) {
|
||||
const attributionText = 'This email was sent automatically with ';
|
||||
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||
'n8n-nodes-base.gmail',
|
||||
)}${instanceId ? '_' + instanceId : ''}`;
|
||||
const link = createUtmCampaignLink('n8n-nodes-base.gmail', instanceId);
|
||||
if (emailType === 'html') {
|
||||
message = `
|
||||
${message}
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
"node": "n8n-nodes-base.microsoftTeams",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication"],
|
||||
"categories": ["Communication", "HITL"],
|
||||
"subcategories": {
|
||||
"HITL": ["Human in the Loop"]
|
||||
},
|
||||
"alias": ["human", "form", "wait", "hitl", "approval"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import type { MockProxy } from 'jest-mock-extended';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { SEND_AND_WAIT_OPERATION, type IExecuteFunctions, type INode } from 'n8n-workflow';
|
||||
|
||||
import { versionDescription } from '../../../../v2/actions/versionDescription';
|
||||
import { MicrosoftTeamsV2 } from '../../../../v2/MicrosoftTeamsV2.node';
|
||||
import * as transport from '../../../../v2/transport';
|
||||
|
||||
jest.mock('../../../../v2/transport', () => {
|
||||
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||
return {
|
||||
...originalModule,
|
||||
microsoftApiRequest: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Test MicrosoftTeamsV2, chatMessage => sendAndWait', () => {
|
||||
let microsoftTeamsV2: MicrosoftTeamsV2;
|
||||
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
|
||||
|
||||
beforeEach(() => {
|
||||
microsoftTeamsV2 = new MicrosoftTeamsV2(versionDescription);
|
||||
mockExecuteFunctions = mock<IExecuteFunctions>();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should send message and put execution to wait', async () => {
|
||||
const items = [{ json: { data: 'test' } }];
|
||||
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
|
||||
if (key === 'operation') return SEND_AND_WAIT_OPERATION;
|
||||
if (key === 'resource') return 'chatMessage';
|
||||
if (key === 'chatId') return 'chatID';
|
||||
if (key === 'message') return 'my message';
|
||||
if (key === 'subject') return '';
|
||||
if (key === 'approvalOptions.values') return {};
|
||||
if (key === 'responseType') return 'approval';
|
||||
if (key === 'options.limitWaitTime.values') return {};
|
||||
});
|
||||
|
||||
mockExecuteFunctions.putExecutionToWait.mockImplementation();
|
||||
mockExecuteFunctions.getInputData.mockReturnValue(items);
|
||||
mockExecuteFunctions.getInstanceId.mockReturnValue('instanceId');
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mock<INode>({ typeVersion: 2 }));
|
||||
|
||||
mockExecuteFunctions.evaluateExpression.mockReturnValueOnce('http://localhost/waiting-webhook');
|
||||
mockExecuteFunctions.evaluateExpression.mockReturnValueOnce('nodeID');
|
||||
|
||||
const result = await microsoftTeamsV2.execute.call(mockExecuteFunctions);
|
||||
|
||||
expect(result).toEqual([items]);
|
||||
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockExecuteFunctions.putExecutionToWait).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/v1.0/chats/chatID/messages',
|
||||
{
|
||||
body: {
|
||||
content:
|
||||
'my message<br><br><a href="http://localhost/waiting-webhook/nodeID?approved=true">Approve</a><br><br><em>This message was sent automatically with <a href="https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=n8n-nodes-base.microsoftTeams_instanceId">n8n</a></em>',
|
||||
contentType: 'html',
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
|
@ -8,6 +8,7 @@ import type {
|
|||
import { router } from './actions/router';
|
||||
import { versionDescription } from './actions/versionDescription';
|
||||
import { listSearch } from './methods';
|
||||
import { sendAndWaitWebhook } from '../../../../utils/sendAndWait/utils';
|
||||
|
||||
export class MicrosoftTeamsV2 implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
@ -22,6 +23,8 @@ export class MicrosoftTeamsV2 implements INodeType {
|
|||
|
||||
methods = { listSearch };
|
||||
|
||||
webhook = sendAndWaitWebhook;
|
||||
|
||||
async execute(this: IExecuteFunctions) {
|
||||
return await router.call(this);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import { SEND_AND_WAIT_OPERATION, type INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as create from './create.operation';
|
||||
import * as get from './get.operation';
|
||||
import * as getAll from './getAll.operation';
|
||||
import * as sendAndWait from './sendAndWait.operation';
|
||||
|
||||
export { create, get, getAll };
|
||||
export { create, get, getAll, sendAndWait };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
|
@ -36,6 +37,12 @@ export const description: INodeProperties[] = [
|
|||
description: 'Get many messages from a chat',
|
||||
action: 'Get many chat messages',
|
||||
},
|
||||
{
|
||||
name: 'Send and Wait for Response',
|
||||
value: SEND_AND_WAIT_OPERATION,
|
||||
description: 'Send a message and wait for response',
|
||||
action: 'Send message and wait for response',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
|
@ -43,4 +50,5 @@ export const description: INodeProperties[] = [
|
|||
...create.description,
|
||||
...get.description,
|
||||
...getAll.description,
|
||||
...sendAndWait.description,
|
||||
];
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import type { INodeProperties, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
getSendAndWaitConfig,
|
||||
getSendAndWaitProperties,
|
||||
} from '../../../../../../utils/sendAndWait/utils';
|
||||
import { createUtmCampaignLink } from '../../../../../../utils/utilities';
|
||||
import { chatRLC } from '../../descriptions';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
|
||||
export const description: INodeProperties[] = getSendAndWaitProperties(
|
||||
[chatRLC],
|
||||
'chatMessage',
|
||||
undefined,
|
||||
{
|
||||
noButtonStyle: true,
|
||||
defaultApproveLabel: '✓ Approve',
|
||||
defaultDisapproveLabel: '✗ Decline',
|
||||
},
|
||||
).filter((p) => p.name !== 'subject');
|
||||
|
||||
export async function execute(this: IExecuteFunctions, i: number, instanceId: string) {
|
||||
const chatId = this.getNodeParameter('chatId', i, '', { extractValue: true }) as string;
|
||||
const config = getSendAndWaitConfig(this);
|
||||
|
||||
const attributionText = 'This message was sent automatically with';
|
||||
const link = createUtmCampaignLink('n8n-nodes-base.microsoftTeams', instanceId);
|
||||
const attribution = `<em>${attributionText} <a href="${link}">n8n</a></em>`;
|
||||
|
||||
const buttons = config.options.map(
|
||||
(option) => `<a href="${config.url}?approved=${option.value}">${option.label}</a>`,
|
||||
);
|
||||
|
||||
const content = `${config.message}<br><br>${buttons.join(' ')}<br><br>${attribution}`;
|
||||
|
||||
const body = {
|
||||
body: {
|
||||
contentType: 'html',
|
||||
content,
|
||||
},
|
||||
};
|
||||
|
||||
return await microsoftApiRequest.call(this, 'POST', `/v1.0/chats/${chatId}/messages`, body);
|
||||
}
|
|
@ -3,7 +3,7 @@ import type { AllEntities } from 'n8n-workflow';
|
|||
type NodeMap = {
|
||||
channel: 'create' | 'deleteChannel' | 'get' | 'getAll' | 'update';
|
||||
channelMessage: 'create' | 'getAll';
|
||||
chatMessage: 'create' | 'get' | 'getAll';
|
||||
chatMessage: 'create' | 'get' | 'getAll' | 'sendAndWait';
|
||||
task: 'create' | 'deleteTask' | 'get' | 'getAll' | 'update';
|
||||
};
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
type IDataObject,
|
||||
type INodeExecutionData,
|
||||
NodeOperationError,
|
||||
SEND_AND_WAIT_OPERATION,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import * as channel from './channel';
|
||||
|
@ -10,6 +11,7 @@ import * as channelMessage from './channelMessage';
|
|||
import * as chatMessage from './chatMessage';
|
||||
import type { MicrosoftTeamsType } from './node.type';
|
||||
import * as task from './task';
|
||||
import { configureWaitTillDate } from '../../../../../utils/sendAndWait/utils';
|
||||
|
||||
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
|
@ -27,6 +29,18 @@ export async function router(this: IExecuteFunctions): Promise<INodeExecutionDat
|
|||
operation,
|
||||
} as MicrosoftTeamsType;
|
||||
|
||||
if (
|
||||
microsoftTeamsTypeData.resource === 'chatMessage' &&
|
||||
microsoftTeamsTypeData.operation === SEND_AND_WAIT_OPERATION
|
||||
) {
|
||||
await chatMessage[microsoftTeamsTypeData.operation].execute.call(this, 0, instanceId);
|
||||
|
||||
const waitTill = configureWaitTillDate(this);
|
||||
|
||||
await this.putExecutionToWait(waitTill);
|
||||
return [items];
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
switch (microsoftTeamsTypeData.resource) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import * as channel from './channel';
|
|||
import * as channelMessage from './channelMessage';
|
||||
import * as chatMessage from './chatMessage';
|
||||
import * as task from './task';
|
||||
import { sendAndWaitWebhooksDescription } from '../../../../../utils/sendAndWait/descriptions';
|
||||
|
||||
export const versionDescription: INodeTypeDescription = {
|
||||
displayName: 'Microsoft Teams',
|
||||
|
@ -25,6 +26,7 @@ export const versionDescription: INodeTypeDescription = {
|
|||
required: true,
|
||||
},
|
||||
],
|
||||
webhooks: sendAndWaitWebhooksDescription,
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
|
|
|
@ -11,6 +11,7 @@ import type {
|
|||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import { getSendAndWaitConfig } from '../../utils/sendAndWait/utils';
|
||||
import { createUtmCampaignLink } from '../../utils/utilities';
|
||||
|
||||
// Interface in n8n
|
||||
export interface IMarkupKeyboard {
|
||||
|
@ -80,9 +81,7 @@ export function addAdditionalFields(
|
|||
|
||||
if (operation === 'sendMessage') {
|
||||
const attributionText = 'This message was sent automatically with ';
|
||||
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||
'n8n-nodes-base.telegram',
|
||||
)}${instanceId ? '_' + instanceId : ''}`;
|
||||
const link = createUtmCampaignLink('n8n-nodes-base.telegram', instanceId);
|
||||
|
||||
if (nodeVersion && nodeVersion >= 1.1 && additionalFields.appendAttribution === undefined) {
|
||||
additionalFields.appendAttribution = true;
|
||||
|
@ -263,9 +262,7 @@ export function createSendAndWaitMessageBody(context: IExecuteFunctions) {
|
|||
|
||||
const instanceId = context.getInstanceId();
|
||||
const attributionText = 'This message was sent automatically with ';
|
||||
const link = `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||
'n8n-nodes-base.telegram',
|
||||
)}${instanceId ? '_' + instanceId : ''}`;
|
||||
const link = createUtmCampaignLink('n8n-nodes-base.telegram', instanceId);
|
||||
text = `${text}\n\n_${attributionText}_[n8n](${link})`;
|
||||
|
||||
const body = {
|
||||
|
|
|
@ -461,3 +461,9 @@ export function sortItemKeysByPriorityList(data: INodeExecutionData[], priorityL
|
|||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
export function createUtmCampaignLink(nodeType: string, instanceId?: string) {
|
||||
return `https://n8n.io/?utm_source=n8n-internal&utm_medium=powered_by&utm_campaign=${encodeURIComponent(
|
||||
nodeType,
|
||||
)}${instanceId ? '_' + instanceId : ''}`;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue