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..a60b85d30f8e9 --- /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"; + +export default { + key: "salespype-add-contact-to-campaign", + name: "Add Contact to Campaign", + 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, + 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..862f01822770f --- /dev/null +++ b/components/salespype/actions/create-contact/create-contact.mjs @@ -0,0 +1,88 @@ +import salespype from "../../salespype.app.mjs"; + +export default { + key: "salespype-create-contact", + name: "Create Contact", + 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, + 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, + }, + }, + async run({ $ }) { + const contact = await this.salespype.createContact({ + $, + 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 ${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 new file mode 100644 index 0000000000000..b6bf72bc6755d --- /dev/null +++ b/components/salespype/actions/create-task/create-task.mjs @@ -0,0 +1,72 @@ +import salespype from "../../salespype.app.mjs"; + +export default { + key: "salespype-create-task", + name: "Create Task", + 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, + contactId: { + propDefinition: [ + salespype, + "contactId", + ], + }, + task: { + type: "string", + label: "Task", + description: "The task description", + }, + taskTypeId: { + propDefinition: [ + salespype, + "taskTypeId", + ], + }, + date: { + type: "string", + label: "Date", + description: "The date for the task. E.g. `2021-02-20`", + }, + time: { + type: "string", + label: "Time", + description: "The time for the task. E.g. `34:00:34`", + }, + duration: { + type: "string", + label: "Duration", + description: "The duration of the task. E.g. `34:00:34`", + }, + note: { + type: "string", + label: "Note", + description: "Additional notes for the task", + }, + }, + async run({ $ }) { + const { + task, message, + } = await this.salespype.createTask({ + $, + contactId: this.contactId, + data: { + task: this.task, + taskTypeId: this.taskTypeId, + date: this.date, + time: this.time, + duration: this.duration, + note: this.note, + }, + }); + + 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 26aa36269108c..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" } -} \ No newline at end of file +} diff --git a/components/salespype/salespype.app.mjs b/components/salespype/salespype.app.mjs index ffaa86df13cb0..e296df1766732 100644 --- a/components/salespype/salespype.app.mjs +++ b/components/salespype/salespype.app.mjs @@ -1,11 +1,142 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "salespype", - propDefinitions: {}, + 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", + 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", + async options() { + const { taskTypes } = await this.listTaskTypes(); + return taskTypes?.map(({ + id: value, task: label, + }) => ({ + value, + label, + })) || []; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.pypepro.io/crm/v1"; + }, + _makeRequest({ + $ = this, + path, + ...otherOpts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + apikey: this.$auth.api_token, + }, + ...otherOpts, + }); + }, + listContacts(opts = {}) { + return this._makeRequest({ + path: "/contacts/list", + ...opts, + }); + }, + listCampaigns(opts = {}) { + return this._makeRequest({ + path: "/campaigns", + ...opts, + }); + }, + listTaskTypes(opts = {}) { + return this._makeRequest({ + path: "/tasks/types", + ...opts, + }); + }, + createContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contacts", + ...opts, + }); + }, + addContactToCampaign({ + campaignId, contactId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/campaigns/${campaignId}/contacts/${contactId}`, + ...opts, + }); + }, + createTask({ + contactId, ...opts + }) { + return this._makeRequest({ + method: "POST", + 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 new file mode 100644 index 0000000000000..41b8c8c244c6a --- /dev/null +++ b/components/salespype/sources/contact-updated/contact-updated.mjs @@ -0,0 +1,35 @@ +import common from "../common/base.mjs"; +import md5 from "md5"; + +export default { + ...common, + key: "salespype-contact-updated", + 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", + methods: { + ...common.methods, + getResourceFn() { + return this.salespype.listContacts; + }, + getResourceKey() { + return "contacts"; + }, + getFieldValue(contact) { + return Date.parse(contact.updatedAt); + }, + 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 new file mode 100644 index 0000000000000..d64221b28e1a7 --- /dev/null +++ b/components/salespype/sources/new-campaign-created/new-campaign-created.mjs @@ -0,0 +1,30 @@ +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](https://documenter.getpostman.com/view/5101444/2s93Y3u1Eb#f6b1d9d0-0251-4b6c-b70e-699b35f59a39)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.salespype.listCampaigns; + }, + getResourceKey() { + return "campaigns"; + }, + getFieldValue(campaign) { + return campaign.id; + }, + generateMeta(campaign) { + return { + id: campaign.id, + summary: `New Campaign: ${campaign.id}`, + ts: Date.now(), + }; + }, + }, +}; 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..bfb306a0ccf40 --- /dev/null +++ b/components/salespype/sources/new-contact-created/new-contact-created.mjs @@ -0,0 +1,30 @@ +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](https://documenter.getpostman.com/view/5101444/2s93Y3u1Eb#e8b86665-e0b3-4c2e-9bd0-05fcf81f6c48)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.salespype.listContacts; + }, + getResourceKey() { + return "contacts"; + }, + getFieldValue(contact) { + return Date.parse(contact.createdAt); + }, + generateMeta(contact) { + return { + id: contact.id, + summary: `New Contact Created: ${contact.id}`, + ts: Date.parse(contact.createdAt), + }; + }, + }, +}; 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: {}