diff --git a/components/zendesk_sell/actions/create-contact/create-contact.mjs b/components/zendesk_sell/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..d4e2c8fd37a18 --- /dev/null +++ b/components/zendesk_sell/actions/create-contact/create-contact.mjs @@ -0,0 +1,91 @@ +import zendeskSell from "../../zendesk_sell.app.mjs"; + +export default { + key: "zendesk_sell-create-contact", + name: "Create Contact", + description: "Creates a new contact. [See the documentation](https://developer.zendesk.com/api-reference/sales-crm/resources/contacts/#create-a-contact).", + type: "action", + version: "0.0.1", + props: { + zendeskSell, + isOrganization: { + propDefinition: [ + zendeskSell, + "isOrganization", + ], + reloadProps: true, + }, + status: { + propDefinition: [ + zendeskSell, + "status", + ], + }, + title: { + propDefinition: [ + zendeskSell, + "title", + ], + }, + description: { + propDefinition: [ + zendeskSell, + "description", + ], + }, + email: { + propDefinition: [ + zendeskSell, + "email", + ], + }, + phone: { + propDefinition: [ + zendeskSell, + "phone", + ], + }, + }, + async additionalProps() { + const props = {}; + if (this.isOrganization) { + props.name = { + type: "string", + label: "Name", + description: "Name of the contact", + }; + } else { + props.firstName = { + type: "string", + label: "First Name", + description: "First name of the contact", + }; + props.lastName = { + type: "string", + label: "Last Name", + description: "Last name of the contact", + }; + } + return props; + }, + async run({ $ }) { + const response = await this.zendeskSell.createContact({ + $, + data: { + data: { + is_organization: this.isOrganization, + name: this.name, + first_name: this.firstName, + last_name: this.lastName, + customer_status: this.status, + title: this.title, + description: this.description, + email: this.email, + phone: this.phone, + }, + }, + }); + $.export("$summary", `Successfully created contact with ID ${response.data.id}`); + return response; + }, +}; diff --git a/components/zendesk_sell/actions/create-lead/create-lead.mjs b/components/zendesk_sell/actions/create-lead/create-lead.mjs new file mode 100644 index 0000000000000..42a855752168f --- /dev/null +++ b/components/zendesk_sell/actions/create-lead/create-lead.mjs @@ -0,0 +1,96 @@ +import zendeskSell from "../../zendesk_sell.app.mjs"; + +export default { + key: "zendesk_sell-create-lead", + name: "Create Lead", + description: "Creates a new lead. [See the documentation](https://developer.zendesk.com/api-reference/sales-crm/resources/leads/#create-a-lead).", + type: "action", + version: "0.0.1", + props: { + zendeskSell, + isOrganization: { + propDefinition: [ + zendeskSell, + "isOrganization", + ], + description: "Indicator of whether or not this lead refers to an organization or an individual", + reloadProps: true, + }, + status: { + propDefinition: [ + zendeskSell, + "status", + ], + description: "The status of the lead", + }, + title: { + propDefinition: [ + zendeskSell, + "title", + ], + description: "The lead’s job title", + }, + description: { + propDefinition: [ + zendeskSell, + "description", + ], + description: "The lead’s description", + }, + email: { + propDefinition: [ + zendeskSell, + "email", + ], + description: "The lead’s email address", + }, + phone: { + propDefinition: [ + zendeskSell, + "phone", + ], + description: "The lead’s phone number", + }, + }, + async additionalProps() { + const props = {}; + if (this.isOrganization) { + props.name = { + type: "string", + label: "Name", + description: "Name of the lead", + }; + } else { + props.firstName = { + type: "string", + label: "First Name", + description: "First name of the lead", + }; + props.lastName = { + type: "string", + label: "Last Name", + description: "Last name of the lead", + }; + } + return props; + }, + async run({ $ }) { + const response = await this.zendeskSell.createLead({ + $, + data: { + data: { + first_name: this.firstName, + last_name: this.lastName, + organization_name: this.name, + status: this.status, + title: this.title, + description: this.description, + email: this.email, + phone: this.phone, + }, + }, + }); + $.export("$summary", `Successfully created lead with ID ${response.data.id}`); + return response; + }, +}; diff --git a/components/zendesk_sell/actions/create-task/create-task.mjs b/components/zendesk_sell/actions/create-task/create-task.mjs new file mode 100644 index 0000000000000..dac6db39968b2 --- /dev/null +++ b/components/zendesk_sell/actions/create-task/create-task.mjs @@ -0,0 +1,86 @@ +import zendeskSell from "../../zendesk_sell.app.mjs"; + +export default { + key: "zendesk_sell-create-task", + name: "Create Task", + description: "Creates a new task. [See the documentation](https://developer.zendesk.com/api-reference/sales-crm/resources/tasks/#create-a-task).", + type: "action", + version: "0.0.1", + props: { + zendeskSell, + resourceType: { + type: "string", + label: "Resource Type", + description: "Name of the resource type the task is attached to", + options: [ + "contact", + "lead", + "deal", + ], + reloadProps: true, + }, + contactId: { + propDefinition: [ + zendeskSell, + "contactId", + ], + hidden: true, + }, + leadId: { + propDefinition: [ + zendeskSell, + "leadId", + ], + hidden: true, + }, + dealId: { + propDefinition: [ + zendeskSell, + "dealId", + ], + hidden: true, + }, + content: { + type: "string", + label: "Content", + description: "Content of the task", + }, + completed: { + type: "boolean", + label: "Completed", + description: "Indicator of whether the task is completed or not", + optional: true, + }, + dueDate: { + type: "string", + label: "Due Date", + description: "Date and time the task is due in UTC (ISO8601 format)", + optional: true, + }, + }, + async additionalProps(props) { + props.contactId.hidden = this.resourceType !== "contact"; + props.leadId.hidden = this.resourceType !== "lead"; + props.dealId.hidden = this.resourceType !== "deal"; + return {}; + }, + async run({ $ }) { + const response = await this.zendeskSell.createTask({ + $, + data: { + data: { + resource_type: this.resourceType, + resource_id: this.resourceType === "contact" + ? this.contactId + : this.resourceType === "lead" + ? this.leadId + : this.dealId, + content: this.content, + completed: this.completed, + due_date: this.dueDate, + }, + }, + }); + return response; + }, +}; diff --git a/components/zendesk_sell/package.json b/components/zendesk_sell/package.json index dc46f54d86d38..c445f120a18a7 100644 --- a/components/zendesk_sell/package.json +++ b/components/zendesk_sell/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/zendesk_sell", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Zendesk Sell Components", "main": "zendesk_sell.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/zendesk_sell/sources/common/base.mjs b/components/zendesk_sell/sources/common/base.mjs new file mode 100644 index 0000000000000..97b9a92952d86 --- /dev/null +++ b/components/zendesk_sell/sources/common/base.mjs @@ -0,0 +1,86 @@ +import zendeskSell from "../../zendesk_sell.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + zendeskSell, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs") || 0; + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + getTsField() { + return "created_at"; + }, + getParams() { + return { + sort_by: `${this.getTsField()}:desc`, + }; + }, + generateMeta(item) { + return { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item[this.getTsField()]), + }; + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + getSummary() { + throw new Error("getSummary is not implemented"); + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + const fn = this.getResourceFn(); + const params = this.getParams(); + const tsField = this.getTsField(); + + const results = this.zendeskSell.paginate({ + fn, + params, + max, + }); + + const items = []; + for await (const result of results) { + const { data: item } = result; + const ts = Date.parse(item[tsField]); + if (ts >= lastTs) { + items.push(item); + } else { + break; + } + } + + if (!items?.length) { + return; + } + + this._setLastTs(Date.parse(items[0][tsField])); + + items.forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/zendesk_sell/sources/new-contact-created/new-contact-created.mjs b/components/zendesk_sell/sources/new-contact-created/new-contact-created.mjs new file mode 100644 index 0000000000000..f9b978d665624 --- /dev/null +++ b/components/zendesk_sell/sources/new-contact-created/new-contact-created.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "zendesk_sell-new-contact-created", + name: "New Contact Created", + description: "Emit new event when a new contact is created in Zendesk Sell.", + type: "source", + version: "0.0.1", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.zendeskSell.listContacts; + }, + getSummary(contact) { + return `New Contact ID: ${contact.id}`; + }, + }, +}; diff --git a/components/zendesk_sell/sources/new-deal-created/new-deal-created.mjs b/components/zendesk_sell/sources/new-deal-created/new-deal-created.mjs new file mode 100644 index 0000000000000..317231798499c --- /dev/null +++ b/components/zendesk_sell/sources/new-deal-created/new-deal-created.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "zendesk_sell-new-deal-created", + name: "New Deal Created", + description: "Emit new event when a new deal is created in Zendesk Sell.", + type: "source", + version: "0.0.1", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.zendeskSell.listDeals; + }, + getSummary(deal) { + return `New Deal ID: ${deal.id}`; + }, + }, +}; diff --git a/components/zendesk_sell/sources/new-lead-created/new-lead-created.mjs b/components/zendesk_sell/sources/new-lead-created/new-lead-created.mjs new file mode 100644 index 0000000000000..7af498231679a --- /dev/null +++ b/components/zendesk_sell/sources/new-lead-created/new-lead-created.mjs @@ -0,0 +1,20 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "zendesk_sell-new-lead-created", + name: "New Lead Created", + description: "Emit new event when a new lead is created in Zendesk Sell.", + type: "source", + version: "0.0.1", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.zendeskSell.listLeads; + }, + getSummary(lead) { + return `New Lead ID: ${lead.id}`; + }, + }, +}; diff --git a/components/zendesk_sell/zendesk_sell.app.mjs b/components/zendesk_sell/zendesk_sell.app.mjs index ef61a06ef79c3..09c30bf5a7820 100644 --- a/components/zendesk_sell/zendesk_sell.app.mjs +++ b/components/zendesk_sell/zendesk_sell.app.mjs @@ -1,11 +1,173 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "zendesk_sell", - propDefinitions: {}, + propDefinitions: { + contactId: { + type: "string", + label: "Contact ID", + description: "Identifier of a contact", + async options({ page }) { + const { items } = await this.listContacts({ + page: page + 1, + }); + return items?.map(({ data }) => ({ + value: data.id, + label: data.name || (`${data.first_name} ${data.last_name}`).trim(), + })) || []; + }, + }, + leadId: { + type: "string", + label: "Lead ID", + description: "Identifier of a lead", + async options({ page }) { + const { items } = await this.listLeads({ + page: page + 1, + }); + return items?.map(({ data }) => ({ + value: data.id, + label: data.organization_name || (`${data.first_name} ${data.last_name}`).trim(), + })) || []; + }, + }, + dealId: { + type: "string", + label: "Deal ID", + description: "Identifier of a deal", + async options({ page }) { + const { items } = await this.listDeals({ + page: page + 1, + }); + return items?.map(({ data }) => ({ + value: data.id, + label: data.name, + })) || []; + }, + }, + isOrganization: { + type: "boolean", + label: "Is Organization", + description: "Indicator of whether or not this contact refers to an organization or an individual", + }, + status: { + type: "string", + label: "Status", + description: "The customer status of the contact", + options: [ + "none", + "current", + "past", + ], + optional: true, + }, + title: { + type: "string", + label: "Title", + description: "The contact’s job title", + optional: true, + }, + description: { + type: "string", + label: "Description", + description: "The contact’s description", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "The contact’s email address", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "The contact’s phone number", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.getbase.com/v2"; + }, + _makeRequest({ + $ = this, + path, + ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + accept: "application/json", + }, + ...opts, + }); + }, + listContacts(opts = {}) { + return this._makeRequest({ + path: "/contacts", + ...opts, + }); + }, + listLeads(opts = {}) { + return this._makeRequest({ + path: "/leads", + ...opts, + }); + }, + listDeals(opts = {}) { + return this._makeRequest({ + path: "/deals", + ...opts, + }); + }, + createContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contacts", + ...opts, + }); + }, + createLead(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/leads", + ...opts, + }); + }, + createTask(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/tasks", + ...opts, + }); + }, + async *paginate({ + fn, + params, + max, + }) { + params = { + ...params, + per_page: 100, + page: 1, + }; + let total, count = 0; + do { + const { items } = await fn({ + params, + }); + for (const item of items) { + yield item; + if (max && ++count >= max) { + return; + } + } + total = items?.length; + params.page++; + } while (total); }, }, -}; \ No newline at end of file +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 219682069cdfe..2260763fbcbe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11703,7 +11703,10 @@ importers: crypto: 1.0.1 components/zendesk_sell: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 components/zenkit: specifiers: @@ -13302,6 +13305,55 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sso-oidc/3.600.0_tdq3komn4zwyd65w7klbptsu34: + resolution: {integrity: sha512-7+I8RWURGfzvChyNQSyj5/tKrqRbzRl7H+BnTOf/4Vsw1nFOi5ROhlhD4X/Y0QCTacxnaoNcIrqnY7uGGvVRzw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/core': 3.598.0 + '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 + '@aws-sdk/middleware-host-header': 3.598.0 + '@aws-sdk/middleware-logger': 3.598.0 + '@aws-sdk/middleware-recursion-detection': 3.598.0 + '@aws-sdk/middleware-user-agent': 3.598.0 + '@aws-sdk/region-config-resolver': 3.598.0 + '@aws-sdk/types': 3.598.0 + '@aws-sdk/util-endpoints': 3.598.0 + '@aws-sdk/util-user-agent-browser': 3.598.0 + '@aws-sdk/util-user-agent-node': 3.598.0 + '@smithy/config-resolver': 3.0.3 + '@smithy/core': 2.2.3 + '@smithy/fetch-http-handler': 3.2.1 + '@smithy/hash-node': 3.0.2 + '@smithy/invalid-dependency': 3.0.2 + '@smithy/middleware-content-length': 3.0.2 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-retry': 3.0.6 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.3 + '@smithy/node-http-handler': 3.1.2 + '@smithy/protocol-http': 4.0.3 + '@smithy/smithy-client': 3.1.6 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.6 + '@smithy/util-defaults-mode-node': 3.0.6 + '@smithy/util-endpoints': 2.0.3 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.2 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sts' + - aws-crt + dev: false + /@aws-sdk/client-sso/3.423.0: resolution: {integrity: sha512-znIufHkwhCIePgaYciIs3x/+BpzR57CZzbCKHR9+oOvGyufEPPpUT5bFLvbwTgfiVkTjuk6sG/ES3U5Bc+xtrA==} engines: {node: '>=14.0.0'} @@ -13537,55 +13589,7 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0 - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.3 - '@smithy/core': 2.2.3 - '@smithy/fetch-http-handler': 3.2.1 - '@smithy/hash-node': 3.0.2 - '@smithy/invalid-dependency': 3.0.2 - '@smithy/middleware-content-length': 3.0.2 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.6 - '@smithy/middleware-serde': 3.0.3 - '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.2 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.6 - '@smithy/types': 3.3.0 - '@smithy/url-parser': 3.0.3 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.6 - '@smithy/util-defaults-mode-node': 3.0.6 - '@smithy/util-endpoints': 2.0.3 - '@smithy/util-middleware': 3.0.3 - '@smithy/util-retry': 3.0.2 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - transitivePeerDependencies: - - aws-crt - dev: false - - /@aws-sdk/client-sts/3.600.0_dseaa2p5u2yk67qiepewcq3hkq: - resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} - engines: {node: '>=16.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0 + '@aws-sdk/client-sso-oidc': 3.600.0_tdq3komn4zwyd65w7klbptsu34 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 '@aws-sdk/middleware-host-header': 3.598.0 @@ -13624,7 +13628,6 @@ packages: '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - aws-crt dev: false @@ -17971,7 +17974,7 @@ packages: '@aws-sdk/client-sns': 3.423.0 '@aws-sdk/client-sqs': 3.423.0 '@aws-sdk/client-ssm': 3.423.0 - '@aws-sdk/client-sts': 3.600.0_dseaa2p5u2yk67qiepewcq3hkq + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/s3-request-presigner': 3.609.0 '@pipedream/helper_functions': 0.3.12 '@pipedream/platform': 1.6.6 @@ -22896,7 +22899,7 @@ packages: /axios/1.7.5: resolution: {integrity: sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==} dependencies: - follow-redirects: 1.15.6 + follow-redirects: 1.15.9 form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -23978,7 +23981,7 @@ packages: dev: false /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} /concat-stream/2.0.0: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} @@ -36665,7 +36668,7 @@ packages: dev: false /verror/1.10.0: - resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=} + resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} dependencies: assert-plus: 1.0.0