Skip to content

Commit a39502f

Browse files
fix(Pipedrive Trigger Node): Add support for webhooks v2 (#14220)
Co-authored-by: Michael Kret <[email protected]>
1 parent 37e5349 commit a39502f

File tree

2 files changed

+215
-40
lines changed

2 files changed

+215
-40
lines changed

packages/nodes-base/nodes/Pipedrive/PipedriveTrigger.node.ts

Lines changed: 106 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,60 @@ function authorizationError(resp: Response, realm: string, responseCode: number,
2929
noWebhookResponse: true,
3030
};
3131
}
32-
32+
const entityOptions = [
33+
{
34+
name: 'Activity',
35+
value: 'activity',
36+
},
37+
{
38+
name: 'Activity Type',
39+
value: 'activityType',
40+
},
41+
{
42+
name: 'All',
43+
value: '*',
44+
},
45+
{
46+
name: 'Deal',
47+
value: 'deal',
48+
},
49+
{
50+
name: 'Note',
51+
value: 'note',
52+
},
53+
{
54+
name: 'Organization',
55+
value: 'organization',
56+
},
57+
{
58+
name: 'Person',
59+
value: 'person',
60+
},
61+
{
62+
name: 'Pipeline',
63+
value: 'pipeline',
64+
},
65+
{
66+
name: 'Product',
67+
value: 'product',
68+
},
69+
{
70+
name: 'Stage',
71+
value: 'stage',
72+
},
73+
{
74+
name: 'User',
75+
value: 'user',
76+
},
77+
];
3378
export class PipedriveTrigger implements INodeType {
3479
description: INodeTypeDescription = {
3580
displayName: 'Pipedrive Trigger',
3681
name: 'pipedriveTrigger',
3782
icon: 'file:pipedrive.svg',
3883
group: ['trigger'],
39-
version: 1,
84+
version: [1, 1.1],
85+
defaultVersion: 1.1,
4086
description: 'Starts the workflow when Pipedrive events occur',
4187
defaults: {
4288
name: 'Pipedrive Trigger',
@@ -118,6 +164,11 @@ export class PipedriveTrigger implements INodeType {
118164
displayName: 'Action',
119165
name: 'action',
120166
type: 'options',
167+
displayOptions: {
168+
hide: {
169+
'@version': [{ _cnd: { gte: 1.1 } }],
170+
},
171+
},
121172
options: [
122173
{
123174
name: 'Added',
@@ -154,57 +205,68 @@ export class PipedriveTrigger implements INodeType {
154205
description: 'Type of action to receive notifications about',
155206
},
156207
{
157-
displayName: 'Object',
158-
name: 'object',
208+
displayName: 'Action',
209+
name: 'action',
159210
type: 'options',
160-
options: [
161-
{
162-
name: 'Activity',
163-
value: 'activity',
164-
},
165-
{
166-
name: 'Activity Type',
167-
value: 'activityType',
211+
displayOptions: {
212+
hide: {
213+
'@version': [{ _cnd: { lte: 1 } }],
168214
},
215+
},
216+
options: [
169217
{
170218
name: 'All',
171219
value: '*',
220+
description: 'Any change',
221+
action: 'Any change',
172222
},
173223
{
174-
name: 'Deal',
175-
value: 'deal',
176-
},
177-
{
178-
name: 'Note',
179-
value: 'note',
180-
},
181-
{
182-
name: 'Organization',
183-
value: 'organization',
184-
},
185-
{
186-
name: 'Person',
187-
value: 'person',
188-
},
189-
{
190-
name: 'Pipeline',
191-
value: 'pipeline',
192-
},
193-
{
194-
name: 'Product',
195-
value: 'product',
224+
name: 'Create',
225+
value: 'create',
226+
description: 'Data got added',
227+
action: 'Data was added',
196228
},
197229
{
198-
name: 'Stage',
199-
value: 'stage',
230+
name: 'Delete',
231+
value: 'delete',
232+
description: 'Data got deleted',
233+
action: 'Data was deleted',
200234
},
201235
{
202-
name: 'User',
203-
value: 'user',
236+
name: 'Change',
237+
value: 'change',
238+
description: 'Data got changed',
239+
action: 'Data was changed',
204240
},
205241
],
206242
default: '*',
243+
description: 'Type of action to receive notifications about',
244+
},
245+
{
246+
displayName: 'Entity',
247+
name: 'entity',
248+
type: 'options',
249+
options: entityOptions,
250+
default: '*',
251+
description: 'Type of object to receive notifications about',
252+
displayOptions: {
253+
hide: {
254+
'@version': [{ _cnd: { lte: 1 } }],
255+
},
256+
},
257+
},
258+
{
259+
displayName: 'Object',
260+
name: 'object',
261+
type: 'options',
262+
options: entityOptions,
263+
default: '*',
207264
description: 'Type of object to receive notifications about',
265+
displayOptions: {
266+
hide: {
267+
'@version': [{ _cnd: { gte: 1.1 } }],
268+
},
269+
},
208270
},
209271
],
210272
};
@@ -218,7 +280,9 @@ export class PipedriveTrigger implements INodeType {
218280

219281
const eventAction = this.getNodeParameter('action') as string;
220282

221-
const eventObject = this.getNodeParameter('object') as string;
283+
const nodeVersion = this.getNode().typeVersion;
284+
const entityParamName = nodeVersion === 1 ? 'object' : 'entity';
285+
const eventObject = this.getNodeParameter(entityParamName) as string;
222286

223287
// Webhook got created before so check if it still exists
224288
const endpoint = '/webhooks';
@@ -247,7 +311,9 @@ export class PipedriveTrigger implements INodeType {
247311
const webhookUrl = this.getNodeWebhookUrl('default');
248312
const incomingAuthentication = this.getNodeParameter('incomingAuthentication', 0) as string;
249313
const eventAction = this.getNodeParameter('action') as string;
250-
const eventObject = this.getNodeParameter('object') as string;
314+
const nodeVersion = this.getNode().typeVersion;
315+
const entityParamName = nodeVersion === 1 ? 'object' : 'entity';
316+
const eventObject = this.getNodeParameter(entityParamName) as string;
251317

252318
const endpoint = '/webhooks';
253319

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import type { IHookFunctions } from 'n8n-workflow';
2+
3+
import { pipedriveApiRequest } from '../GenericFunctions';
4+
import { PipedriveTrigger } from '../PipedriveTrigger.node';
5+
6+
jest.mock('basic-auth');
7+
jest.mock('../GenericFunctions');
8+
9+
describe('PipedriveTrigger', () => {
10+
let pipedriveTrigger: PipedriveTrigger;
11+
let mockHookFunctions: jest.Mocked<IHookFunctions>;
12+
13+
beforeEach(() => {
14+
pipedriveTrigger = new PipedriveTrigger();
15+
16+
mockHookFunctions = {
17+
getNodeWebhookUrl: jest.fn(),
18+
getWorkflowStaticData: jest.fn(),
19+
getNodeParameter: jest.fn(),
20+
getNode: jest.fn().mockReturnValue({ typeVersion: 1.1 }),
21+
} as unknown as jest.Mocked<IHookFunctions>;
22+
});
23+
24+
describe('Webhook Methods', () => {
25+
describe('checkExists', () => {
26+
it('should return true if webhook already exists', async () => {
27+
mockHookFunctions.getNodeWebhookUrl.mockReturnValue('http://test-webhook-url');
28+
mockHookFunctions.getWorkflowStaticData.mockReturnValue({});
29+
mockHookFunctions.getNodeParameter.mockImplementation((param) => {
30+
if (param === 'action') return '*';
31+
if (param === 'entity') return 'deal';
32+
return null;
33+
});
34+
35+
(pipedriveApiRequest as jest.Mock).mockResolvedValue({
36+
data: [
37+
{
38+
id: '123',
39+
subscription_url: 'http://test-webhook-url',
40+
event_action: '*',
41+
event_object: 'deal',
42+
},
43+
],
44+
});
45+
46+
const result =
47+
await pipedriveTrigger.webhookMethods.default.checkExists.call(mockHookFunctions);
48+
49+
expect(result).toBe(true);
50+
expect(mockHookFunctions.getWorkflowStaticData('node').webhookId).toBe('123');
51+
});
52+
53+
it('should return false if no matching webhook exists', async () => {
54+
mockHookFunctions.getNodeWebhookUrl.mockReturnValue('http://test-webhook-url');
55+
mockHookFunctions.getWorkflowStaticData.mockReturnValue({});
56+
mockHookFunctions.getNodeParameter.mockImplementation((param) => {
57+
if (param === 'action') return '*';
58+
if (param === 'entity') return 'deal';
59+
return null;
60+
});
61+
62+
(pipedriveApiRequest as jest.Mock).mockResolvedValue({
63+
data: [
64+
{
65+
subscription_url: 'http://different-url',
66+
event_action: 'create',
67+
event_object: 'person',
68+
},
69+
],
70+
});
71+
72+
const result =
73+
await pipedriveTrigger.webhookMethods.default.checkExists.call(mockHookFunctions);
74+
75+
expect(result).toBe(false);
76+
});
77+
});
78+
79+
describe('create', () => {
80+
it('should create a webhook successfully', async () => {
81+
mockHookFunctions.getNodeWebhookUrl.mockReturnValue('http://test-webhook-url');
82+
mockHookFunctions.getNodeParameter.mockImplementation((param) => {
83+
if (param === 'incomingAuthentication') return 'none';
84+
if (param === 'action') return '*';
85+
if (param === 'entity') return 'deal';
86+
return null;
87+
});
88+
mockHookFunctions.getWorkflowStaticData.mockReturnValue({});
89+
90+
(pipedriveApiRequest as jest.Mock).mockResolvedValue({
91+
data: { id: '123' },
92+
});
93+
94+
const result = await pipedriveTrigger.webhookMethods.default.create.call(mockHookFunctions);
95+
96+
expect(result).toBe(true);
97+
expect(pipedriveApiRequest).toHaveBeenCalledWith(
98+
'POST',
99+
'/webhooks',
100+
expect.objectContaining({
101+
event_action: '*',
102+
event_object: 'deal',
103+
subscription_url: 'http://test-webhook-url',
104+
}),
105+
);
106+
});
107+
});
108+
});
109+
});

0 commit comments

Comments
 (0)