Skip to content

Commit e8b546f

Browse files
authored
Pipedrive - search-notes & remove-duplicate-notes actions (#17045)
* new actions * pnpm-lock.yaml * updates * update * update * pnpm-lock.yaml * remove console.log
1 parent f1eb5a3 commit e8b546f

File tree

18 files changed

+393
-19
lines changed

18 files changed

+393
-19
lines changed

components/pipedrive/actions/add-activity/add-activity.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default {
77
key: "pipedrive-add-activity",
88
name: "Add Activity",
99
description: "Adds a new activity. Includes `more_activities_scheduled_in_context` property in response's `additional_data` which indicates whether there are more undone activities scheduled with the same deal, person or organization (depending on the supplied data). See the Pipedrive API docs for Activities [here](https://developers.pipedrive.com/docs/api/v1/#!/Activities). For info on [adding an activity in Pipedrive](https://developers.pipedrive.com/docs/api/v1/Activities#addActivity)",
10-
version: "0.1.9",
10+
version: "0.1.10",
1111
type: "action",
1212
props: {
1313
pipedriveApp,

components/pipedrive/actions/add-deal/add-deal.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default {
55
key: "pipedrive-add-deal",
66
name: "Add Deal",
77
description: "Adds a new deal. See the Pipedrive API docs for Deals [here](https://developers.pipedrive.com/docs/api/v1/Deals#addDeal)",
8-
version: "0.1.9",
8+
version: "0.1.10",
99
type: "action",
1010
props: {
1111
pipedriveApp,

components/pipedrive/actions/add-lead/add-lead.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default {
66
key: "pipedrive-add-lead",
77
name: "Add Lead",
88
description: "Create a new lead in Pipedrive. [See the documentation](https://developers.pipedrive.com/docs/api/v1/Leads#addLead)",
9-
version: "0.0.3",
9+
version: "0.0.4",
1010
type: "action",
1111
props: {
1212
pipedrive,

components/pipedrive/actions/add-note/add-note.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default {
55
key: "pipedrive-add-note",
66
name: "Add Note",
77
description: "Adds a new note. For info on [adding an note in Pipedrive](https://developers.pipedrive.com/docs/api/v1/Notes#addNote)",
8-
version: "0.0.7",
8+
version: "0.0.8",
99
type: "action",
1010
props: {
1111
pipedriveApp,

components/pipedrive/actions/add-organization/add-organization.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default {
55
key: "pipedrive-add-organization",
66
name: "Add Organization",
77
description: "Adds a new organization. See the Pipedrive API docs for Organizations [here](https://developers.pipedrive.com/docs/api/v1/Organizations#addOrganization)",
8-
version: "0.1.9",
8+
version: "0.1.10",
99
type: "action",
1010
props: {
1111
pipedriveApp,

components/pipedrive/actions/add-person/add-person.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default {
66
key: "pipedrive-add-person",
77
name: "Add Person",
88
description: "Adds a new person. See the Pipedrive API docs for People [here](https://developers.pipedrive.com/docs/api/v1/Persons#addPerson)",
9-
version: "0.1.9",
9+
version: "0.1.10",
1010
type: "action",
1111
props: {
1212
pipedriveApp,
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import pipedriveApp from "../../pipedrive.app.mjs";
2+
import { decode } from "html-entities";
3+
4+
export default {
5+
key: "pipedrive-remove-duplicate-notes",
6+
name: "Remove Duplicate Notes",
7+
description: "Remove duplicate notes from an object in Pipedrive. See the documentation for [getting notes](https://developers.pipedrive.com/docs/api/v1/Notes#getNotes) and [deleting notes](https://developers.pipedrive.com/docs/api/v1/Notes#deleteNote)",
8+
version: "0.0.1",
9+
type: "action",
10+
props: {
11+
pipedriveApp,
12+
leadId: {
13+
propDefinition: [
14+
pipedriveApp,
15+
"leadId",
16+
],
17+
description: "The ID of the lead that the notes are attached to",
18+
},
19+
dealId: {
20+
propDefinition: [
21+
pipedriveApp,
22+
"dealId",
23+
],
24+
description: "The ID of the deal that the notes are attached to",
25+
},
26+
personId: {
27+
propDefinition: [
28+
pipedriveApp,
29+
"personId",
30+
],
31+
description: "The ID of the person that the notes are attached to",
32+
},
33+
organizationId: {
34+
propDefinition: [
35+
pipedriveApp,
36+
"organizationId",
37+
],
38+
description: "The ID of the organization that the notes are attached to",
39+
},
40+
userId: {
41+
propDefinition: [
42+
pipedriveApp,
43+
"userId",
44+
],
45+
description: "The ID of the user that the notes are attached to",
46+
},
47+
projectId: {
48+
propDefinition: [
49+
pipedriveApp,
50+
"projectId",
51+
],
52+
description: "The ID of the project that the notes are attached to",
53+
},
54+
keyword: {
55+
type: "string",
56+
label: "Keyword",
57+
description: "Only remove duplicate notes that contain the specified keyword(s)",
58+
optional: true,
59+
},
60+
},
61+
methods: {
62+
getDuplicateNotes(notes) {
63+
const seenContent = new Map();
64+
const uniqueNotes = [];
65+
const duplicates = [];
66+
67+
// Sort notes by add_time (ascending) to keep the oldest duplicate
68+
const sortedNotes = notes.sort((a, b) => {
69+
const dateA = new Date(a.add_time);
70+
const dateB = new Date(b.add_time);
71+
return dateA - dateB;
72+
});
73+
74+
for (const note of sortedNotes) {
75+
// Normalize content by removing extra whitespace and converting to lowercase
76+
const decodedContent = decode(note.content || "");
77+
const normalizedContent = decodedContent?.replace(/^\s*<br\s*\/?>|<br\s*\/?>\s*$/gi, "").trim()
78+
.toLowerCase();
79+
80+
if (!normalizedContent) {
81+
// Skip notes with empty content
82+
continue;
83+
}
84+
85+
if (seenContent.has(normalizedContent)) {
86+
// This is a duplicate
87+
duplicates.push({
88+
duplicate: note,
89+
original: seenContent.get(normalizedContent),
90+
});
91+
} else {
92+
// This is the first occurrence
93+
seenContent.set(normalizedContent, note);
94+
uniqueNotes.push(note);
95+
}
96+
}
97+
98+
return {
99+
uniqueNotes,
100+
duplicates,
101+
duplicateCount: duplicates.length,
102+
};
103+
},
104+
},
105+
async run({ $ }) {
106+
let notes = await this.pipedriveApp.getPaginatedResources({
107+
fn: this.pipedriveApp.getNotes,
108+
params: {
109+
user_id: this.userId,
110+
lead_id: this.leadId,
111+
deal_id: this.dealId,
112+
person_id: this.personId,
113+
org_id: this.organizationId,
114+
project_id: this.projectId,
115+
},
116+
});
117+
118+
if (this.keyword) {
119+
notes = notes.filter((note) =>
120+
note.content?.toLowerCase().includes(this.keyword.toLowerCase()));
121+
}
122+
123+
let result = {
124+
notes,
125+
totalNotes: notes.length,
126+
};
127+
128+
const {
129+
uniqueNotes, duplicates, duplicateCount,
130+
} = this.getDuplicateNotes(notes);
131+
132+
for (const note of duplicates) {
133+
await this.pipedriveApp.deleteNote(note.duplicate.id);
134+
}
135+
136+
result = {
137+
notes: uniqueNotes,
138+
totalNotes: uniqueNotes.length,
139+
duplicatesFound: duplicateCount,
140+
duplicates: duplicates,
141+
originalCount: notes.length,
142+
};
143+
144+
$.export("$summary", `Found ${notes.length} total note(s), removed ${duplicateCount} duplicate(s), returning ${uniqueNotes.length} unique note(s)`);
145+
146+
return result;
147+
},
148+
};
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import pipedriveApp from "../../pipedrive.app.mjs";
2+
3+
export default {
4+
key: "pipedrive-search-notes",
5+
name: "Search Notes",
6+
description: "Search for notes in Pipedrive. [See the documentation](https://developers.pipedrive.com/docs/api/v1/Notes#getNotes)",
7+
version: "0.0.1",
8+
type: "action",
9+
props: {
10+
pipedriveApp,
11+
searchTerm: {
12+
type: "string",
13+
label: "Search Term",
14+
description: "The term to search for in the note content",
15+
optional: true,
16+
},
17+
leadId: {
18+
propDefinition: [
19+
pipedriveApp,
20+
"leadId",
21+
],
22+
description: "The ID of the lead that the note is attached to",
23+
},
24+
dealId: {
25+
propDefinition: [
26+
pipedriveApp,
27+
"dealId",
28+
],
29+
description: "The ID of the deal that the note is attached to",
30+
},
31+
personId: {
32+
propDefinition: [
33+
pipedriveApp,
34+
"personId",
35+
],
36+
description: "The ID of the person that the note is attached to",
37+
},
38+
organizationId: {
39+
propDefinition: [
40+
pipedriveApp,
41+
"organizationId",
42+
],
43+
description: "The ID of the organization that the note is attached to",
44+
},
45+
userId: {
46+
propDefinition: [
47+
pipedriveApp,
48+
"userId",
49+
],
50+
description: "The ID of the user that the note is attached to",
51+
},
52+
projectId: {
53+
propDefinition: [
54+
pipedriveApp,
55+
"projectId",
56+
],
57+
description: "The ID of the project that the note is attached to",
58+
},
59+
sortField: {
60+
type: "string",
61+
label: "Sort Field",
62+
description: "The field name to sort by",
63+
options: [
64+
"id",
65+
"user_id",
66+
"deal_id",
67+
"org_id",
68+
"person_id",
69+
"content",
70+
"add_time",
71+
"update_time",
72+
],
73+
optional: true,
74+
},
75+
sortDirection: {
76+
type: "string",
77+
label: "Sort Direction",
78+
description: "The direction to sort the results in",
79+
options: [
80+
"ASC",
81+
"DESC",
82+
],
83+
default: "DESC",
84+
optional: true,
85+
},
86+
startDate: {
87+
type: "string",
88+
label: "Start Date",
89+
description: "The date in format of YYYY-MM-DD from which notes to fetch",
90+
optional: true,
91+
},
92+
endDate: {
93+
type: "string",
94+
label: "End Date",
95+
description: "The date in format of YYYY-MM-DD until which notes to fetch to",
96+
optional: true,
97+
},
98+
pinnedToLeadFlag: {
99+
type: "boolean",
100+
label: "Pinned to Lead Flag",
101+
description: "If `true`, the results are filtered by note to lead pinning state",
102+
optional: true,
103+
},
104+
pinnedToDealFlag: {
105+
type: "boolean",
106+
label: "Pinned to Deal Flag",
107+
description: "If `true`, the results are filtered by note to deal pinning state",
108+
optional: true,
109+
},
110+
pinnedToOrganizationFlag: {
111+
type: "boolean",
112+
label: "Pinned to Organization Flag",
113+
description: "If `true`, the results are filtered by note to organization pinning state",
114+
optional: true,
115+
},
116+
pinnedToPersonFlag: {
117+
type: "boolean",
118+
label: "Pinned to Person Flag",
119+
description: "If `true`, the results are filtered by note to person pinning state",
120+
optional: true,
121+
},
122+
pinnedToProjectFlag: {
123+
type: "boolean",
124+
label: "Pinned to Project Flag",
125+
description: "If `true`, the results are filtered by note to project pinning state",
126+
optional: true,
127+
},
128+
maxResults: {
129+
type: "integer",
130+
label: "Max Results",
131+
description: "The maximum number of results to return",
132+
optional: true,
133+
},
134+
},
135+
async run({ $ }) {
136+
let notes = await this.pipedriveApp.getPaginatedResources({
137+
fn: this.pipedriveApp.getNotes,
138+
params: {
139+
user_id: this.userId,
140+
lead_id: this.leadId,
141+
deal_id: this.dealId,
142+
person_id: this.personId,
143+
org_id: this.organizationId,
144+
project_id: this.projectId,
145+
sort: this.sortField
146+
? `${this.sortField} ${this.sortDirection}`
147+
: undefined,
148+
pinned_to_lead_flag: this.pinnedToLeadFlag === true
149+
? 1
150+
: undefined,
151+
pinned_to_deal_flag: this.pinnedToDealFlag === true
152+
? 1
153+
: undefined,
154+
pinned_to_organization_flag: this.pinnedToOrganizationFlag === true
155+
? 1
156+
: undefined,
157+
pinned_to_person_flag: this.pinnedToPersonFlag === true
158+
? 1
159+
: undefined,
160+
pinned_to_project_flag: this.pinnedToProjectFlag === true
161+
? 1
162+
: undefined,
163+
start_date: this.startDate,
164+
end_date: this.endDate,
165+
},
166+
max: this.maxResults,
167+
});
168+
169+
if (this.searchTerm) {
170+
notes = notes.filter((note) =>
171+
note.content?.toLowerCase().includes(this.searchTerm.toLowerCase()));
172+
}
173+
174+
$.export("$summary", `Successfully found ${notes.length} note${notes.length === 1
175+
? ""
176+
: "s"}`);
177+
return notes;
178+
},
179+
};

components/pipedrive/actions/search-persons/search-persons.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default {
77
key: "pipedrive-search-persons",
88
name: "Search persons",
99
description: "Searches all Persons by `name`, `email`, `phone`, `notes` and/or custom fields. This endpoint is a wrapper of `/v1/itemSearch` with a narrower OAuth scope. Found Persons can be filtered by Organization ID. See the Pipedrive API docs [here](https://developers.pipedrive.com/docs/api/v1/Persons#searchPersons)",
10-
version: "0.1.9",
10+
version: "0.1.10",
1111
type: "action",
1212
props: {
1313
pipedriveApp,

0 commit comments

Comments
 (0)