From 04ae14464145553625d3040b593de6f6e0c07c38 Mon Sep 17 00:00:00 2001 From: Jorge Cortes Date: Fri, 31 Jan 2025 11:22:34 -0500 Subject: [PATCH] [Components] textline - new components --- .../create-update-contact.mjs | 122 ++++++++++++++++++ .../send-announcement/send-announcement.mjs | 119 +++++++++++++++++ .../actions/send-message/send-message.mjs | 51 ++++++++ components/textline/common/constants.mjs | 21 +++ components/textline/package.json | 7 +- components/textline/textline.app.mjs | 111 +++++++++++++++- pnpm-lock.yaml | 6 +- 7 files changed, 430 insertions(+), 7 deletions(-) create mode 100644 components/textline/actions/create-update-contact/create-update-contact.mjs create mode 100644 components/textline/actions/send-announcement/send-announcement.mjs create mode 100644 components/textline/actions/send-message/send-message.mjs create mode 100644 components/textline/common/constants.mjs diff --git a/components/textline/actions/create-update-contact/create-update-contact.mjs b/components/textline/actions/create-update-contact/create-update-contact.mjs new file mode 100644 index 0000000000000..84739e20f67b3 --- /dev/null +++ b/components/textline/actions/create-update-contact/create-update-contact.mjs @@ -0,0 +1,122 @@ +import app from "../../textline.app.mjs"; + +export default { + key: "textline-create-update-contact", + name: "Create Or Update Contact", + description: "Create or update a contact in the Textline address book. [See the documentation](https://textline.docs.apiary.io/#reference/customers/customers/create-a-customer).", + version: "0.0.1", + type: "action", + props: { + app, + phoneNumber: { + propDefinition: [ + app, + "phoneNumber", + ], + }, + email: { + type: "string", + label: "Email", + description: "The email of the contact.", + optional: true, + }, + name: { + type: "string", + label: "Name", + description: "The name of the contact.", + optional: true, + }, + notes: { + type: "string", + label: "Notes", + description: "Notes about the contact.", + optional: true, + }, + tags: { + type: "string[]", + label: "Tags", + description: "Tags associated with the contact.", + optional: true, + }, + }, + methods: { + updateCustomer({ + uuid, ...args + } = {}) { + return this.app.put({ + path: `/customer/${uuid}`, + ...args, + }); + }, + createCustomer(args = {}) { + return this.app.post({ + path: "/customers", + ...args, + }); + }, + }, + async run({ $ }) { + const { + app, + updateCustomer, + createCustomer, + phoneNumber, + email, + name, + notes, + tags, + } = this; + + let response; + let uuid; + + try { + response = await createCustomer({ + $, + data: { + customer: { + phone_number: phoneNumber, + email, + name, + notes, + tags, + }, + }, + }); + } catch (error) { + if (error.response.status === 400 && error.response.data?.errors?.phone_number[0] === "Already in use") { + + ({ customer: { uuid } } = await app.getCustomerByPhoneNumber({ + $, + params: { + phone_number: phoneNumber, + }, + })); + + } else { + throw error; + } + } + + if (!uuid) { + $.export("$summary", `Successfully created the contact with uuid \`${response.customer.uuid}\`.`); + return response; + } + + response = await updateCustomer({ + $, + uuid, + data: { + customer: { + email, + name, + notes, + tags, + }, + }, + }); + + $.export("$summary", `Successfully updated the contact with uuid \`${uuid}\`.`); + return response; + }, +}; diff --git a/components/textline/actions/send-announcement/send-announcement.mjs b/components/textline/actions/send-announcement/send-announcement.mjs new file mode 100644 index 0000000000000..4eb0ea77af620 --- /dev/null +++ b/components/textline/actions/send-announcement/send-announcement.mjs @@ -0,0 +1,119 @@ +import app from "../../textline.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "textline-send-announcement", + name: "Send Announcement", + description: "Send an announcement to a group of contacts. [See the documentation](https://textline.docs.apiary.io/#reference/messaging-tools/announcements/send-an-announcement).", + version: "0.0.1", + type: "action", + props: { + app, + massTextGroupUuid: { + label: "Mass Text Group UUID", + description: "The UUID of the mass text group.", + propDefinition: [ + app, + "groupUuid", + ], + }, + massTextCommentBody: { + type: "string", + label: "Mass Text Comment Body", + description: "The content of the message to send.", + }, + massTextTitle: { + type: "string", + label: "Mass Text Title", + description: "The title of the message.", + }, + selectionType: { + type: "string", + label: "Selection Type", + description: "The type of the selection for the announcement.", + reloadProps: true, + options: Object.values(constants.SELECTION_TYPE), + }, + }, + additionalProps() { + const { selectionType } = this; + + if (selectionType === constants.SELECTION_TYPE.TAGS.value) { + return { + tag: { + type: "string", + label: "Tag", + description: "Send to all contacts with this tag.", + }, + }; + } + + if (selectionType === constants.SELECTION_TYPE.PHONE_NUMBERS.value) { + return { + phoneNumbers: { + type: "string[]", + label: "Phone Numbers", + description: "The phone numbers to send the announcement to.", + useQuery: true, + options: async ({ + page, query, + }) => { + const { customers } = await this.app.listCustomers({ + params: { + page, + page_size: 30, + query: query || "", + }, + }); + return customers.map(({ + name: label, phone_number: value, + }) => ({ + label, + value, + })); + }, + }, + }; + } + + return {}; + }, + methods: { + sendAnnouncement(args = {}) { + return this.app.post({ + path: "/announcements", + ...args, + }); + }, + }, + async run({ $ }) { + const { + sendAnnouncement, + selectionType, + massTextGroupUuid, + massTextCommentBody, + massTextTitle, + tag, + phoneNumbers, + } = this; + + const response = await sendAnnouncement({ + $, + data: { + selection_type: selectionType, + recipients: { + tag, + phone_numbers: phoneNumbers, + }, + mass_text: { + group_uuid: massTextGroupUuid, + comment_body: massTextCommentBody, + title: massTextTitle, + }, + }, + }); + + $.export("$summary", "Successfully sent the announcement."); + return response; + }, +}; diff --git a/components/textline/actions/send-message/send-message.mjs b/components/textline/actions/send-message/send-message.mjs new file mode 100644 index 0000000000000..55316e853d1a9 --- /dev/null +++ b/components/textline/actions/send-message/send-message.mjs @@ -0,0 +1,51 @@ +import app from "../../textline.app.mjs"; + +export default { + key: "textline-send-message", + name: "Send Message", + description: "Send a new message directly to a contact. [See the documentation](https://textline.docs.apiary.io/#reference/conversations/group-conversations/message-a-phone-number).", + version: "0.0.1", + type: "action", + props: { + app, + phoneNumber: { + propDefinition: [ + app, + "phoneNumber", + ], + }, + comment: { + type: "string", + label: "Message", + description: "The content of the message to send.", + }, + }, + methods: { + sendMessage(args = {}) { + return this.app.post({ + path: "/conversations", + ...args, + }); + }, + }, + async run({ $ }) { + const { + sendMessage, + phoneNumber, + comment, + } = this; + const response = await sendMessage({ + $, + data: { + phone_number: phoneNumber, + comment: { + body: comment, + }, + resolve: "1", + }, + }); + + $.export("$summary", `Successfully sent message to ${phoneNumber}.`); + return response; + }, +}; diff --git a/components/textline/common/constants.mjs b/components/textline/common/constants.mjs new file mode 100644 index 0000000000000..740c099551cb8 --- /dev/null +++ b/components/textline/common/constants.mjs @@ -0,0 +1,21 @@ +const BASE_URL = "https://application.textline.com"; +const VERSION_PATH = "/api"; +const DEFAULT_LIMIT = 30; + +const SELECTION_TYPE = { + TAGS: { + label: "Tags", + value: "tags", + }, + PHONE_NUMBERS: { + label: "Phone Numbers", + value: "phone_numbers", + }, +}; + +export default { + BASE_URL, + VERSION_PATH, + DEFAULT_LIMIT, + SELECTION_TYPE, +}; diff --git a/components/textline/package.json b/components/textline/package.json index f8ecf788ca9f9..735492706153f 100644 --- a/components/textline/package.json +++ b/components/textline/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/textline", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Textline Components", "main": "textline.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/textline/textline.app.mjs b/components/textline/textline.app.mjs index 7fe56e9326683..8ebf4d8bc4a7d 100644 --- a/components/textline/textline.app.mjs +++ b/components/textline/textline.app.mjs @@ -1,11 +1,114 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "textline", - propDefinitions: {}, + propDefinitions: { + phoneNumber: { + type: "string", + label: "Phone Number", + description: "The phone number of the contact. Eg. `(222) 222-2222`.", + useQuery: true, + async options({ + page, + query, + mapper = ({ + name: label, phone_number: value, + }) => ({ + label, + value, + }), + }) { + const { customers } = await this.listCustomers({ + params: { + page, + page_size: constants.DEFAULT_LIMIT, + query: query || "", + }, + }); + return customers.map(mapper); + }, + }, + groupUuid: { + type: "string", + label: "Group UUID", + description: "The UUID of the group or organization.", + async options({ + params = { + include_groups: true, + include_users: false, + }, + }) { + const { groups } = await this.listOrganizationDetails({ + params, + }); + return groups.map(({ + name: label, uuid: value, + }) => ({ + label, + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + return `${constants.BASE_URL}${constants.VERSION_PATH}${path}.json`; + }, + getHeaders(headers) { + return { + ...headers, + "Content-Type": "application/json", + "x-tgp-access-token": this.$auth.access_token, + }; + }, + async _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + try { + return await axios($, { + ...args, + url: this.getUrl(path), + headers: this.getHeaders(headers), + }); + } catch (error) { + if (error.response?.status === 500) { + console.log("Error response", error.response); + throw new Error("Textline is currently experiencing issues. Please try again later."); + } + throw error; + } + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + put(args = {}) { + return this._makeRequest({ + method: "PUT", + ...args, + }); + }, + getCustomerByPhoneNumber(args = {}) { + return this._makeRequest({ + path: "/customers", + ...args, + }); + }, + listOrganizationDetails(args = {}) { + return this._makeRequest({ + path: "/organization", + ...args, + }); + }, + listCustomers(args = {}) { + return this._makeRequest({ + path: "/customers", + ...args, + }); }, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a233bdbb8e088..365318da10224 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10797,7 +10797,11 @@ importers: specifier: ^1.1.0 version: 1.6.6 - components/textline: {} + components/textline: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/textlocal: dependencies: