From 0f19aced1bc9537bf92d29970939f593d7a3e5ef Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Fri, 7 Feb 2025 12:12:52 -0500 Subject: [PATCH 1/4] salespype init --- .../add-contact-to-campaign.mjs | 33 +++ .../actions/create-contact/create-contact.mjs | 99 +++++++ .../actions/create-task/create-task.mjs | 83 ++++++ components/salespype/package.json | 2 +- components/salespype/salespype.app.mjs | 270 +++++++++++++++++- .../contact-updated/contact-updated.mjs | 75 +++++ .../new-campaign-created.mjs | 80 ++++++ .../new-contact-created.mjs | 86 ++++++ 8 files changed, 723 insertions(+), 5 deletions(-) create mode 100644 components/salespype/actions/add-contact-to-campaign/add-contact-to-campaign.mjs create mode 100644 components/salespype/actions/create-contact/create-contact.mjs create mode 100644 components/salespype/actions/create-task/create-task.mjs create mode 100644 components/salespype/sources/contact-updated/contact-updated.mjs create mode 100644 components/salespype/sources/new-campaign-created/new-campaign-created.mjs create mode 100644 components/salespype/sources/new-contact-created/new-contact-created.mjs diff --git a/components/salespype/actions/add-contact-to-campaign/add-contact-to-campaign.mjs b/components/salespype/actions/add-contact-to-campaign/add-contact-to-campaign.mjs new file mode 100644 index 0000000000000..4d42ebc37ae48 --- /dev/null +++ b/components/salespype/actions/add-contact-to-campaign/add-contact-to-campaign.mjs @@ -0,0 +1,33 @@ +import salespype from "../../salespype.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "salespype-add-contact-to-campaign", + name: "Add Contact to Campaign", + description: "Adds a contact to a campaign. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + salespype, + campaignId: { + propDefinition: [ + salespype, + "campaignId", + ], + }, + contactId: { + propDefinition: [ + salespype, + "contactId", + ], + }, + }, + async run({ $ }) { + const response = await this.salespype.addContactToCampaign({ + campaignId: this.campaignId, + contactId: this.contactId, + }); + $.export("$summary", `Added contact ${this.contactId} to campaign ${this.campaignId}`); + return response; + }, +}; diff --git a/components/salespype/actions/create-contact/create-contact.mjs b/components/salespype/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..308d9467aca22 --- /dev/null +++ b/components/salespype/actions/create-contact/create-contact.mjs @@ -0,0 +1,99 @@ +import salespype from "../../salespype.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "salespype-create-contact", + name: "Create Contact", + description: "Creates a new contact in Salespype. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + salespype: { + type: "app", + app: "salespype", + }, + firstName: { + propDefinition: [ + salespype, + "firstName", + ], + }, + lastName: { + propDefinition: [ + salespype, + "lastName", + ], + }, + email: { + propDefinition: [ + salespype, + "email", + ], + }, + address: { + propDefinition: [ + salespype, + "address", + ], + optional: true, + }, + city: { + propDefinition: [ + salespype, + "city", + ], + optional: true, + }, + state: { + propDefinition: [ + salespype, + "state", + ], + optional: true, + }, + zip: { + propDefinition: [ + salespype, + "zip", + ], + optional: true, + }, + country: { + propDefinition: [ + salespype, + "country", + ], + optional: true, + }, + companyName: { + propDefinition: [ + salespype, + "companyName", + ], + optional: true, + }, + birthDate: { + propDefinition: [ + salespype, + "birthDate", + ], + optional: true, + }, + }, + async run({ $ }) { + const contact = await this.salespype.createContact({ + firstName: this.firstName, + lastName: this.lastName, + email: this.email, + address: this.address, + city: this.city, + state: this.state, + zip: this.zip, + country: this.country, + companyName: this.companyName, + birthDate: this.birthDate, + }); + $.export("$summary", `Created contact ${contact.first_name} ${contact.last_name} (${contact.email})`); + return contact; + }, +}; diff --git a/components/salespype/actions/create-task/create-task.mjs b/components/salespype/actions/create-task/create-task.mjs new file mode 100644 index 0000000000000..53a9a94994f1e --- /dev/null +++ b/components/salespype/actions/create-task/create-task.mjs @@ -0,0 +1,83 @@ +import salespype from "../../salespype.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "salespype-create-task", + name: "Create Task", + description: "Creates a new task in Salespype. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + salespype, + contactId: { + propDefinition: [ + salespype, + "contactId", + ], + }, + task: { + propDefinition: [ + salespype, + "task", + ], + }, + taskTypeId: { + propDefinition: [ + salespype, + "taskTypeId", + ], + }, + date: { + propDefinition: [ + salespype, + "date", + ], + optional: true, + }, + time: { + propDefinition: [ + salespype, + "time", + ], + optional: true, + }, + duration: { + propDefinition: [ + salespype, + "duration", + ], + optional: true, + }, + note: { + propDefinition: [ + salespype, + "note", + ], + optional: true, + }, + }, + async run({ $ }) { + const taskData = { + contact_id: this.contactId, + task: this.task, + task_type_id: this.taskTypeId, + ...(this.date && { + date: this.date, + }), + ...(this.time && { + time: this.time, + }), + ...(this.duration && { + duration: this.duration, + }), + ...(this.note && { + note: this.note, + }), + }; + + const task = await this.salespype.createTask(taskData); + + $.export("$summary", `Created task '${this.task}' with ID ${task.id}`); + return task; + }, +}; diff --git a/components/salespype/package.json b/components/salespype/package.json index 26aa36269108c..e68679187794b 100644 --- a/components/salespype/package.json +++ b/components/salespype/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/salespype/salespype.app.mjs b/components/salespype/salespype.app.mjs index ffaa86df13cb0..1721955ef52d5 100644 --- a/components/salespype/salespype.app.mjs +++ b/components/salespype/salespype.app.mjs @@ -1,11 +1,273 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "salespype", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + contactId: { + type: "string", + label: "Contact ID", + description: "The unique identifier of the contact", + }, + campaignId: { + type: "string", + label: "Campaign ID", + description: "The unique identifier of the campaign", + }, + firstName: { + type: "string", + label: "First Name", + description: "The first name of the contact", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the contact", + }, + email: { + type: "string", + label: "Email", + description: "The email address of the contact", + }, + address: { + type: "string", + label: "Address", + description: "The address of the contact", + optional: true, + }, + city: { + type: "string", + label: "City", + description: "The city of the contact", + optional: true, + }, + state: { + type: "string", + label: "State", + description: "The state of the contact", + optional: true, + }, + zip: { + type: "string", + label: "ZIP Code", + description: "The ZIP code of the contact", + optional: true, + }, + country: { + type: "string", + label: "Country", + description: "The country of the contact", + optional: true, + }, + companyName: { + type: "string", + label: "Company Name", + description: "The company name of the contact", + optional: true, + }, + birthDate: { + type: "string", + label: "Birthdate", + description: "The birthdate of the contact", + optional: true, + }, + task: { + type: "string", + label: "Task", + description: "The task description", + }, + taskTypeId: { + type: "string", + label: "Task Type ID", + description: "The unique identifier of the task type", + }, + date: { + type: "string", + label: "Date", + description: "The date for the task", + optional: true, + }, + time: { + type: "string", + label: "Time", + description: "The time for the task", + optional: true, + }, + duration: { + type: "string", + label: "Duration", + description: "The duration of the task", + optional: true, + }, + note: { + type: "string", + label: "Note", + description: "Additional notes for the task", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.salespype.com"; + }, + async _makeRequest(opts = {}) { + const { + $, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_token}`, + }, + ...otherOpts, + }); + }, + async createContact(opts = {}) { + const { + firstName, + lastName, + email, + address, + city, + state, + zip, + country, + companyName, + birthDate, + } = opts; + const data = { + first_name: firstName, + last_name: lastName, + email, + ...(address && { + address, + }), + ...(city && { + city, + }), + ...(state && { + state, + }), + ...(zip && { + zip, + }), + ...(country && { + country, + }), + ...(companyName && { + company_name: companyName, + }), + ...(birthDate && { + birthdate: birthDate, + }), + }; + return this._makeRequest({ + method: "POST", + path: "/contacts", + data, + }); + }, + async updateContact(opts = {}) { + const { + contactId, + firstName, + lastName, + email, + address, + city, + state, + zip, + country, + companyName, + birthDate, + } = opts; + const data = { + first_name: firstName, + last_name: lastName, + email, + ...(address && { + address, + }), + ...(city && { + city, + }), + ...(state && { + state, + }), + ...(zip && { + zip, + }), + ...(country && { + country, + }), + ...(companyName && { + company_name: companyName, + }), + ...(birthDate && { + birthdate: birthDate, + }), + }; + return this._makeRequest({ + method: "PUT", + path: `/contacts/${contactId}`, + data, + }); + }, + async createCampaign(opts = {}) { + const data = {}; + return this._makeRequest({ + method: "POST", + path: "/campaigns", + data, + }); + }, + async addContactToCampaign(opts = {}) { + const { + campaignId, contactId, + } = opts; + return this._makeRequest({ + method: "POST", + path: `/campaigns/${campaignId}/add_contact`, + data: { + contact_id: contactId, + }, + }); + }, + async createTask(opts = {}) { + const { + contactId, + task, + taskTypeId, + date, + time, + duration, + note, + } = opts; + const data = { + contact_id: contactId, + task, + task_type_id: taskTypeId, + ...(date && { + date, + }), + ...(time && { + time, + }), + ...(duration && { + duration, + }), + ...(note && { + note, + }), + }; + return this._makeRequest({ + method: "POST", + path: "/tasks", + data, + }); }, }, }; diff --git a/components/salespype/sources/contact-updated/contact-updated.mjs b/components/salespype/sources/contact-updated/contact-updated.mjs new file mode 100644 index 0000000000000..ec41d7c70adfe --- /dev/null +++ b/components/salespype/sources/contact-updated/contact-updated.mjs @@ -0,0 +1,75 @@ +import salespype from "../../salespype.app.mjs"; +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; + +export default { + key: "salespype-contact-updated", + name: "Salespype Contact Updated", + description: "Emit a new event when an existing contact is updated. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + salespype, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + contactId: { + propDefinition: [ + salespype, + "contactId", + ], + }, + }, + hooks: { + async deploy() { + const contact = await this.fetchContact(); + if (contact) { + this.$emit(contact, { + summary: `Contact Updated: ${contact.first_name} ${contact.last_name}`, + ts: new Date(contact.updated_at).getTime(), + }); + await this.db.set(`contact_${this.contactId}`, contact.updated_at); + } + }, + async activate() { + // No webhook setup required for polling source + }, + async deactivate() { + // No webhook teardown required for polling source + }, + }, + async run() { + const contact = await this.fetchContact(); + if (contact) { + const lastUpdated = await this.db.get(`contact_${this.contactId}`); + const currentUpdated = contact.updated_at; + + if (!lastUpdated || new Date(currentUpdated) > new Date(lastUpdated)) { + this.$emit(contact, { + summary: `Contact Updated: ${contact.first_name} ${contact.last_name}`, + ts: new Date(currentUpdated).getTime(), + }); + await this.db.set(`contact_${this.contactId}`, currentUpdated); + } + } + }, + methods: { + async fetchContact() { + try { + const contact = await this.salespype._makeRequest({ + method: "GET", + path: `/contacts/${this.contactId}`, + }); + return contact; + } catch (error) { + this.$emitError(error); + } + }, + }, +}; diff --git a/components/salespype/sources/new-campaign-created/new-campaign-created.mjs b/components/salespype/sources/new-campaign-created/new-campaign-created.mjs new file mode 100644 index 0000000000000..953d430da4c12 --- /dev/null +++ b/components/salespype/sources/new-campaign-created/new-campaign-created.mjs @@ -0,0 +1,80 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import salespype from "../../salespype.app.mjs"; + +export default { + key: "salespype-new-campaign-created", + name: "New Campaign Created", + description: "Emit new events when a new campaign is created. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + salespype, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + // Fetch the latest 50 campaigns and store their IDs to prevent emitting old events + const campaigns = await this.salespype._makeRequest({ + method: "GET", + path: "/campaigns", + }); + const sortedCampaigns = campaigns.sort( + (a, b) => new Date(b.created_at) - new Date(a.created_at), + ); + const recentCampaigns = sortedCampaigns.slice(0, 50); + const campaignIds = recentCampaigns.map((campaign) => campaign.id); + await this.db.set("campaignIds", campaignIds); + }, + async activate() { + // No webhook to activate + }, + async deactivate() { + // No webhook to deactivate + }, + }, + async run() { + // Fetch all campaigns + const campaigns = await this.salespype._makeRequest({ + method: "GET", + path: "/campaigns", + }); + + // Sort campaigns by creation date descending + const sortedCampaigns = campaigns.sort( + (a, b) => new Date(b.created_at) - new Date(a.created_at), + ); + + // Get the stored campaign IDs + const storedCampaignIds = (await this.db.get("campaignIds")) || []; + + // Find new campaigns that are not in the storedCampaignIds + const newCampaigns = sortedCampaigns.filter( + (campaign) => !storedCampaignIds.includes(campaign.id), + ); + + // Emit each new campaign + for (const campaign of newCampaigns) { + this.$emit( + campaign, + { + id: campaign.id, + summary: `New Campaign: ${campaign.name}`, + ts: new Date(campaign.created_at).getTime(), + }, + ); + } + + // Update the stored campaign IDs with the latest 50 + const latestCampaignIds = sortedCampaigns.slice(0, 50).map((campaign) => campaign.id); + await this.db.set("campaignIds", latestCampaignIds); + }, +}; diff --git a/components/salespype/sources/new-contact-created/new-contact-created.mjs b/components/salespype/sources/new-contact-created/new-contact-created.mjs new file mode 100644 index 0000000000000..f74dec3926072 --- /dev/null +++ b/components/salespype/sources/new-contact-created/new-contact-created.mjs @@ -0,0 +1,86 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import salespype from "../../salespype.app.mjs"; + +export default { + key: "salespype-new-contact-created", + name: "New Contact Created", + description: "Emit new event when a new contact is created. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + salespype: { + type: "app", + app: "salespype", + }, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + const contacts = await this.salespype._makeRequest({ + method: "GET", + path: "/contacts", + params: { + limit: 50, + sort: "created_at_desc", + }, + }); + + for (const contact of contacts) { + this.$emit(contact, { + id: contact.contactId, + summary: `New Contact: ${contact.firstName} ${contact.lastName}`, + ts: new Date(contact.created_at).getTime(), + }); + } + + if (contacts.length > 0) { + const lastContact = contacts[0]; + this.db.set("lastTimestamp", new Date(lastContact.created_at).getTime()); + } + }, + async activate() { + // No webhook to create + }, + async deactivate() { + // No webhook to delete + }, + }, + async run() { + const lastTimestamp = this.db.get("lastTimestamp") || 0; + + const contacts = await this.salespype._makeRequest({ + method: "GET", + path: "/contacts", + params: { + since: lastTimestamp, + limit: 50, + sort: "created_at_desc", + }, + }); + + for (const contact of contacts) { + const contactTs = new Date(contact.created_at).getTime(); + if (contactTs > lastTimestamp) { + this.$emit(contact, { + id: contact.contactId, + summary: `New Contact: ${contact.firstName} ${contact.lastName}`, + ts: contactTs, + }); + } + } + + if (contacts.length > 0) { + const newLastTimestamp = new Date(contacts[0].created_at).getTime(); + this.db.set("lastTimestamp", newLastTimestamp); + } + }, +}; From a47bd04309ba01f8c03e0f3fd07fee3f0e75f6e3 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Fri, 7 Feb 2025 14:11:37 -0500 Subject: [PATCH 2/4] new components --- .../add-contact-to-campaign.mjs | 6 +- .../actions/create-contact/create-contact.mjs | 105 +++--- .../actions/create-task/create-task.mjs | 71 ++-- components/salespype/package.json | 6 +- components/salespype/salespype.app.mjs | 313 +++++------------- components/salespype/sources/common/base.mjs | 80 +++++ .../contact-updated/contact-updated.mjs | 88 ++--- .../new-campaign-created.mjs | 86 +---- .../new-contact-created.mjs | 92 +---- 9 files changed, 316 insertions(+), 531 deletions(-) create mode 100644 components/salespype/sources/common/base.mjs diff --git a/components/salespype/actions/add-contact-to-campaign/add-contact-to-campaign.mjs b/components/salespype/actions/add-contact-to-campaign/add-contact-to-campaign.mjs index 4d42ebc37ae48..a60b85d30f8e9 100644 --- a/components/salespype/actions/add-contact-to-campaign/add-contact-to-campaign.mjs +++ b/components/salespype/actions/add-contact-to-campaign/add-contact-to-campaign.mjs @@ -1,11 +1,10 @@ import salespype from "../../salespype.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "salespype-add-contact-to-campaign", name: "Add Contact to Campaign", - description: "Adds a contact to a campaign. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Adds a contact to a campaign. [See the documentation](https://documenter.getpostman.com/view/5101444/2s93Y3u1Eb#4b2f8b3e-155d-4485-9a25-4f7d98d04b53)", + version: "0.0.1", type: "action", props: { salespype, @@ -24,6 +23,7 @@ export default { }, async run({ $ }) { const response = await this.salespype.addContactToCampaign({ + $, campaignId: this.campaignId, contactId: this.contactId, }); diff --git a/components/salespype/actions/create-contact/create-contact.mjs b/components/salespype/actions/create-contact/create-contact.mjs index 308d9467aca22..862f01822770f 100644 --- a/components/salespype/actions/create-contact/create-contact.mjs +++ b/components/salespype/actions/create-contact/create-contact.mjs @@ -1,99 +1,88 @@ import salespype from "../../salespype.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "salespype-create-contact", name: "Create Contact", - description: "Creates a new contact in Salespype. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Creates a new contact in Salespype. [See the documentation](https://documenter.getpostman.com/view/5101444/2s93Y3u1Eb#0a9f8441-c7fa-48dc-b02b-0117037d86ab)", + version: "0.0.1", type: "action", props: { - salespype: { - type: "app", - app: "salespype", - }, + salespype, firstName: { - propDefinition: [ - salespype, - "firstName", - ], + type: "string", + label: "First Name", + description: "The first name of the contact", }, lastName: { - propDefinition: [ - salespype, - "lastName", - ], + type: "string", + label: "Last Name", + description: "The last name of the contact", }, email: { - propDefinition: [ - salespype, - "email", - ], + type: "string", + label: "Email", + description: "The email address of the contact", }, address: { - propDefinition: [ - salespype, - "address", - ], + type: "string", + label: "Address", + description: "The address of the contact", optional: true, }, city: { - propDefinition: [ - salespype, - "city", - ], + type: "string", + label: "City", + description: "The city of the contact", optional: true, }, state: { - propDefinition: [ - salespype, - "state", - ], + type: "string", + label: "State", + description: "The state of the contact", optional: true, }, zip: { - propDefinition: [ - salespype, - "zip", - ], + type: "string", + label: "ZIP Code", + description: "The ZIP code of the contact", optional: true, }, country: { - propDefinition: [ - salespype, - "country", - ], + type: "string", + label: "Country", + description: "The country of the contact", optional: true, }, companyName: { - propDefinition: [ - salespype, - "companyName", - ], + type: "string", + label: "Company Name", + description: "The company name of the contact", optional: true, }, birthDate: { - propDefinition: [ - salespype, - "birthDate", - ], + type: "string", + label: "Birthdate", + description: "The birthdate of the contact", optional: true, }, }, async run({ $ }) { const contact = await this.salespype.createContact({ - firstName: this.firstName, - lastName: this.lastName, - email: this.email, - address: this.address, - city: this.city, - state: this.state, - zip: this.zip, - country: this.country, - companyName: this.companyName, - birthDate: this.birthDate, + $, + data: { + firstName: this.firstName, + lastName: this.lastName, + email: this.email, + address: this.address, + city: this.city, + state: this.state, + zip: this.zip, + country: this.country, + companyName: this.companyName, + birthDate: this.birthDate, + }, }); - $.export("$summary", `Created contact ${contact.first_name} ${contact.last_name} (${contact.email})`); + $.export("$summary", `Created contact ${this.firstName} ${this.lastName} (${this.email})`); return contact; }, }; diff --git a/components/salespype/actions/create-task/create-task.mjs b/components/salespype/actions/create-task/create-task.mjs index 53a9a94994f1e..b6bf72bc6755d 100644 --- a/components/salespype/actions/create-task/create-task.mjs +++ b/components/salespype/actions/create-task/create-task.mjs @@ -1,11 +1,10 @@ import salespype from "../../salespype.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "salespype-create-task", name: "Create Task", - description: "Creates a new task in Salespype. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Creates a new task in Salespype. [See the documentation](https://documenter.getpostman.com/view/5101444/2s93Y3u1Eb#a9c6449a-b844-465c-a342-deea01e52c3f)", + version: "0.0.1", type: "action", props: { salespype, @@ -16,10 +15,9 @@ export default { ], }, task: { - propDefinition: [ - salespype, - "task", - ], + type: "string", + label: "Task", + description: "The task description", }, taskTypeId: { propDefinition: [ @@ -28,54 +26,45 @@ export default { ], }, date: { - propDefinition: [ - salespype, - "date", - ], - optional: true, + type: "string", + label: "Date", + description: "The date for the task. E.g. `2021-02-20`", }, time: { - propDefinition: [ - salespype, - "time", - ], - optional: true, + type: "string", + label: "Time", + description: "The time for the task. E.g. `34:00:34`", }, duration: { - propDefinition: [ - salespype, - "duration", - ], - optional: true, + type: "string", + label: "Duration", + description: "The duration of the task. E.g. `34:00:34`", }, note: { - propDefinition: [ - salespype, - "note", - ], - optional: true, + type: "string", + label: "Note", + description: "Additional notes for the task", }, }, async run({ $ }) { - const taskData = { - contact_id: this.contactId, - task: this.task, - task_type_id: this.taskTypeId, - ...(this.date && { + const { + task, message, + } = await this.salespype.createTask({ + $, + contactId: this.contactId, + data: { + task: this.task, + taskTypeId: this.taskTypeId, date: this.date, - }), - ...(this.time && { time: this.time, - }), - ...(this.duration && { duration: this.duration, - }), - ...(this.note && { note: this.note, - }), - }; + }, + }); - const task = await this.salespype.createTask(taskData); + if (message) { + throw new Error(`${message}`); + } $.export("$summary", `Created task '${this.task}' with ID ${task.id}`); return task; diff --git a/components/salespype/package.json b/components/salespype/package.json index e68679187794b..375ac02fea886 100644 --- a/components/salespype/package.json +++ b/components/salespype/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/salespype", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Salespype Components", "main": "salespype.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "md5": "^2.3.0" } } diff --git a/components/salespype/salespype.app.mjs b/components/salespype/salespype.app.mjs index 1721955ef52d5..6d578194bcbe1 100644 --- a/components/salespype/salespype.app.mjs +++ b/components/salespype/salespype.app.mjs @@ -3,271 +3,140 @@ import { axios } from "@pipedream/platform"; export default { type: "app", app: "salespype", - version: "0.0.{{ts}}", propDefinitions: { contactId: { type: "string", label: "Contact ID", description: "The unique identifier of the contact", + async options({ page }) { + const { contacts } = await this.listContacts({ + params: { + page: page + 1, + }, + }); + return contacts?.map(({ + id: value, fullName: label, + }) => ({ + value, + label, + })) || []; + }, }, campaignId: { type: "string", label: "Campaign ID", description: "The unique identifier of the campaign", - }, - firstName: { - type: "string", - label: "First Name", - description: "The first name of the contact", - }, - lastName: { - type: "string", - label: "Last Name", - description: "The last name of the contact", - }, - email: { - type: "string", - label: "Email", - description: "The email address of the contact", - }, - address: { - type: "string", - label: "Address", - description: "The address of the contact", - optional: true, - }, - city: { - type: "string", - label: "City", - description: "The city of the contact", - optional: true, - }, - state: { - type: "string", - label: "State", - description: "The state of the contact", - optional: true, - }, - zip: { - type: "string", - label: "ZIP Code", - description: "The ZIP code of the contact", - optional: true, - }, - country: { - type: "string", - label: "Country", - description: "The country of the contact", - optional: true, - }, - companyName: { - type: "string", - label: "Company Name", - description: "The company name of the contact", - optional: true, - }, - birthDate: { - type: "string", - label: "Birthdate", - description: "The birthdate of the contact", - optional: true, - }, - task: { - type: "string", - label: "Task", - description: "The task description", + async options({ page }) { + const { campaigns } = await this.listCampaigns({ + params: { + page: page + 1, + }, + }); + return campaigns?.map(({ + id: value, title: label, + }) => ({ + value, + label, + })) || []; + }, }, taskTypeId: { type: "string", label: "Task Type ID", description: "The unique identifier of the task type", - }, - date: { - type: "string", - label: "Date", - description: "The date for the task", - optional: true, - }, - time: { - type: "string", - label: "Time", - description: "The time for the task", - optional: true, - }, - duration: { - type: "string", - label: "Duration", - description: "The duration of the task", - optional: true, - }, - note: { - type: "string", - label: "Note", - description: "Additional notes for the task", - optional: true, + async options() { + const { taskTypes } = await this.listTaskTypes(); + return taskTypes?.map(({ + id: value, task: label, + }) => ({ + value, + label, + })) || []; + }, }, }, methods: { _baseUrl() { - return "https://api.salespype.com"; + return "https://api.pypepro.io/crm/v1"; }, - async _makeRequest(opts = {}) { - const { - $, method = "GET", path = "/", headers, ...otherOpts - } = opts; + _makeRequest({ + $ = this, + path, + ...otherOpts + }) { return axios($, { - method, - url: this._baseUrl() + path, + url: `${this._baseUrl()}${path}`, headers: { - ...headers, - Authorization: `Bearer ${this.$auth.api_token}`, + apikey: this.$auth.api_token, }, ...otherOpts, }); }, - async createContact(opts = {}) { - const { - firstName, - lastName, - email, - address, - city, - state, - zip, - country, - companyName, - birthDate, - } = opts; - const data = { - first_name: firstName, - last_name: lastName, - email, - ...(address && { - address, - }), - ...(city && { - city, - }), - ...(state && { - state, - }), - ...(zip && { - zip, - }), - ...(country && { - country, - }), - ...(companyName && { - company_name: companyName, - }), - ...(birthDate && { - birthdate: birthDate, - }), - }; + listContacts(opts = {}) { return this._makeRequest({ - method: "POST", - path: "/contacts", - data, + path: "/contacts/list", + ...opts, }); }, - async updateContact(opts = {}) { - const { - contactId, - firstName, - lastName, - email, - address, - city, - state, - zip, - country, - companyName, - birthDate, - } = opts; - const data = { - first_name: firstName, - last_name: lastName, - email, - ...(address && { - address, - }), - ...(city && { - city, - }), - ...(state && { - state, - }), - ...(zip && { - zip, - }), - ...(country && { - country, - }), - ...(companyName && { - company_name: companyName, - }), - ...(birthDate && { - birthdate: birthDate, - }), - }; + listCampaigns(opts = {}) { + return this._makeRequest({ + path: "/campaigns", + ...opts, + }); + }, + listTaskTypes(opts = {}) { return this._makeRequest({ - method: "PUT", - path: `/contacts/${contactId}`, - data, + path: "/tasks/types", + ...opts, }); }, - async createCampaign(opts = {}) { - const data = {}; + createContact(opts = {}) { return this._makeRequest({ method: "POST", - path: "/campaigns", - data, + path: "/contacts", + ...opts, }); }, - async addContactToCampaign(opts = {}) { - const { - campaignId, contactId, - } = opts; + addContactToCampaign({ + campaignId, contactId, ...opts + }) { return this._makeRequest({ method: "POST", - path: `/campaigns/${campaignId}/add_contact`, - data: { - contact_id: contactId, - }, + path: `/campaigns/${campaignId}/contacts/${contactId}`, + ...opts, }); }, - async createTask(opts = {}) { - const { - contactId, - task, - taskTypeId, - date, - time, - duration, - note, - } = opts; - const data = { - contact_id: contactId, - task, - task_type_id: taskTypeId, - ...(date && { - date, - }), - ...(time && { - time, - }), - ...(duration && { - duration, - }), - ...(note && { - note, - }), - }; + createTask({ + contactId, ...opts + }) { return this._makeRequest({ method: "POST", - path: "/tasks", - data, + path: `/tasks/contacts/${contactId}`, + ...opts, }); }, + async *paginate({ + fn, params, resourceKey, max, + }) { + params = { + ...params, + page: 0, + }; + let totalPages, count = 0;; + do { + params.page++; + const results = await fn({ + params, + }); + const items = results[resourceKey]; + for (const item of items) { + yield item; + if (max && ++count >= max) { + return; + } + } + totalPages = results.totalPages; + } while (params.page < totalPages); + }, }, }; diff --git a/components/salespype/sources/common/base.mjs b/components/salespype/sources/common/base.mjs new file mode 100644 index 0000000000000..1d3cc9ff79cf4 --- /dev/null +++ b/components/salespype/sources/common/base.mjs @@ -0,0 +1,80 @@ +import salespype from "../../salespype.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + salespype, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLast() { + return this.db.get("last") || 0; + }, + _setLast(last) { + this.db.set("last", last); + }, + async processEvent(max) { + const last = this._getLast(); + let maxLast = last; + const resourceFn = this.getResourceFn(); + + const items = this.salespype.paginate({ + fn: resourceFn, + params: this.getParams(), + resourceKey: this.getResourceKey(), + max, + }); + + const results = []; + for await (const item of items) { + const fieldValue = this.getFieldValue(item); + if (fieldValue >= last) { + results.push(item); + maxLast = Math.max(maxLast, fieldValue); + } + } + + if (!results.length) { + return; + } + + this._setLast(maxLast); + + results.reverse().forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + getParams() { + return { + order: "desc", + }; + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + getResourceKey() { + throw new Error("getResourceKey is not implemented"); + }, + getFieldValue() { + throw new Error("getFieldValue is not implemented"); + }, + generateMeta() { + throw new Error("generateMeta is not implemented"); + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/salespype/sources/contact-updated/contact-updated.mjs b/components/salespype/sources/contact-updated/contact-updated.mjs index ec41d7c70adfe..41b8c8c244c6a 100644 --- a/components/salespype/sources/contact-updated/contact-updated.mjs +++ b/components/salespype/sources/contact-updated/contact-updated.mjs @@ -1,75 +1,35 @@ -import salespype from "../../salespype.app.mjs"; -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; +import common from "../common/base.mjs"; +import md5 from "md5"; export default { + ...common, key: "salespype-contact-updated", - name: "Salespype Contact Updated", - description: "Emit a new event when an existing contact is updated. [See the documentation]()", - version: "0.0.{{ts}}", + name: "Contact Updated", + description: "Emit new event when an existing contact is updated. [See the documentation](https://documenter.getpostman.com/view/5101444/2s93Y3u1Eb#e8b86665-e0b3-4c2e-9bd0-05fcf81f6c48)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - salespype, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - contactId: { - propDefinition: [ - salespype, - "contactId", - ], - }, - }, - hooks: { - async deploy() { - const contact = await this.fetchContact(); - if (contact) { - this.$emit(contact, { - summary: `Contact Updated: ${contact.first_name} ${contact.last_name}`, - ts: new Date(contact.updated_at).getTime(), - }); - await this.db.set(`contact_${this.contactId}`, contact.updated_at); - } + methods: { + ...common.methods, + getResourceFn() { + return this.salespype.listContacts; }, - async activate() { - // No webhook setup required for polling source + getResourceKey() { + return "contacts"; }, - async deactivate() { - // No webhook teardown required for polling source + getFieldValue(contact) { + return Date.parse(contact.updatedAt); }, - }, - async run() { - const contact = await this.fetchContact(); - if (contact) { - const lastUpdated = await this.db.get(`contact_${this.contactId}`); - const currentUpdated = contact.updated_at; - - if (!lastUpdated || new Date(currentUpdated) > new Date(lastUpdated)) { - this.$emit(contact, { - summary: `Contact Updated: ${contact.first_name} ${contact.last_name}`, - ts: new Date(currentUpdated).getTime(), - }); - await this.db.set(`contact_${this.contactId}`, currentUpdated); - } - } - }, - methods: { - async fetchContact() { - try { - const contact = await this.salespype._makeRequest({ - method: "GET", - path: `/contacts/${this.contactId}`, - }); - return contact; - } catch (error) { - this.$emitError(error); - } + generateMeta(contact) { + return { + id: md5(JSON.stringify({ + ...contact, + createdAt: undefined, + updatedAt: undefined, + })), + summary: `Contact Updated: ${contact.id}`, + ts: Date.parse(contact.updatedAt), + }; }, }, }; diff --git a/components/salespype/sources/new-campaign-created/new-campaign-created.mjs b/components/salespype/sources/new-campaign-created/new-campaign-created.mjs index 953d430da4c12..d64221b28e1a7 100644 --- a/components/salespype/sources/new-campaign-created/new-campaign-created.mjs +++ b/components/salespype/sources/new-campaign-created/new-campaign-created.mjs @@ -1,80 +1,30 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import salespype from "../../salespype.app.mjs"; +import common from "../common/base.mjs"; export default { + ...common, key: "salespype-new-campaign-created", name: "New Campaign Created", - description: "Emit new events when a new campaign is created. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Emit new events when a new campaign is created. [See the documentation](https://documenter.getpostman.com/view/5101444/2s93Y3u1Eb#f6b1d9d0-0251-4b6c-b70e-699b35f59a39)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - salespype, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + methods: { + ...common.methods, + getResourceFn() { + return this.salespype.listCampaigns; }, - }, - hooks: { - async deploy() { - // Fetch the latest 50 campaigns and store their IDs to prevent emitting old events - const campaigns = await this.salespype._makeRequest({ - method: "GET", - path: "/campaigns", - }); - const sortedCampaigns = campaigns.sort( - (a, b) => new Date(b.created_at) - new Date(a.created_at), - ); - const recentCampaigns = sortedCampaigns.slice(0, 50); - const campaignIds = recentCampaigns.map((campaign) => campaign.id); - await this.db.set("campaignIds", campaignIds); + getResourceKey() { + return "campaigns"; }, - async activate() { - // No webhook to activate + getFieldValue(campaign) { + return campaign.id; }, - async deactivate() { - // No webhook to deactivate + generateMeta(campaign) { + return { + id: campaign.id, + summary: `New Campaign: ${campaign.id}`, + ts: Date.now(), + }; }, }, - async run() { - // Fetch all campaigns - const campaigns = await this.salespype._makeRequest({ - method: "GET", - path: "/campaigns", - }); - - // Sort campaigns by creation date descending - const sortedCampaigns = campaigns.sort( - (a, b) => new Date(b.created_at) - new Date(a.created_at), - ); - - // Get the stored campaign IDs - const storedCampaignIds = (await this.db.get("campaignIds")) || []; - - // Find new campaigns that are not in the storedCampaignIds - const newCampaigns = sortedCampaigns.filter( - (campaign) => !storedCampaignIds.includes(campaign.id), - ); - - // Emit each new campaign - for (const campaign of newCampaigns) { - this.$emit( - campaign, - { - id: campaign.id, - summary: `New Campaign: ${campaign.name}`, - ts: new Date(campaign.created_at).getTime(), - }, - ); - } - - // Update the stored campaign IDs with the latest 50 - const latestCampaignIds = sortedCampaigns.slice(0, 50).map((campaign) => campaign.id); - await this.db.set("campaignIds", latestCampaignIds); - }, }; diff --git a/components/salespype/sources/new-contact-created/new-contact-created.mjs b/components/salespype/sources/new-contact-created/new-contact-created.mjs index f74dec3926072..bfb306a0ccf40 100644 --- a/components/salespype/sources/new-contact-created/new-contact-created.mjs +++ b/components/salespype/sources/new-contact-created/new-contact-created.mjs @@ -1,86 +1,30 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import salespype from "../../salespype.app.mjs"; +import common from "../common/base.mjs"; export default { + ...common, key: "salespype-new-contact-created", name: "New Contact Created", - description: "Emit new event when a new contact is created. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Emit new event when a new contact is created. [See the documentation](https://documenter.getpostman.com/view/5101444/2s93Y3u1Eb#e8b86665-e0b3-4c2e-9bd0-05fcf81f6c48)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - salespype: { - type: "app", - app: "salespype", + methods: { + ...common.methods, + getResourceFn() { + return this.salespype.listContacts; }, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + getResourceKey() { + return "contacts"; }, - }, - hooks: { - async deploy() { - const contacts = await this.salespype._makeRequest({ - method: "GET", - path: "/contacts", - params: { - limit: 50, - sort: "created_at_desc", - }, - }); - - for (const contact of contacts) { - this.$emit(contact, { - id: contact.contactId, - summary: `New Contact: ${contact.firstName} ${contact.lastName}`, - ts: new Date(contact.created_at).getTime(), - }); - } - - if (contacts.length > 0) { - const lastContact = contacts[0]; - this.db.set("lastTimestamp", new Date(lastContact.created_at).getTime()); - } - }, - async activate() { - // No webhook to create + getFieldValue(contact) { + return Date.parse(contact.createdAt); }, - async deactivate() { - // No webhook to delete + generateMeta(contact) { + return { + id: contact.id, + summary: `New Contact Created: ${contact.id}`, + ts: Date.parse(contact.createdAt), + }; }, }, - async run() { - const lastTimestamp = this.db.get("lastTimestamp") || 0; - - const contacts = await this.salespype._makeRequest({ - method: "GET", - path: "/contacts", - params: { - since: lastTimestamp, - limit: 50, - sort: "created_at_desc", - }, - }); - - for (const contact of contacts) { - const contactTs = new Date(contact.created_at).getTime(); - if (contactTs > lastTimestamp) { - this.$emit(contact, { - id: contact.contactId, - summary: `New Contact: ${contact.firstName} ${contact.lastName}`, - ts: contactTs, - }); - } - } - - if (contacts.length > 0) { - const newLastTimestamp = new Date(contacts[0].created_at).getTime(); - this.db.set("lastTimestamp", newLastTimestamp); - } - }, }; From 17529780964686a0c7e6c2d948da19fe278eff1b Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Fri, 7 Feb 2025 14:13:01 -0500 Subject: [PATCH 3/4] pnpm-lock.yaml --- pnpm-lock.yaml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c923106a30908..3b6bdb2fd55e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1891,11 +1891,9 @@ importers: components/clarify: {} - components/claris_filemaker_server_admin_api: - specifiers: {} + components/claris_filemaker_server_admin_api: {} - components/claris_filemaker_server_data_api: - specifiers: {} + components/claris_filemaker_server_data_api: {} components/claris_filemaker_server_odata_api: {} @@ -6810,8 +6808,7 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/morgen: - specifiers: {} + components/morgen: {} components/morningmate: dependencies: @@ -9199,7 +9196,14 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/salespype: {} + components/salespype: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 + md5: + specifier: ^2.3.0 + version: 2.3.0 components/salestown: {} From 0f2db189f633a18254bfe0cbcc9c10692ae659c0 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Fri, 7 Feb 2025 14:26:31 -0500 Subject: [PATCH 4/4] remove duplicate semicolon --- components/salespype/salespype.app.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/salespype/salespype.app.mjs b/components/salespype/salespype.app.mjs index 6d578194bcbe1..e296df1766732 100644 --- a/components/salespype/salespype.app.mjs +++ b/components/salespype/salespype.app.mjs @@ -122,7 +122,7 @@ export default { ...params, page: 0, }; - let totalPages, count = 0;; + let totalPages, count = 0; do { params.page++; const results = await fn({