fix(Pipedrive Trigger Node): Add support for webhooks v2 (#14220)

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
Shireen Missi
2025-04-02 08:54:31 +01:00
committed by GitHub
parent 37e5349fe1
commit a39502f3bb
2 changed files with 215 additions and 40 deletions

View File

@@ -29,14 +29,60 @@ function authorizationError(resp: Response, realm: string, responseCode: number,
noWebhookResponse: true,
};
}
const entityOptions = [
{
name: 'Activity',
value: 'activity',
},
{
name: 'Activity Type',
value: 'activityType',
},
{
name: 'All',
value: '*',
},
{
name: 'Deal',
value: 'deal',
},
{
name: 'Note',
value: 'note',
},
{
name: 'Organization',
value: 'organization',
},
{
name: 'Person',
value: 'person',
},
{
name: 'Pipeline',
value: 'pipeline',
},
{
name: 'Product',
value: 'product',
},
{
name: 'Stage',
value: 'stage',
},
{
name: 'User',
value: 'user',
},
];
export class PipedriveTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Pipedrive Trigger',
name: 'pipedriveTrigger',
icon: 'file:pipedrive.svg',
group: ['trigger'],
version: 1,
version: [1, 1.1],
defaultVersion: 1.1,
description: 'Starts the workflow when Pipedrive events occur',
defaults: {
name: 'Pipedrive Trigger',
@@ -118,6 +164,11 @@ export class PipedriveTrigger implements INodeType {
displayName: 'Action',
name: 'action',
type: 'options',
displayOptions: {
hide: {
'@version': [{ _cnd: { gte: 1.1 } }],
},
},
options: [
{
name: 'Added',
@@ -154,57 +205,68 @@ export class PipedriveTrigger implements INodeType {
description: 'Type of action to receive notifications about',
},
{
displayName: 'Object',
name: 'object',
displayName: 'Action',
name: 'action',
type: 'options',
displayOptions: {
hide: {
'@version': [{ _cnd: { lte: 1 } }],
},
},
options: [
{
name: 'Activity',
value: 'activity',
},
{
name: 'Activity Type',
value: 'activityType',
},
{
name: 'All',
value: '*',
description: 'Any change',
action: 'Any change',
},
{
name: 'Deal',
value: 'deal',
name: 'Create',
value: 'create',
description: 'Data got added',
action: 'Data was added',
},
{
name: 'Note',
value: 'note',
name: 'Delete',
value: 'delete',
description: 'Data got deleted',
action: 'Data was deleted',
},
{
name: 'Organization',
value: 'organization',
},
{
name: 'Person',
value: 'person',
},
{
name: 'Pipeline',
value: 'pipeline',
},
{
name: 'Product',
value: 'product',
},
{
name: 'Stage',
value: 'stage',
},
{
name: 'User',
value: 'user',
name: 'Change',
value: 'change',
description: 'Data got changed',
action: 'Data was changed',
},
],
default: '*',
description: 'Type of action to receive notifications about',
},
{
displayName: 'Entity',
name: 'entity',
type: 'options',
options: entityOptions,
default: '*',
description: 'Type of object to receive notifications about',
displayOptions: {
hide: {
'@version': [{ _cnd: { lte: 1 } }],
},
},
},
{
displayName: 'Object',
name: 'object',
type: 'options',
options: entityOptions,
default: '*',
description: 'Type of object to receive notifications about',
displayOptions: {
hide: {
'@version': [{ _cnd: { gte: 1.1 } }],
},
},
},
],
};
@@ -218,7 +280,9 @@ export class PipedriveTrigger implements INodeType {
const eventAction = this.getNodeParameter('action') as string;
const eventObject = this.getNodeParameter('object') as string;
const nodeVersion = this.getNode().typeVersion;
const entityParamName = nodeVersion === 1 ? 'object' : 'entity';
const eventObject = this.getNodeParameter(entityParamName) as string;
// Webhook got created before so check if it still exists
const endpoint = '/webhooks';
@@ -247,7 +311,9 @@ export class PipedriveTrigger implements INodeType {
const webhookUrl = this.getNodeWebhookUrl('default');
const incomingAuthentication = this.getNodeParameter('incomingAuthentication', 0) as string;
const eventAction = this.getNodeParameter('action') as string;
const eventObject = this.getNodeParameter('object') as string;
const nodeVersion = this.getNode().typeVersion;
const entityParamName = nodeVersion === 1 ? 'object' : 'entity';
const eventObject = this.getNodeParameter(entityParamName) as string;
const endpoint = '/webhooks';

View File

@@ -0,0 +1,109 @@
import type { IHookFunctions } from 'n8n-workflow';
import { pipedriveApiRequest } from '../GenericFunctions';
import { PipedriveTrigger } from '../PipedriveTrigger.node';
jest.mock('basic-auth');
jest.mock('../GenericFunctions');
describe('PipedriveTrigger', () => {
let pipedriveTrigger: PipedriveTrigger;
let mockHookFunctions: jest.Mocked<IHookFunctions>;
beforeEach(() => {
pipedriveTrigger = new PipedriveTrigger();
mockHookFunctions = {
getNodeWebhookUrl: jest.fn(),
getWorkflowStaticData: jest.fn(),
getNodeParameter: jest.fn(),
getNode: jest.fn().mockReturnValue({ typeVersion: 1.1 }),
} as unknown as jest.Mocked<IHookFunctions>;
});
describe('Webhook Methods', () => {
describe('checkExists', () => {
it('should return true if webhook already exists', async () => {
mockHookFunctions.getNodeWebhookUrl.mockReturnValue('http://test-webhook-url');
mockHookFunctions.getWorkflowStaticData.mockReturnValue({});
mockHookFunctions.getNodeParameter.mockImplementation((param) => {
if (param === 'action') return '*';
if (param === 'entity') return 'deal';
return null;
});
(pipedriveApiRequest as jest.Mock).mockResolvedValue({
data: [
{
id: '123',
subscription_url: 'http://test-webhook-url',
event_action: '*',
event_object: 'deal',
},
],
});
const result =
await pipedriveTrigger.webhookMethods.default.checkExists.call(mockHookFunctions);
expect(result).toBe(true);
expect(mockHookFunctions.getWorkflowStaticData('node').webhookId).toBe('123');
});
it('should return false if no matching webhook exists', async () => {
mockHookFunctions.getNodeWebhookUrl.mockReturnValue('http://test-webhook-url');
mockHookFunctions.getWorkflowStaticData.mockReturnValue({});
mockHookFunctions.getNodeParameter.mockImplementation((param) => {
if (param === 'action') return '*';
if (param === 'entity') return 'deal';
return null;
});
(pipedriveApiRequest as jest.Mock).mockResolvedValue({
data: [
{
subscription_url: 'http://different-url',
event_action: 'create',
event_object: 'person',
},
],
});
const result =
await pipedriveTrigger.webhookMethods.default.checkExists.call(mockHookFunctions);
expect(result).toBe(false);
});
});
describe('create', () => {
it('should create a webhook successfully', async () => {
mockHookFunctions.getNodeWebhookUrl.mockReturnValue('http://test-webhook-url');
mockHookFunctions.getNodeParameter.mockImplementation((param) => {
if (param === 'incomingAuthentication') return 'none';
if (param === 'action') return '*';
if (param === 'entity') return 'deal';
return null;
});
mockHookFunctions.getWorkflowStaticData.mockReturnValue({});
(pipedriveApiRequest as jest.Mock).mockResolvedValue({
data: { id: '123' },
});
const result = await pipedriveTrigger.webhookMethods.default.create.call(mockHookFunctions);
expect(result).toBe(true);
expect(pipedriveApiRequest).toHaveBeenCalledWith(
'POST',
'/webhooks',
expect.objectContaining({
event_action: '*',
event_object: 'deal',
subscription_url: 'http://test-webhook-url',
}),
);
});
});
});
});