From d6fa1630bc3d9b9ac8900a5de04dc8b7260d7fcd Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Mon, 14 Oct 2024 11:05:12 -0400 Subject: [PATCH 1/4] init --- .../associate-to-project.mjs | 29 +++++++ .../create-update-person.mjs | 18 ++++ .../create-update-project.mjs | 23 +++++ components/copper/copper.app.mjs | 85 ++++++++++++++++++- .../new-lead-instant/new-lead-instant.mjs | 49 +++++++++++ .../new-opportunity-instant.mjs | 59 +++++++++++++ .../new-person-instant/new-person-instant.mjs | 48 +++++++++++ 7 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 components/copper/actions/associate-to-project/associate-to-project.mjs create mode 100644 components/copper/actions/create-update-person/create-update-person.mjs create mode 100644 components/copper/actions/create-update-project/create-update-project.mjs create mode 100644 components/copper/sources/new-lead-instant/new-lead-instant.mjs create mode 100644 components/copper/sources/new-opportunity-instant/new-opportunity-instant.mjs create mode 100644 components/copper/sources/new-person-instant/new-person-instant.mjs diff --git a/components/copper/actions/associate-to-project/associate-to-project.mjs b/components/copper/actions/associate-to-project/associate-to-project.mjs new file mode 100644 index 0000000000000..0cdfe56f14e9d --- /dev/null +++ b/components/copper/actions/associate-to-project/associate-to-project.mjs @@ -0,0 +1,29 @@ +import copper from "../../copper.app.mjs"; + +export default { + key: "copper-associate-to-project", + name: "Associate to Project", + description: "Relates an existing project with an existing CRM object. Assumes the project and CRM object already exist.", + version: "0.0.{{ts}}", + type: "action", + props: { + copper, + crmId: { + propDefinition: [ + copper, + "crmId", + ], + }, + projectId: { + propDefinition: [ + copper, + "projectId", + ], + }, + }, + async run({ $ }) { + const response = await this.copper.relateProjectToCrm(this.crmId, this.projectId); + $.export("$summary", `Successfully associated Project ID ${this.projectId} with CRM ID ${this.crmId}`); + return response; + }, +}; diff --git a/components/copper/actions/create-update-person/create-update-person.mjs b/components/copper/actions/create-update-person/create-update-person.mjs new file mode 100644 index 0000000000000..7b9c53ecb360e --- /dev/null +++ b/components/copper/actions/create-update-person/create-update-person.mjs @@ -0,0 +1,18 @@ +import copper from "../../copper.app.mjs"; + +export default { + key: "copper-create-update-person", + name: "Create or Update Person", + description: "Creates a new person or updates an existing one based on the matching criteria. [See the documentation](https://developer.copper.com/)", + version: "0.0.{{ts}}", + type: "action", + props: { + copper, + personData: copper.propDefinitions.personData, + }, + async run({ $ }) { + const response = await this.copper.createOrUpdatePerson(this.personData); + $.export("$summary", `Successfully created or updated person with id: ${response.id}`); + return response; + }, +}; diff --git a/components/copper/actions/create-update-project/create-update-project.mjs b/components/copper/actions/create-update-project/create-update-project.mjs new file mode 100644 index 0000000000000..b6a4f61064050 --- /dev/null +++ b/components/copper/actions/create-update-project/create-update-project.mjs @@ -0,0 +1,23 @@ +import copper from "../../copper.app.mjs"; + +export default { + key: "copper-create-update-project", + name: "Create or Update Project", + description: "Creates a new project or updates an existing one based on the matching criteria. [See the documentation](https://developer.copper.com/)", + version: "0.0.{{ts}}", + type: "action", + props: { + copper, + projectData: { + propDefinition: [ + copper, + "projectData", + ], + }, + }, + async run({ $ }) { + const response = await this.copper.createOrUpdateProject(this.projectData); + $.export("$summary", `Successfully created or updated project ${this.projectData.name}`); + return response; + }, +}; diff --git a/components/copper/copper.app.mjs b/components/copper/copper.app.mjs index 844ee8264d5f5..fe0b1db14589b 100644 --- a/components/copper/copper.app.mjs +++ b/components/copper/copper.app.mjs @@ -1,11 +1,92 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "copper", - propDefinitions: {}, + propDefinitions: { + personData: { + type: "object", + label: "Person Data", + description: "Provide person data for creating or criteria for the update", + }, + projectData: { + type: "object", + label: "Project Data", + description: "Provide project data for creating or criteria for the update", + }, + crmId: { + type: "string", + label: "CRM ID", + description: "The ID of the CRM record", + }, + projectId: { + type: "string", + label: "Project ID", + description: "The ID of the project you wish to relate", + }, + }, methods: { - // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.copper.com"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + method = "get", + path, + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + async createOrUpdatePerson(personData) { + return this._makeRequest({ + method: "post", + path: "/person", + data: personData, + }); + }, + async createOrUpdateProject(projectData) { + return this._makeRequest({ + method: "post", + path: "/project", + data: projectData, + }); + }, + async relateProjectToCrm(crmId, projectId) { + return this._makeRequest({ + method: "post", + path: `/project/${projectId}/relate`, + data: { + crmId, + }, + }); + }, + async getNewPerson() { + return this._makeRequest({ + path: "/person/new", + }); + }, + async getNewLead() { + return this._makeRequest({ + path: "/lead/new", + }); + }, + async getNewOpportunity() { + return this._makeRequest({ + path: "/opportunity/new", + }); + }, }, }; diff --git a/components/copper/sources/new-lead-instant/new-lead-instant.mjs b/components/copper/sources/new-lead-instant/new-lead-instant.mjs new file mode 100644 index 0000000000000..77594fe8eef0c --- /dev/null +++ b/components/copper/sources/new-lead-instant/new-lead-instant.mjs @@ -0,0 +1,49 @@ +import copper from "../../copper.app.mjs"; + +export default { + key: "copper-new-lead-instant", + name: "New Lead Instant", + description: "Emits an event when a new lead is created in Copper", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + copper, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + }, + + methods: { + generateMeta(data) { + const { + id, name, created_date: createdDate, + } = data; + const ts = new Date(createdDate).getTime(); + return { + id, + summary: name, + ts, + }; + }, + }, + + async run() { + const lastRun = this.db.get("lastRun") || this.timer.intervalSeconds; + const now = Math.floor(Date.now() / 1000); + + const newLeads = await this.copper.getNewLead(); + newLeads.forEach((lead) => { + if (lead.created_date >= lastRun && lead.created_date < now) { + const meta = this.generateMeta(lead); + this.$emit(lead, meta); + } + }); + + this.db.set("lastRun", now); + }, +}; diff --git a/components/copper/sources/new-opportunity-instant/new-opportunity-instant.mjs b/components/copper/sources/new-opportunity-instant/new-opportunity-instant.mjs new file mode 100644 index 0000000000000..b4008d3bc6d5e --- /dev/null +++ b/components/copper/sources/new-opportunity-instant/new-opportunity-instant.mjs @@ -0,0 +1,59 @@ +import copper from "../../copper.app.mjs"; + +export default { + key: "copper-new-opportunity-instant", + name: "New Opportunity Instant", + description: "Emits an event when a new opportunity is created in Copper", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + copper, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + }, + methods: { + generateMeta(data) { + const { + id, name, date_created: created, + } = data; + const ts = new Date(created).getTime(); + return { + id, + summary: `New Opportunity: ${name}`, + ts, + }; + }, + }, + hooks: { + async deploy() { + const opportunities = await this.copper.getNewOpportunity(); + for (const opportunity of opportunities) { + this.$emit(opportunity, { + id: opportunity.id, + summary: `New Opportunity: ${opportunity.name}`, + ts: Date.parse(opportunity.date_created), + }); + } + }, + }, + async run() { + const lastRun = this.db.get("lastRun") || this.copper._baseUrl() + "/opportunity/new"; + const { data } = await this.copper._makeRequest({ + path: lastRun, + }); + if (data.length > 0) { + const { id: lastId } = data[data.length - 1]; + this.db.set("lastRun", lastId); + data.forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + } + }, +}; diff --git a/components/copper/sources/new-person-instant/new-person-instant.mjs b/components/copper/sources/new-person-instant/new-person-instant.mjs new file mode 100644 index 0000000000000..0bd6db672d003 --- /dev/null +++ b/components/copper/sources/new-person-instant/new-person-instant.mjs @@ -0,0 +1,48 @@ +import copper from "../../copper.app.mjs"; + +export default { + key: "copper-new-person-instant", + name: "New Person Instant", + description: "Emits a new event when a person object is newly created in Copper", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + copper, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + }, + methods: { + generateMeta(data) { + const id = data.id; + const summary = `New Person: ${data.name}`; + const ts = Date.parse(data.created_date); + return { + id, + summary, + ts, + }; + }, + }, + async run() { + const lastRun = this.db.get("lastRun") || this.timer.intervalSeconds; + const now = Math.floor(Date.now() / 1000); + const newPersons = await this.copper.getNewPerson(); + + for (const person of newPersons) { + const createdDate = Math.floor(Date.parse(person.created_date) / 1000); + + if (createdDate > lastRun) { + const meta = this.generateMeta(person); + this.$emit(person, meta); + } + } + + this.db.set("lastRun", now); + }, +}; From 8b63275d299d69b0d67222958205551faf76f12e Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Mon, 14 Oct 2024 14:32:43 -0400 Subject: [PATCH 2/4] new components --- .../associate-to-project.mjs | 40 +++- .../create-update-person.mjs | 118 +++++++++++- .../create-update-project.mjs | 75 +++++++- .../copper/actions/get-object/get-object.mjs | 36 ++++ components/copper/copper.app.mjs | 182 +++++++++++++----- components/copper/package.json | 18 ++ components/copper/sources/common/base.mjs | 58 ++++++ .../new-lead-instant/new-lead-instant.mjs | 53 ++--- .../sources/new-lead-instant/test-event.mjs | 10 + .../new-opportunity-instant.mjs | 61 ++---- .../new-opportunity-instant/test-event.mjs | 10 + .../new-person-instant/new-person-instant.mjs | 50 ++--- .../sources/new-person-instant/test-event.mjs | 10 + 13 files changed, 530 insertions(+), 191 deletions(-) create mode 100644 components/copper/actions/get-object/get-object.mjs create mode 100644 components/copper/package.json create mode 100644 components/copper/sources/common/base.mjs create mode 100644 components/copper/sources/new-lead-instant/test-event.mjs create mode 100644 components/copper/sources/new-opportunity-instant/test-event.mjs create mode 100644 components/copper/sources/new-person-instant/test-event.mjs diff --git a/components/copper/actions/associate-to-project/associate-to-project.mjs b/components/copper/actions/associate-to-project/associate-to-project.mjs index 0cdfe56f14e9d..1f9417ffb322b 100644 --- a/components/copper/actions/associate-to-project/associate-to-project.mjs +++ b/components/copper/actions/associate-to-project/associate-to-project.mjs @@ -3,27 +3,51 @@ import copper from "../../copper.app.mjs"; export default { key: "copper-associate-to-project", name: "Associate to Project", - description: "Relates an existing project with an existing CRM object. Assumes the project and CRM object already exist.", - version: "0.0.{{ts}}", + description: "Relates an existing project with an existing CRM object. [See the documentation](https://developer.copper.com/related-items/relate-an-existing-record-to-an-entity.html)", + version: "0.0.1", type: "action", props: { copper, - crmId: { + projectId: { propDefinition: [ copper, - "crmId", + "objectId", + () => ({ + objectType: "projects", + }), ], + label: "Project ID", + description: "The ID of the project you wish to relate", }, - projectId: { + objectType: { + propDefinition: [ + copper, + "objectType", + ], + }, + objectId: { propDefinition: [ copper, - "projectId", + "objectId", + (c) => ({ + objectType: c.objectType, + }), ], }, }, async run({ $ }) { - const response = await this.copper.relateProjectToCrm(this.crmId, this.projectId); - $.export("$summary", `Successfully associated Project ID ${this.projectId} with CRM ID ${this.crmId}`); + const response = await this.copper.relateProjectToCrmObject({ + $, + objectType: this.objectType, + objectId: this.objectId, + data: { + resource: { + id: this.projectId, + type: "project", + }, + }, + }); + $.export("$summary", `Successfully associated Project ID ${this.projectId} with CRM ID ${this.objectId}`); return response; }, }; diff --git a/components/copper/actions/create-update-person/create-update-person.mjs b/components/copper/actions/create-update-person/create-update-person.mjs index 7b9c53ecb360e..2e6148c4379e6 100644 --- a/components/copper/actions/create-update-person/create-update-person.mjs +++ b/components/copper/actions/create-update-person/create-update-person.mjs @@ -1,18 +1,126 @@ import copper from "../../copper.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; export default { key: "copper-create-update-person", name: "Create or Update Person", - description: "Creates a new person or updates an existing one based on the matching criteria. [See the documentation](https://developer.copper.com/)", - version: "0.0.{{ts}}", + description: "Creates a new person or updates an existing one based on email address. [See the documentation](https://developer.copper.com/people/create-a-new-person.html)", + version: "0.0.1", type: "action", props: { copper, - personData: copper.propDefinitions.personData, + email: { + type: "string", + label: "Email", + description: "Email address of the person. If email already exists, the person will be updated", + }, + name: { + type: "string", + label: "Name", + description: "Name of the contact. Required if creating a new person.", + optional: true, + }, + streetAddress: { + type: "string", + label: "Street Address", + description: "Street address of the person", + optional: true, + }, + city: { + type: "string", + label: "City", + description: "City address of the person", + optional: true, + }, + state: { + type: "string", + label: "State", + description: "State address of the person", + optional: true, + }, + postalCode: { + type: "string", + label: "Postal Code", + description: "Postal code of the person", + optional: true, + }, + country: { + type: "string", + label: "Country", + description: "Country of the person", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "Phone number of the person", + optional: true, + }, }, async run({ $ }) { - const response = await this.copper.createOrUpdatePerson(this.personData); - $.export("$summary", `Successfully created or updated person with id: ${response.id}`); + let response; + const hasAddress = this.streetAddress + || this.city + || this.state + || this.postalCode + || this.country; + const data = { + emails: [ + { + email: this.email, + category: "work", + }, + ], + name: this.name, + address: hasAddress && { + street: this.streetAddress, + city: this.city, + state: this.state, + postal_code: this.postalode, + country: this.country, + }, + phone_numbers: this.phone && [ + { + number: this.phone, + category: "work", + }, + ], + }; + + // search for the person + const person = await this.copper.listObjects({ + $, + objectType: "people", + data: { + emails: [ + this.email, + ], + }, + }); + + if (!person?.length && !this.name) { + throw new ConfigurationError(`Person with email ${this.email} not found. Name is required for creating a new person.`); + } + + // create person if not found + if (!person?.length) { + response = await this.copper.createPerson({ + $, + data, + }); + } + // update person if found + else { + response = await this.copper.updatePerson({ + $, + personId: person[0].id, + data, + }); + } + + $.export("$summary", `Successfully ${person?.length + ? "updated" + : "created"} person with ID: ${response.id}`); return response; }, }; diff --git a/components/copper/actions/create-update-project/create-update-project.mjs b/components/copper/actions/create-update-project/create-update-project.mjs index b6a4f61064050..4c2c75d0da895 100644 --- a/components/copper/actions/create-update-project/create-update-project.mjs +++ b/components/copper/actions/create-update-project/create-update-project.mjs @@ -3,21 +3,84 @@ import copper from "../../copper.app.mjs"; export default { key: "copper-create-update-project", name: "Create or Update Project", - description: "Creates a new project or updates an existing one based on the matching criteria. [See the documentation](https://developer.copper.com/)", - version: "0.0.{{ts}}", + description: "Creates a new project or updates an existing one based on the project name. [See the documentation](https://developer.copper.com/projects/create-a-new-project.html)", + version: "0.0.1", type: "action", props: { copper, - projectData: { + name: { + type: "string", + label: "Name", + description: "Name of the project. If a project with the specified name already exists, it will be updated", + }, + details: { + type: "string", + label: "Details", + description: "Description of the Project", + optional: true, + }, + assigneeId: { propDefinition: [ copper, - "projectData", + "objectId", + () => ({ + objectType: "users", + }), + ], + label: "Assignee ID", + description: "The ID of the User that will be the owner of the Project", + optional: true, + }, + status: { + propDefinition: [ + copper, + "status", + ], + }, + tags: { + propDefinition: [ + copper, + "tags", ], }, }, async run({ $ }) { - const response = await this.copper.createOrUpdateProject(this.projectData); - $.export("$summary", `Successfully created or updated project ${this.projectData.name}`); + let response; + const data = { + name: this.name, + assignee_id: this.assigneeId, + status: this.status, + tags: this.tags, + }; + + // search for the project + const project = await this.copper.listObjects({ + $, + objectType: "projects", + data: { + name: this.name, + }, + }); + + // create project if not found + if (!project?.length) { + response = await this.copper.createProject({ + $, + data, + }); + } + // update project if found + else { + response = await this.copper.updateProject({ + $, + projectId: project[0].id, + data, + }); + } + + $.export("$summary", `Successfully ${project?.length + ? "updated" + : "created"} project with ID: ${response.id}`); return response; }, }; diff --git a/components/copper/actions/get-object/get-object.mjs b/components/copper/actions/get-object/get-object.mjs new file mode 100644 index 0000000000000..bc709b8d39b74 --- /dev/null +++ b/components/copper/actions/get-object/get-object.mjs @@ -0,0 +1,36 @@ +import copper from "../../copper.app.mjs"; + +export default { + key: "copper-get-object", + name: "Get Object", + description: "Retrieves an existing CRM object. [See the documentation](https://developer.copper.com/account-and-users/fetch-user-by-id.html)", + version: "0.0.1", + type: "action", + props: { + copper, + objectType: { + propDefinition: [ + copper, + "objectType", + ], + }, + objectId: { + propDefinition: [ + copper, + "objectId", + (c) => ({ + objectType: c.objectType, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.copper.getObject({ + objectType: this.objectType, + objectId: this.objectId, + $, + }); + $.export("$summary", `Successfully retrieved CRM object with ID ${this.objectId}`); + return response; + }, +}; diff --git a/components/copper/copper.app.mjs b/components/copper/copper.app.mjs index fe0b1db14589b..58bcaf83985bb 100644 --- a/components/copper/copper.app.mjs +++ b/components/copper/copper.app.mjs @@ -4,88 +4,180 @@ export default { type: "app", app: "copper", propDefinitions: { - personData: { - type: "object", - label: "Person Data", - description: "Provide person data for creating or criteria for the update", + objectId: { + type: "string", + label: "Object ID", + description: "The ID of the CRM object", + async options({ + objectType, page, + }) { + const objects = await this.listObjects({ + objectType, + params: { + page_number: page + 1, + }, + }); + return objects?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, }, - projectData: { - type: "object", - label: "Project Data", - description: "Provide project data for creating or criteria for the update", + tags: { + type: "string[]", + label: "Tags", + description: "An array of the tags associated with the Project", + optional: true, + async options() { + const tags = await this.listTags(); + return tags?.map(({ name }) => name) || []; + }, }, - crmId: { + status: { type: "string", - label: "CRM ID", - description: "The ID of the CRM record", + label: "Status", + description: "The status of the Project", + optional: true, + options: [ + "Open", + "Completed", + ], }, - projectId: { + objectType: { type: "string", - label: "Project ID", - description: "The ID of the project you wish to relate", + label: "Object Type", + description: "The type of CRM object", + async options() { + return [ + { + label: "Lead", + value: "leads", + }, + { + label: "Person", + value: "people", + }, + { + label: "Company", + value: "companies", + }, + { + label: "Opportunity", + value: "opportunities", + }, + { + label: "Project", + value: "projects", + }, + { + label: "Task", + value: "tasks", + }, + ]; + }, }, }, methods: { - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { - return "https://api.copper.com"; + return "https://api.copper.com/developer_api/v1"; }, - async _makeRequest(opts = {}) { + _makeRequest(opts = {}) { const { $ = this, - method = "get", path, - headers, ...otherOpts } = opts; return axios($, { ...otherOpts, - method, - url: this._baseUrl() + path, + url: `${this._baseUrl()}${path}`, headers: { - ...headers, - "Authorization": `Bearer ${this.$auth.oauth_access_token}`, + "X-PW-AccessToken": this.$auth.api_key, + "X-PW-Application": "developer_api", + "X-PW-UserEmail": this.$auth.email, + "Content-Type": "application/json", }, }); }, - async createOrUpdatePerson(personData) { + createWebhook(opts = {}) { return this._makeRequest({ - method: "post", - path: "/person", - data: personData, + method: "POST", + path: "/webhooks", + ...opts, }); }, - async createOrUpdateProject(projectData) { + deleteWebhook({ + hookId, ...opts + }) { return this._makeRequest({ - method: "post", - path: "/project", - data: projectData, + method: "DELETE", + path: `/webhooks/${hookId}`, + ...opts, }); }, - async relateProjectToCrm(crmId, projectId) { + listObjects({ + objectType, ...opts + }) { return this._makeRequest({ - method: "post", - path: `/project/${projectId}/relate`, - data: { - crmId, - }, + method: "POST", + path: `/${objectType}/search`, + ...opts, + }); + }, + listTags(opts = {}) { + return this._makeRequest({ + path: "/tags", + ...opts, + }); + }, + getObject({ + objectType, objectId, ...opts + }) { + return this._makeRequest({ + path: `/${objectType}/${objectId}`, + ...opts, + }); + }, + createPerson(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/people", + ...opts, + }); + }, + updatePerson({ + personId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/people/${personId}`, + ...opts, }); }, - async getNewPerson() { + createProject(opts = {}) { return this._makeRequest({ - path: "/person/new", + method: "POST", + path: "/projects", + ...opts, }); }, - async getNewLead() { + updateProject({ + projectId, ...opts + }) { return this._makeRequest({ - path: "/lead/new", + method: "PUT", + path: `/projects/${projectId}`, + ...opts, }); }, - async getNewOpportunity() { + relateProjectToCrmObject({ + objectType, objectId, ...opts + }) { return this._makeRequest({ - path: "/opportunity/new", + method: "POST", + path: `/${objectType}/${objectId}/related`, + ...opts, }); }, }, diff --git a/components/copper/package.json b/components/copper/package.json new file mode 100644 index 0000000000000..b0eb582b196cb --- /dev/null +++ b/components/copper/package.json @@ -0,0 +1,18 @@ +{ + "name": "@pipedream/copper", + "version": "0.0.1", + "description": "Pipedream Copper Components", + "main": "copper.app.mjs", + "keywords": [ + "pipedream", + "copper" + ], + "homepage": "https://pipedream.com/apps/copper", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} diff --git a/components/copper/sources/common/base.mjs b/components/copper/sources/common/base.mjs new file mode 100644 index 0000000000000..6a69e9bac4873 --- /dev/null +++ b/components/copper/sources/common/base.mjs @@ -0,0 +1,58 @@ +import copper from "../../copper.app.mjs"; + +export default { + props: { + copper, + db: "$.service.db", + http: "$.interface.http", + }, + hooks: { + async activate() { + const { id } = await this.copper.createWebhook({ + data: { + target: this.http.endpoint, + type: this.getObjectType(), + event: this.getEventType(), + }, + }); + this._setHookId(id); + }, + async deactivate() { + const hookId = this._getHookId(); + if (hookId) { + await this.copper.deleteWebhook({ + hookId, + }); + } + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getEventType() { + return "new"; + }, + generateMeta(item) { + return { + id: item.ids[0], + summary: this.getSummary(item), + ts: Date.parse(item.timestamp), + }; + }, + getObjectType() { + throw new Error("getObjectType is not implemented"); + }, + getSummary() { + throw new Error("getSummary is not implemented"); + }, + }, + async run(event) { + const { body } = event; + const meta = this.generateMeta(body); + this.$emit(body, meta); + }, +}; diff --git a/components/copper/sources/new-lead-instant/new-lead-instant.mjs b/components/copper/sources/new-lead-instant/new-lead-instant.mjs index 77594fe8eef0c..9b3a3aee48cff 100644 --- a/components/copper/sources/new-lead-instant/new-lead-instant.mjs +++ b/components/copper/sources/new-lead-instant/new-lead-instant.mjs @@ -1,49 +1,22 @@ -import copper from "../../copper.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "copper-new-lead-instant", - name: "New Lead Instant", - description: "Emits an event when a new lead is created in Copper", - version: "0.0.{{ts}}", + name: "New Lead (Instant)", + description: "Emit new event when a new lead is created in Copper", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - copper, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, - }, - }, - methods: { - generateMeta(data) { - const { - id, name, created_date: createdDate, - } = data; - const ts = new Date(createdDate).getTime(); - return { - id, - summary: name, - ts, - }; + ...common.methods, + getObjectType() { + return "lead"; + }, + getSummary(item) { + return `New lead created with ID ${item.ids[0]}`; }, }, - - async run() { - const lastRun = this.db.get("lastRun") || this.timer.intervalSeconds; - const now = Math.floor(Date.now() / 1000); - - const newLeads = await this.copper.getNewLead(); - newLeads.forEach((lead) => { - if (lead.created_date >= lastRun && lead.created_date < now) { - const meta = this.generateMeta(lead); - this.$emit(lead, meta); - } - }); - - this.db.set("lastRun", now); - }, + sampleEmit, }; diff --git a/components/copper/sources/new-lead-instant/test-event.mjs b/components/copper/sources/new-lead-instant/test-event.mjs new file mode 100644 index 0000000000000..ea50c83f1924f --- /dev/null +++ b/components/copper/sources/new-lead-instant/test-event.mjs @@ -0,0 +1,10 @@ +export default { + "subscription_id": 395049, + "event": "new", + "type": "lead", + "ids": [ + 89011972 + ], + "updated_attributes": {}, + "timestamp": "2024-10-14T16:15:54.893Z" +} \ No newline at end of file diff --git a/components/copper/sources/new-opportunity-instant/new-opportunity-instant.mjs b/components/copper/sources/new-opportunity-instant/new-opportunity-instant.mjs index b4008d3bc6d5e..f1a78821c4dfa 100644 --- a/components/copper/sources/new-opportunity-instant/new-opportunity-instant.mjs +++ b/components/copper/sources/new-opportunity-instant/new-opportunity-instant.mjs @@ -1,59 +1,22 @@ -import copper from "../../copper.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "copper-new-opportunity-instant", - name: "New Opportunity Instant", - description: "Emits an event when a new opportunity is created in Copper", - version: "0.0.{{ts}}", + name: "New Opportunity (Instant)", + description: "Emit new event when a new opportunity is created in Copper", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - copper, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, - }, - }, methods: { - generateMeta(data) { - const { - id, name, date_created: created, - } = data; - const ts = new Date(created).getTime(); - return { - id, - summary: `New Opportunity: ${name}`, - ts, - }; + ...common.methods, + getObjectType() { + return "opportunity"; }, - }, - hooks: { - async deploy() { - const opportunities = await this.copper.getNewOpportunity(); - for (const opportunity of opportunities) { - this.$emit(opportunity, { - id: opportunity.id, - summary: `New Opportunity: ${opportunity.name}`, - ts: Date.parse(opportunity.date_created), - }); - } + getSummary(item) { + return `New opportunity created with ID ${item.ids[0]}`; }, }, - async run() { - const lastRun = this.db.get("lastRun") || this.copper._baseUrl() + "/opportunity/new"; - const { data } = await this.copper._makeRequest({ - path: lastRun, - }); - if (data.length > 0) { - const { id: lastId } = data[data.length - 1]; - this.db.set("lastRun", lastId); - data.forEach((item) => { - const meta = this.generateMeta(item); - this.$emit(item, meta); - }); - } - }, + sampleEmit, }; diff --git a/components/copper/sources/new-opportunity-instant/test-event.mjs b/components/copper/sources/new-opportunity-instant/test-event.mjs new file mode 100644 index 0000000000000..43a76ff5c6c4f --- /dev/null +++ b/components/copper/sources/new-opportunity-instant/test-event.mjs @@ -0,0 +1,10 @@ +export default { + "subscription_id": 395050, + "event": "new", + "type": "opportunity", + "ids": [ + 32978697 + ], + "updated_attributes": {}, + "timestamp": "2024-10-14T16:22:15.070Z" +} \ No newline at end of file diff --git a/components/copper/sources/new-person-instant/new-person-instant.mjs b/components/copper/sources/new-person-instant/new-person-instant.mjs index 0bd6db672d003..0bd763c87bfab 100644 --- a/components/copper/sources/new-person-instant/new-person-instant.mjs +++ b/components/copper/sources/new-person-instant/new-person-instant.mjs @@ -1,48 +1,22 @@ -import copper from "../../copper.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "copper-new-person-instant", - name: "New Person Instant", - description: "Emits a new event when a person object is newly created in Copper", + name: "New Person (Instant)", + description: "Emit new event when a person object is newly created in Copper", version: "0.0.1", type: "source", dedupe: "unique", - props: { - copper, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, - }, - }, methods: { - generateMeta(data) { - const id = data.id; - const summary = `New Person: ${data.name}`; - const ts = Date.parse(data.created_date); - return { - id, - summary, - ts, - }; + ...common.methods, + getObjectType() { + return "person"; + }, + getSummary(item) { + return `New person created with ID ${item.ids[0]}`; }, }, - async run() { - const lastRun = this.db.get("lastRun") || this.timer.intervalSeconds; - const now = Math.floor(Date.now() / 1000); - const newPersons = await this.copper.getNewPerson(); - - for (const person of newPersons) { - const createdDate = Math.floor(Date.parse(person.created_date) / 1000); - - if (createdDate > lastRun) { - const meta = this.generateMeta(person); - this.$emit(person, meta); - } - } - - this.db.set("lastRun", now); - }, + sampleEmit, }; diff --git a/components/copper/sources/new-person-instant/test-event.mjs b/components/copper/sources/new-person-instant/test-event.mjs new file mode 100644 index 0000000000000..ff2c0c257af8d --- /dev/null +++ b/components/copper/sources/new-person-instant/test-event.mjs @@ -0,0 +1,10 @@ +export default { + "subscription_id": 395034, + "event": "new", + "type": "person", + "ids": [ + 167487595 + ], + "updated_attributes": {}, + "timestamp": "2024-10-14T16:07:45.004Z" +} \ No newline at end of file From fbd989f5501661197903a6487defd2bfea68993f Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Mon, 14 Oct 2024 14:33:39 -0400 Subject: [PATCH 3/4] pnpm-lock.yaml --- pnpm-lock.yaml | 114 ++++++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 54 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1145af726f4d9..cd7c5c0e9cde0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,7 +60,7 @@ importers: pnpm: 7.33.6 putout: 32.2.0_typescript@5.2.2 renamer: 4.0.0 - ts-jest: 29.1.1_py5cyg2l76gggsf4xgc65fzuzq + ts-jest: 29.1.1_s6pp5jfszqvqftxetajx5tybba tsc-esm-fix: 2.20.17 tsc-watch: 5.0.3_typescript@5.2.2 typescript: 5.2.2 @@ -2136,6 +2136,12 @@ importers: dependencies: '@pipedream/platform': 1.5.1 + components/copper: + specifiers: + '@pipedream/platform': ^3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 + components/copperx: specifiers: '@pipedream/platform': ^1.5.1 @@ -13000,55 +13006,6 @@ 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'} @@ -13284,7 +13241,55 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0_tdq3komn4zwyd65w7klbptsu34 + '@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/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 '@aws-sdk/middleware-host-header': 3.598.0 @@ -13323,6 +13328,7 @@ packages: '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt dev: false @@ -17618,7 +17624,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 + '@aws-sdk/client-sts': 3.600.0_dseaa2p5u2yk67qiepewcq3hkq '@aws-sdk/s3-request-presigner': 3.609.0 '@pipedream/helper_functions': 0.3.12 '@pipedream/platform': 1.6.6 @@ -34618,7 +34624,7 @@ packages: engines: {node: '>=6'} dev: true - /ts-jest/29.1.1_py5cyg2l76gggsf4xgc65fzuzq: + /ts-jest/29.1.1_s6pp5jfszqvqftxetajx5tybba: resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -34639,7 +34645,7 @@ packages: esbuild: optional: true dependencies: - '@babel/core': 7.23.0 + '@babel/core': 7.25.2 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 jest: 29.7.0_@types+node@20.9.2 From 0d26a9f11cf90e20ff7c0b4448caa96318549325 Mon Sep 17 00:00:00 2001 From: michelle0927 Date: Mon, 14 Oct 2024 14:52:54 -0400 Subject: [PATCH 4/4] fix typo --- .../actions/create-update-person/create-update-person.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/copper/actions/create-update-person/create-update-person.mjs b/components/copper/actions/create-update-person/create-update-person.mjs index 2e6148c4379e6..b57c0c21ada11 100644 --- a/components/copper/actions/create-update-person/create-update-person.mjs +++ b/components/copper/actions/create-update-person/create-update-person.mjs @@ -76,7 +76,7 @@ export default { street: this.streetAddress, city: this.city, state: this.state, - postal_code: this.postalode, + postal_code: this.postalCode, country: this.country, }, phone_numbers: this.phone && [