From 795558fc7ab134bc78b1cd2195cdfb5bae5afdf6 Mon Sep 17 00:00:00 2001 From: vellames <91911778+vellames@users.noreply.github.com> Date: Thu, 15 Dec 2022 11:06:51 -0300 Subject: [PATCH] Actions & Sources - RepairShopr (#4939) * repairshopr * pnpm * Update URL format * Update mandatory fields * Update object name * Update object name * Fix bug * revert version * removing action * create lead Co-authored-by: vunguyenhung --- .../repairshopr/actions/common/enums.mjs | 17 ++ .../create-customer/create-customer.mjs | 96 ++++++++++ .../actions/create-lead/create-lead.mjs | 106 +++++++++++ .../actions/create-ticket/create-ticket.mjs | 83 +++++++++ .../actions/list-customers/list-customers.mjs | 93 ++++++++++ components/repairshopr/package.json | 19 ++ components/repairshopr/repairshopr.app.mjs | 174 +++++++++++++++++- .../repairshopr/sources/common/source.mjs | 75 ++++++++ .../sources/new-customer/new-customer.mjs | 26 +++ .../sources/new-invoice/new-invoice.mjs | 31 ++++ .../sources/new-ticket/new-ticket.mjs | 31 ++++ pnpm-lock.yaml | 6 + 12 files changed, 753 insertions(+), 4 deletions(-) create mode 100644 components/repairshopr/actions/common/enums.mjs create mode 100644 components/repairshopr/actions/create-customer/create-customer.mjs create mode 100644 components/repairshopr/actions/create-lead/create-lead.mjs create mode 100644 components/repairshopr/actions/create-ticket/create-ticket.mjs create mode 100644 components/repairshopr/actions/list-customers/list-customers.mjs create mode 100644 components/repairshopr/package.json create mode 100644 components/repairshopr/sources/common/source.mjs create mode 100644 components/repairshopr/sources/new-customer/new-customer.mjs create mode 100644 components/repairshopr/sources/new-invoice/new-invoice.mjs create mode 100644 components/repairshopr/sources/new-ticket/new-ticket.mjs diff --git a/components/repairshopr/actions/common/enums.mjs b/components/repairshopr/actions/common/enums.mjs new file mode 100644 index 0000000000000..c582c0015a31a --- /dev/null +++ b/components/repairshopr/actions/common/enums.mjs @@ -0,0 +1,17 @@ +export default { + TICKET_PROBLEM_TYPE: [ + "Virus", + "TuneUp", + "Software", + "Other", + ], + TICKET_STATUS: [ + "New", + "In Progress", + "Resolved", + "Waiting for Parts", + "Waiting on Customer", + "Scheduled", + "Customer Reply", + ], +}; diff --git a/components/repairshopr/actions/create-customer/create-customer.mjs b/components/repairshopr/actions/create-customer/create-customer.mjs new file mode 100644 index 0000000000000..bb4fa05b1810a --- /dev/null +++ b/components/repairshopr/actions/create-customer/create-customer.mjs @@ -0,0 +1,96 @@ +import app from "../../repairshopr.app.mjs"; + +export default { + key: "repairshopr-create-customer", + name: "Create Customer", + description: "Create a new customer. [See the docs here](https://api-docs.repairshopr.com/#/Customer/post_customers)", + version: "0.0.1", + type: "action", + props: { + app, + businessName: { + propDefinition: [ + app, + "businessName", + ], + }, + firstName: { + propDefinition: [ + app, + "firstName", + ], + }, + lastName: { + propDefinition: [ + app, + "lastName", + ], + }, + email: { + propDefinition: [ + app, + "email", + ], + }, + phone: { + propDefinition: [ + app, + "phone", + ], + }, + mobile: { + propDefinition: [ + app, + "mobile", + ], + }, + address: { + propDefinition: [ + app, + "address", + ], + }, + address2: { + propDefinition: [ + app, + "address2", + ], + }, + city: { + propDefinition: [ + app, + "city", + ], + }, + state: { + propDefinition: [ + app, + "state", + ], + }, + zip: { + propDefinition: [ + app, + "zip", + ], + }, + }, + async run({ $ }) { + const data = { + business_name: this.businessName, + firstname: this.firstName, + lastname: this.lastName, + email: this.email, + phone: this.phone, + mobile: this.mobile, + address: this.address, + address2: this.address2, + city: this.city, + state: this.state, + zip: this.zip, + }; + const res = await this.app.createCustomer(data, $); + $.export("$summary", "Customer successfully created"); + return res?.customer; + }, +}; diff --git a/components/repairshopr/actions/create-lead/create-lead.mjs b/components/repairshopr/actions/create-lead/create-lead.mjs new file mode 100644 index 0000000000000..cd23a8528f8a7 --- /dev/null +++ b/components/repairshopr/actions/create-lead/create-lead.mjs @@ -0,0 +1,106 @@ +import app from "../../repairshopr.app.mjs"; + +export default { + key: "repairshopr-create-lead", + name: "Create Lead", + description: "Create a new lead. [See the docs here](https://api-docs.repairshopr.com/#/Lead/post_leads)", + version: "0.0.1", + type: "action", + props: { + app, + businessName: { + propDefinition: [ + app, + "businessName", + ], + description: "The business name of the lead.", + }, + firstName: { + propDefinition: [ + app, + "firstName", + ], + description: "The first name of the lead.", + }, + lastName: { + propDefinition: [ + app, + "lastName", + ], + description: "The last name of the lead.", + }, + email: { + propDefinition: [ + app, + "email", + ], + description: "The email address of the lead.", + }, + phone: { + propDefinition: [ + app, + "phone", + ], + description: "The phone number of the lead.", + }, + mobile: { + propDefinition: [ + app, + "mobile", + ], + description: "The mobile number of the lead.", + }, + address: { + propDefinition: [ + app, + "address", + ], + description: "The address of the lead.", + }, + city: { + propDefinition: [ + app, + "city", + ], + description: "The city of the lead.", + }, + state: { + propDefinition: [ + app, + "state", + ], + description: "The state of the lead.", + }, + zip: { + propDefinition: [ + app, + "zip", + ], + description: "The zip code of the lead.", + }, + converted: { + type: "boolean", + label: "Converted", + description: "Whether the lead has been converted to a customer.", + optional: true, + }, + }, + async run({ $ }) { + const data = { + business_name: this.businessName, + first_name: this.firstName, + last_name: this.lastName, + email: this.email, + phone: this.phone, + mobile: this.mobile, + address: this.address, + city: this.city, + state: this.state, + zip: this.zip, + converted: this.converted, + }; + const res = await this.app.createLead(data, this); + $.export("$summary", "Lead successfully created"); + return res?.lead; + }, +}; diff --git a/components/repairshopr/actions/create-ticket/create-ticket.mjs b/components/repairshopr/actions/create-ticket/create-ticket.mjs new file mode 100644 index 0000000000000..c7173ef72491b --- /dev/null +++ b/components/repairshopr/actions/create-ticket/create-ticket.mjs @@ -0,0 +1,83 @@ +import app from "../../repairshopr.app.mjs"; +import enums from "../common/enums.mjs"; + +export default { + key: "repairshopr-create-ticket", + name: "Create Ticket", + description: "Create a new ticket. [See the docs here](https://api-docs.repairshopr.com/#/Ticket/post_tickets)", + version: "0.0.1", + type: "action", + props: { + app, + customerId: { + propDefinition: [ + app, + "customerId", + ], + }, + ticketTypeId: { + type: "integer", + label: "Ticket Type ID", + description: "The ID of the ticket type.", + optional: true, + }, + number: { + type: "string", + label: "Number", + description: "The ticket number.", + optional: true, + }, + subject: { + type: "string", + label: "Subject", + description: "The subject of the ticket.", + }, + dueDate: { + type: "string", + label: "Due Date", + description: "The due date of the ticket. Use the format `YYYY-MM-DD`.", + optional: true, + }, + startAt: { + type: "string", + label: "Start At", + description: "The start date of the ticket. Use the format `YYYY-MM-DDTHH:MM:SS`.", + optional: true, + }, + endAt: { + type: "string", + label: "End At", + description: "The end date of the ticket. Use the format `YYYY-MM-DDTHH:MM:SS`.", + optional: true, + }, + problemType: { + type: "string", + label: "Problem Type", + description: "The problem type of the ticket.", + options: enums.TICKET_PROBLEM_TYPE, + }, + status: { + type: "string", + label: "Status", + description: "The status of the ticket.", + options: enums.TICKET_STATUS, + optional: true, + }, + }, + async run({ $ }) { + const data = { + customer_id: this.customerId, + ticket_type_id: this.ticketTypeId, + number: this.number, + subject: this.subject, + due_date: this.dueDate, + start_at: this.startAt, + end_at: this.endAt, + problem_type: this.problemType, + status: this.status, + }; + const res = await this.app.createTicket(data, $); + $.export("$summary", "Ticket successfully created"); + return res?.ticket; + }, +}; diff --git a/components/repairshopr/actions/list-customers/list-customers.mjs b/components/repairshopr/actions/list-customers/list-customers.mjs new file mode 100644 index 0000000000000..f48805e6d2b09 --- /dev/null +++ b/components/repairshopr/actions/list-customers/list-customers.mjs @@ -0,0 +1,93 @@ +import app from "../../repairshopr.app.mjs"; + +export default { + key: "repairshopr-list-customers", + name: "List Customers", + description: "List Customers. [See the docs here](https://api-docs.repairshopr.com/#/Customer/get_customers)", + version: "0.0.1", + type: "action", + props: { + app, + sort: { + type: "string", + label: "Sort", + description: "A customer field to order by. Examples `firstname ASC`, `city DESC`", + optional: true, + }, + query: { + type: "string", + label: "Query", + description: "Search Query", + optional: true, + }, + firstName: { + type: "string", + label: "First Name", + description: "Any customers with a first name like the parameter", + optional: true, + }, + lastName: { + type: "string", + label: "Last Name", + description: "Any customers with a last name like the parameter", + optional: true, + }, + businessName: { + type: "string", + label: "Business Name", + description: "Any customers with a business name like the parameter", + optional: true, + }, + id: { + type: "integer[]", + label: "ID", + description: "Any customers with an ID in the list", + optional: true, + }, + notId: { + type: "integer[]", + label: "Not ID", + description: "Any customers with an ID not in the list", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "Any customers with an email like the parameter", + optional: true, + }, + includeDisabled: { + type: "boolean", + label: "Include Disabled", + description: "Whether or not the returned list of customers includes disabled customers", + optional: true, + }, + }, + async run({ $ }) { + const params = { + sort: this.sort, + query: this.query, + firstname: this.firstName, + lastname: this.lastName, + business_name: this.businessName, + id: this.id, + not_id: this.notId, + email: this.email, + include_disabled: this.includeDisabled, + }; + const data = []; + let page = 1; + while (true) { + const { customers } = await this.app.listCustomers(page, params); + for (const customer of customers) { + data.push(customer); + } + if (customers.length === 0) { + break; + } + page++; + } + $.export("$summary", `Fetched ${data.length} customer(s)`); + return data; + }, +}; diff --git a/components/repairshopr/package.json b/components/repairshopr/package.json new file mode 100644 index 0000000000000..67fd645861405 --- /dev/null +++ b/components/repairshopr/package.json @@ -0,0 +1,19 @@ +{ + "name": "@pipedream/repairshopr", + "version": "0.0.1", + "description": "Pipedream Repairshopr Components", + "main": "repairshopr.app.js", + "keywords": [ + "pipedream", + "repairshopr" + ], + "homepage": "https://pipedream.com/apps/repairshopr", + "author": "Pipedream (https://pipedream.com/)", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^1.2.1" + } +} diff --git a/components/repairshopr/repairshopr.app.mjs b/components/repairshopr/repairshopr.app.mjs index 2220fb7d3bd97..a9b014a5d0b42 100644 --- a/components/repairshopr/repairshopr.app.mjs +++ b/components/repairshopr/repairshopr.app.mjs @@ -1,11 +1,177 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "repairshopr", - propDefinitions: {}, + propDefinitions: { + businessName: { + type: "string", + label: "Business Name", + description: "The business name of the customer.", + }, + firstName: { + type: "string", + label: "First Name", + description: "The first name of the customer.", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the customer.", + }, + email: { + type: "string", + label: "Email", + description: "The email address of the customer.", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "The phone number of the customer.", + optional: true, + }, + mobile: { + type: "string", + label: "Mobile", + description: "The mobile number of the customer.", + optional: true, + }, + address: { + type: "string", + label: "Address", + description: "The address of the customer.", + optional: true, + }, + address2: { + type: "string", + label: "Address 2", + description: "The second address of the customer.", + optional: true, + }, + city: { + type: "string", + label: "City", + description: "The city of the customer.", + optional: true, + }, + state: { + type: "string", + label: "State", + description: "The state of the customer.", + optional: true, + }, + zip: { + type: "string", + label: "Zip", + description: "The zip code of the customer.", + optional: true, + }, + customerId: { + type: "string", + label: "Customer ID", + description: "The ID of the customer.", + async options({ page }) { + const { customers } = await this.listCustomers(page + 1); + return customers.map((customer) => ({ + label: customer.business_name || `${customer.first_name} ${customer.last_name}`, + value: customer.id, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _getBaseUrl() { + return `https://${this._getSubdomain()}.repairshopr.com/api/v1`; + }, + _getApiKey() { + return this.$auth.api_key; + }, + _getSubdomain() { + return this.$auth.subdomain; + }, + _getHeaders() { + return { + "Content-Type": "application/json", + "Authorization": `Bearer ${this._getApiKey()}`, + }; + }, + async _makeHttpRequest(opts = {}, ctx = this) { + const axiosOpts = { + ...opts, + url: this._getBaseUrl() + opts.path, + headers: this._getHeaders(), + }; + return axios(ctx, axiosOpts); + }, + async createCustomer(data, ctx = this) { + return this._makeHttpRequest( + { + path: "/customers", + method: "POST", + data, + }, + ctx, + ); + }, + async createLead(data, ctx = this) { + return this._makeHttpRequest( + { + path: "/leads", + method: "POST", + data, + }, + ctx, + ); + }, + async createTicket(data, ctx = this) { + return this._makeHttpRequest( + { + path: "/tickets", + method: "POST", + data, + }, + ctx, + ); + }, + async listCustomers(page, params = {}, ctx = this) { + return this._makeHttpRequest( + { + path: "/customers", + method: "GET", + params: { + page, + ...params, + }, + }, + ctx, + ); + }, + async listTickets(page, params = {}, ctx = this) { + return this._makeHttpRequest( + { + path: "/tickets", + method: "GET", + params: { + page, + ...params, + }, + }, + ctx, + ); + }, + async listInvoices(page, params = {}, ctx = this) { + return this._makeHttpRequest( + { + path: "/invoices", + method: "GET", + params: { + page, + ...params, + }, + }, + ctx, + ); }, }, }; diff --git a/components/repairshopr/sources/common/source.mjs b/components/repairshopr/sources/common/source.mjs new file mode 100644 index 0000000000000..04677ee690335 --- /dev/null +++ b/components/repairshopr/sources/common/source.mjs @@ -0,0 +1,75 @@ +import app from "../../repairshopr.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + dedupe: "unique", + props: { + app, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + db: "$.service.db", + }, + methods: { + setLastEmmittedId(id) { + this.db.set("lastEmittedId", id); + }, + getLastEmmittedId() { + return this.db.get("lastEmittedId"); + }, + setLastEmittedDate(date) { + this.db.set("lastEmittedDate", date); + }, + getLastEmittedDate() { + return this.db.get("lastEmittedDate"); + }, + getData() { + throw new Error("getData() not implemented"); + }, + getParams() { + return { + sort: "created_at DESC", + }; + }, + getAgregatorProp() { + throw new Error("getAgregatorProp() not implemented"); + }, + getSummary() { + throw new Error("getSummary() not implemented"); + }, + }, + async run() { + const lastEmittedId = this.getLastEmmittedId(); + let page = 1; + const emmitedEvents = []; + + const fetchDataMethod = this.getData(); + loop1: + while (true) { + const res = await fetchDataMethod(page, this.getParams()); + const events = res[this.getAgregatorProp()]; + if (events.length === 0) { + break; + } + for (const event of events) { + if (event.id === lastEmittedId) { + break loop1; + } + emmitedEvents.unshift(event); + } + page++; + } + + for (const event of emmitedEvents) { + this.$emit(event, this.getSummary(event)); + } + + this.setLastEmittedDate(new Date()); + if (emmitedEvents.length > 0) { + this.setLastEmmittedId(emmitedEvents[emmitedEvents.length - 1].id); + } + }, +}; diff --git a/components/repairshopr/sources/new-customer/new-customer.mjs b/components/repairshopr/sources/new-customer/new-customer.mjs new file mode 100644 index 0000000000000..e70d71eb554a9 --- /dev/null +++ b/components/repairshopr/sources/new-customer/new-customer.mjs @@ -0,0 +1,26 @@ +import common from "../common/source.mjs"; + +export default { + ...common, + key: "repairshopr-new-customer", + type: "source", + name: "New Customer", + description: "Emit new event when a new customer is created.", + version: "0.0.1", + methods: { + ...common.methods, + getData() { + return this.app.listCustomers; + }, + getAgregatorProp() { + return "customers"; + }, + getSummary(event) { + return { + id: event.id, + summary: event.business_name || event.email || event.id, + ts: event.created_at || Date.now(), + }; + }, + }, +}; diff --git a/components/repairshopr/sources/new-invoice/new-invoice.mjs b/components/repairshopr/sources/new-invoice/new-invoice.mjs new file mode 100644 index 0000000000000..b8013093c2029 --- /dev/null +++ b/components/repairshopr/sources/new-invoice/new-invoice.mjs @@ -0,0 +1,31 @@ +import common from "../common/source.mjs"; + +export default { + ...common, + key: "repairshopr-new-invoice", + type: "source", + name: "New Invoice", + description: "Emit new event when a new invoice is created.", + version: "0.0.1", + methods: { + ...common.methods, + getData() { + return this.app.listInvoices; + }, + getAgregatorProp() { + return "invoices"; + }, + getParams() { + return { + since_updated_at: this.getLastEmittedDate(), + }; + }, + getSummary(event) { + return { + id: event.id, + summary: event.customer_business_then_name || event.number || event.id, + ts: event.created_at || Date.now(), + }; + }, + }, +}; diff --git a/components/repairshopr/sources/new-ticket/new-ticket.mjs b/components/repairshopr/sources/new-ticket/new-ticket.mjs new file mode 100644 index 0000000000000..4a6a7af29cd13 --- /dev/null +++ b/components/repairshopr/sources/new-ticket/new-ticket.mjs @@ -0,0 +1,31 @@ +import common from "../common/source.mjs"; + +export default { + ...common, + key: "repairshopr-new-ticket", + type: "source", + name: "New Ticket", + description: "Emit new event when a new ticket is created.", + version: "0.0.1", + methods: { + ...common.methods, + getData() { + return this.app.listTickets; + }, + getAgregatorProp() { + return "tickets"; + }, + getParams() { + return { + created_after: this.getLastEmittedDate(), + }; + }, + getSummary(event) { + return { + id: event.id, + summary: event.subject || event.id, + ts: event.created_at || Date.now(), + }; + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 340eabb8f6178..b517359a7fab4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2312,6 +2312,12 @@ importers: components/render: specifiers: {} + components/repairshopr: + specifiers: + '@pipedream/platform': ^1.2.1 + dependencies: + '@pipedream/platform': 1.2.1 + components/reply_io: specifiers: {}