Skip to content

Commit 8b02c21

Browse files
authored
17341 action freshsales (#17506)
* [ACTION] Freshsales #17341 Actions - Create Contact - Create Deal - List All Contacts - List All Deals * pnpm update
1 parent b68121d commit 8b02c21

File tree

11 files changed

+472
-22
lines changed

11 files changed

+472
-22
lines changed

components/freshsales/.gitignore

Lines changed: 0 additions & 3 deletions
This file was deleted.

components/freshsales/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ The Freshsales API offers a suite of functionalities to enhance your CRM experie
44

55
# Example Use Cases
66

7-
- **Lead Scoring Automation**: Automatically update the lead score in Freshsales based on customer interactions tracked in other tools. For instance, increase a lead's score when they open a marketing email sent via SendGrid.
8-
97
- **Deal Progression Notifications**: Set up a workflow that sends real-time Slack notifications to a sales channel when a deal moves to a new stage in the Freshsales pipeline, keeping the team instantly informed.
108

119
- **Customer Success Handover**: Automate the process of creating a task in project management tools like Asana when a deal is won in Freshsales, ensuring smooth handover from sales to customer success teams.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { parseObject } from "../../common/utils.mjs";
2+
import freshsales from "../../freshsales.app.mjs";
3+
4+
export default {
5+
key: "freshsales-create-contact",
6+
name: "Create Contact",
7+
description: "Create a new contact in your Freshsales account. [See the documentation](https://developer.freshsales.io/api/#create_a_contact)",
8+
version: "0.0.1",
9+
type: "action",
10+
props: {
11+
freshsales,
12+
email: {
13+
type: "string",
14+
label: "Email",
15+
description: "Email of the contact",
16+
reloadProps: true,
17+
},
18+
},
19+
async additionalProps() {
20+
const { fields } = await this.freshsales.getContactFields();
21+
const filteredFields = fields.filter((field) => (field.visible === true && field.name != "emails"));
22+
23+
const props = {};
24+
for (const field of filteredFields) {
25+
26+
const data = {
27+
type: field.type === "multi_select_dropdown"
28+
? "string[]"
29+
: "string",
30+
label: field.label,
31+
description: `${field.label} of the contact.`,
32+
optional: field.required === false,
33+
};
34+
35+
if (field.name === "sales_accounts") {
36+
const { sales_accounts: options } = await this.freshsales.getSalesAccounts();
37+
const salesAccountOptions = options.map((account) => ({
38+
label: account.name,
39+
value: `${account.id}`,
40+
}));
41+
props.primaryAccount = {
42+
type: "string",
43+
label: "Primary Sales Account",
44+
description: "Primary sales account of the contact.",
45+
optional: true,
46+
options: salesAccountOptions,
47+
};
48+
props.additionalAccounts = {
49+
type: "string[]",
50+
label: "Additional Sales Accounts",
51+
description: "Additional sales accounts of the contact.",
52+
optional: true,
53+
options: salesAccountOptions,
54+
};
55+
} else {
56+
if ([
57+
"dropdown",
58+
"multi_select_dropdown",
59+
].includes(field.type)) {
60+
data.type = "integer";
61+
data.options = field.choices.map((choice) => ({
62+
label: choice.value,
63+
value: choice.id,
64+
}));
65+
}
66+
props[field.name] = data;
67+
}
68+
}
69+
70+
return props;
71+
},
72+
async run({ $ }) {
73+
74+
const {
75+
freshsales,
76+
...data
77+
} = this;
78+
79+
if (data.primaryAccount) {
80+
data.sales_accounts = [
81+
{
82+
id: data.primaryAccount,
83+
is_primary: true,
84+
},
85+
];
86+
parseObject(data.additionalAccounts)?.map((account) => {
87+
data.sales_accounts.push(
88+
{
89+
id: account,
90+
is_primary: false,
91+
},
92+
);
93+
});
94+
delete data.primaryAccount;
95+
delete data.additionalAccounts;
96+
}
97+
98+
const response = await freshsales.createContact({
99+
$,
100+
data,
101+
});
102+
103+
$.export("$summary", `Successfully created contact with ID: ${response.contact.id}`);
104+
return response;
105+
},
106+
};
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import freshsales from "../../freshsales.app.mjs";
2+
3+
export default {
4+
key: "freshsales-create-deal",
5+
name: "Create Deal",
6+
description: "Create a new deal in your Freshsales account. [See the documentation](https://developer.freshsales.io/api/#create_deal)",
7+
version: "0.0.1",
8+
type: "action",
9+
props: {
10+
freshsales,
11+
name: {
12+
type: "string",
13+
label: "Name",
14+
description: "Name of the deal",
15+
reloadProps: true,
16+
},
17+
},
18+
async additionalProps() {
19+
const { fields } = await this.freshsales.getDealFields();
20+
const filteredFields = fields.filter((field) => (field.visible === true && field.name != "name"));
21+
22+
const props = {};
23+
for (const field of filteredFields) {
24+
25+
const data = {
26+
type: field.type === "multi_select_dropdown"
27+
? "string[]"
28+
: "string",
29+
label: field.label,
30+
description: `${field.label} of the deal.`,
31+
optional: field.required === false,
32+
};
33+
34+
if (field.name === "sales_account_id") {
35+
const { sales_accounts: options } = await this.freshsales.getSalesAccounts();
36+
data.type = "integer";
37+
data.label = "Sales Account";
38+
data.description = "Sales account of the deal.";
39+
data.optional = true;
40+
data.options = options.map((account) => ({
41+
label: account.name,
42+
value: account.id,
43+
}));
44+
}
45+
46+
if (field.name === "contacts") {
47+
const { contacts: options } = await this.freshsales.getContacts();
48+
data.type = "integer[]";
49+
data.label = "Contacts";
50+
data.description = "Contacts of the deal.";
51+
data.optional = true;
52+
data.options = options.map((contact) => ({
53+
label: contact.display_name,
54+
value: contact.id,
55+
}));
56+
}
57+
58+
if ([
59+
"dropdown",
60+
"multi_select_dropdown",
61+
].includes(field.type)) {
62+
data.type = "integer";
63+
data.options = field.choices.map((choice) => ({
64+
label: choice.value,
65+
value: choice.id,
66+
}));
67+
}
68+
69+
props[field.name] = data;
70+
}
71+
72+
return props;
73+
},
74+
async run({ $ }) {
75+
76+
const {
77+
freshsales,
78+
...data
79+
} = this;
80+
81+
const response = await freshsales.createDeal({
82+
$,
83+
data,
84+
});
85+
86+
$.export("$summary", `Successfully created deal with ID: ${response.deal.id}`);
87+
return response;
88+
},
89+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import freshsales from "../../freshsales.app.mjs";
2+
3+
export default {
4+
key: "freshsales-list-all-contacts",
5+
name: "List All Contacts",
6+
description: "Fetch all contacts from your Freshsales account. [See the documentation](https://developer.freshsales.io/api/#list_all_contacts)",
7+
version: "0.0.1",
8+
type: "action",
9+
props: {
10+
freshsales,
11+
},
12+
async run({ $ }) {
13+
const filterId = await this.freshsales.getFilterId({
14+
model: "contacts",
15+
name: "All Contacts",
16+
});
17+
18+
const response = this.freshsales.paginate({
19+
fn: this.freshsales.listContacts,
20+
responseField: "contacts",
21+
filterId,
22+
});
23+
24+
const contacts = [];
25+
for await (const contact of response) {
26+
contacts.push(contact);
27+
}
28+
29+
$.export("$summary", `Successfully fetched ${contacts?.length || 0} contact${contacts?.length === 1
30+
? ""
31+
: "s"}`);
32+
return contacts;
33+
},
34+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import freshsales from "../../freshsales.app.mjs";
2+
3+
export default {
4+
key: "freshsales-list-all-deals",
5+
name: "List All Deals",
6+
description: "Fetch all deals from your Freshsales account. [See the documentation](https://developer.freshsales.io/api/#list_all_deals)",
7+
version: "0.0.1",
8+
type: "action",
9+
props: {
10+
freshsales,
11+
},
12+
async run({ $ }) {
13+
const filterId = await this.freshsales.getFilterId({
14+
model: "deals",
15+
name: "All Deals",
16+
});
17+
18+
const response = this.freshsales.paginate({
19+
fn: this.freshsales.listDeals,
20+
responseField: "deals",
21+
filterId,
22+
});
23+
24+
const deals = [];
25+
for await (const deal of response) {
26+
deals.push(deal);
27+
}
28+
29+
$.export("$summary", `Successfully fetched ${deals?.length || 0} deal${deals?.length === 1
30+
? ""
31+
: "s"}`);
32+
return deals;
33+
},
34+
};

components/freshsales/app/freshsales.app.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export const snakeCaseToTitleCase = (s) =>
2+
s.replace(/^_*(.)|_+(.)/g, (s, c, d) => c
3+
? c.toUpperCase()
4+
: " " + d.toUpperCase());
5+
6+
export const parseObject = (obj) => {
7+
if (!obj) return undefined;
8+
9+
if (Array.isArray(obj)) {
10+
return obj.map((item) => {
11+
if (typeof item === "string") {
12+
try {
13+
return JSON.parse(item);
14+
} catch (e) {
15+
return item;
16+
}
17+
}
18+
return item;
19+
});
20+
}
21+
if (typeof obj === "string") {
22+
try {
23+
return JSON.parse(obj);
24+
} catch (e) {
25+
return obj;
26+
}
27+
}
28+
return obj;
29+
};

0 commit comments

Comments
 (0)