From 9f7d8026f77fe89868b2b6ea922a4d6efda643fc Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Thu, 20 Mar 2025 11:45:42 -0300 Subject: [PATCH 1/4] kustomer init --- .../create-conversation.mjs | 112 +++ .../create-customer/create-customer.mjs | 248 +++++++ .../update-conversation.mjs | 235 ++++++ .../update-customer/update-customer.mjs | 290 ++++++++ components/kustomer/kustomer.app.mjs | 677 +++++++++++++++++- components/kustomer/package.json | 2 +- .../new-conversation-instant.mjs | 84 +++ .../new-customer-instant.mjs | 81 +++ .../new-message-instant.mjs | 122 ++++ .../updated-conversation-instant.mjs | 113 +++ .../updated-customer-instant.mjs | 111 +++ 11 files changed, 2071 insertions(+), 4 deletions(-) create mode 100644 components/kustomer/actions/create-conversation/create-conversation.mjs create mode 100644 components/kustomer/actions/create-customer/create-customer.mjs create mode 100644 components/kustomer/actions/update-conversation/update-conversation.mjs create mode 100644 components/kustomer/actions/update-customer/update-customer.mjs create mode 100644 components/kustomer/sources/new-conversation-instant/new-conversation-instant.mjs create mode 100644 components/kustomer/sources/new-customer-instant/new-customer-instant.mjs create mode 100644 components/kustomer/sources/new-message-instant/new-message-instant.mjs create mode 100644 components/kustomer/sources/updated-conversation-instant/updated-conversation-instant.mjs create mode 100644 components/kustomer/sources/updated-customer-instant/updated-customer-instant.mjs diff --git a/components/kustomer/actions/create-conversation/create-conversation.mjs b/components/kustomer/actions/create-conversation/create-conversation.mjs new file mode 100644 index 0000000000000..b48458a916c14 --- /dev/null +++ b/components/kustomer/actions/create-conversation/create-conversation.mjs @@ -0,0 +1,112 @@ +import kustomer from "../../kustomer.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "kustomer-create-conversation", + name: "Create Conversation", + description: "Creates a new conversation in Kustomer. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + kustomer, + + // Required prop + customerId: { + type: "string", + label: "Customer ID", + description: "Unique identifier for the customer", + }, + + // Optional props + externalId: { + type: "string", + label: "External ID", + description: "External identifier", + optional: true, + }, + name: { + type: "string", + label: "Name", + description: "Name of the conversation", + optional: true, + }, + status: { + type: "string", + label: "Status", + description: "Status of the conversation", + optional: true, + }, + priority: { + type: "integer", + label: "Priority", + description: "Priority level (1-5)", + optional: true, + min: 1, + max: 5, + }, + direction: { + type: "string", + label: "Direction", + description: "Direction of the conversation", + optional: true, + }, + replyChannel: { + type: "string", + label: "Reply Channel", + description: "Channel to reply to", + optional: true, + }, + tags: { + propDefinition: [ + kustomer, + "tags", + ], + optional: true, + }, + assignedUsers: { + propDefinition: [ + kustomer, + "assignedUsers", + ], + optional: true, + }, + assignedTeams: { + propDefinition: [ + kustomer, + "assignedTeams", + ], + optional: true, + }, + defaultLang: { + type: "string", + label: "Default Language", + description: "Default language for the conversation", + optional: true, + }, + queue: { + type: "string", + label: "Queue", + description: "Queue information", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.kustomer.createConversation({ + customerId: this.customerId, + externalId: this.externalId, + name: this.name, + status: this.status, + priority: this.priority, + direction: this.direction, + replyChannel: this.replyChannel, + tags: this.tags, + assignedUsers: this.assignedUsers, + assignedTeams: this.assignedTeams, + defaultLang: this.defaultLang, + queue: this.queue, + }); + + $.export("$summary", `Created conversation with ID ${response.id}`); + return response; + }, +}; diff --git a/components/kustomer/actions/create-customer/create-customer.mjs b/components/kustomer/actions/create-customer/create-customer.mjs new file mode 100644 index 0000000000000..85c9558253715 --- /dev/null +++ b/components/kustomer/actions/create-customer/create-customer.mjs @@ -0,0 +1,248 @@ +import { axios } from "@pipedream/platform"; +import kustomer from "../../kustomer.app.mjs"; + +export default { + key: "kustomer-create-customer", + name: "Create Customer", + description: "Creates a new customer in Kustomer. [See the documentation]()", + version: "0.0.{{ts}}", + type: "action", + props: { + kustomer: { + type: "app", + app: "kustomer", + }, + name: { + propDefinition: [ + "kustomer", + "name", + ], + }, + company: { + propDefinition: [ + "kustomer", + "company", + ], + }, + externalId: { + propDefinition: [ + "kustomer", + "externalId", + ], + }, + username: { + propDefinition: [ + "kustomer", + "username", + ], + }, + signedUpAt: { + propDefinition: [ + "kustomer", + "signedUpAt", + ], + }, + lastActivityAt: { + propDefinition: [ + "kustomer", + "lastActivityAt", + ], + }, + lastCustomerActivityAt: { + propDefinition: [ + "kustomer", + "lastCustomerActivityAt", + ], + }, + lastSeenAt: { + propDefinition: [ + "kustomer", + "lastSeenAt", + ], + }, + avatarUrl: { + propDefinition: [ + "kustomer", + "avatarUrl", + ], + }, + externalIds: { + propDefinition: [ + "kustomer", + "externalIds", + ], + }, + sharedExternalIds: { + propDefinition: [ + "kustomer", + "sharedExternalIds", + ], + }, + emails: { + propDefinition: [ + "kustomer", + "emails", + ], + }, + sharedEmails: { + propDefinition: [ + "kustomer", + "sharedEmails", + ], + }, + phones: { + propDefinition: [ + "kustomer", + "phones", + ], + }, + sharedPhones: { + propDefinition: [ + "kustomer", + "sharedPhones", + ], + }, + whatsApps: { + propDefinition: [ + "kustomer", + "whatsApps", + ], + }, + facebookIds: { + propDefinition: [ + "kustomer", + "facebookIds", + ], + }, + instagramIds: { + propDefinition: [ + "kustomer", + "instagramIds", + ], + }, + socials: { + propDefinition: [ + "kustomer", + "socials", + ], + }, + sharedSocials: { + propDefinition: [ + "kustomer", + "sharedSocials", + ], + }, + urls: { + propDefinition: [ + "kustomer", + "urls", + ], + }, + locations: { + propDefinition: [ + "kustomer", + "locations", + ], + }, + locale: { + propDefinition: [ + "kustomer", + "locale", + ], + }, + timeZone: { + propDefinition: [ + "kustomer", + "timeZone", + ], + }, + tags: { + propDefinition: [ + "kustomer", + "tags", + ], + }, + sentiment: { + propDefinition: [ + "kustomer", + "sentiment", + ], + }, + birthdayAt: { + propDefinition: [ + "kustomer", + "birthdayAt", + ], + }, + gender: { + propDefinition: [ + "kustomer", + "gender", + ], + }, + createdAt: { + propDefinition: [ + "kustomer", + "createdAt", + ], + }, + importedAt: { + propDefinition: [ + "kustomer", + "importedAt", + ], + }, + rev: { + propDefinition: [ + "kustomer", + "rev", + ], + }, + defaultLang: { + propDefinition: [ + "kustomer", + "defaultLang", + ], + }, + }, + async run({ $ }) { + const customerData = { + name: this.name, + company: this.company, + externalId: this.externalId, + username: this.username, + signedUpAt: this.signedUpAt, + lastActivityAt: this.lastActivityAt, + lastCustomerActivityAt: this.lastCustomerActivityAt, + lastSeenAt: this.lastSeenAt, + avatarUrl: this.avatarUrl, + externalIds: this.externalIds, + sharedExternalIds: this.sharedExternalIds, + emails: this.emails, + sharedEmails: this.sharedEmails, + phones: this.phones, + sharedPhones: this.sharedPhones, + whatsApps: this.whatsApps, + facebookIds: this.facebookIds, + instagramIds: this.instagramIds, + socials: this.socials, + sharedSocials: this.sharedSocials, + urls: this.urls, + locations: this.locations, + locale: this.locale, + timeZone: this.timeZone, + tags: this.tags, + sentiment: this.sentiment, + birthdayAt: this.birthdayAt, + gender: this.gender, + createdAt: this.createdAt, + importedAt: this.importedAt, + rev: this.rev, + defaultLang: this.defaultLang, + }; + + const response = await this.kustomer.createCustomer(customerData); + $.export("$summary", `Created customer with ID ${response.id}`); + return response; + }, +}; diff --git a/components/kustomer/actions/update-conversation/update-conversation.mjs b/components/kustomer/actions/update-conversation/update-conversation.mjs new file mode 100644 index 0000000000000..6be6df703d841 --- /dev/null +++ b/components/kustomer/actions/update-conversation/update-conversation.mjs @@ -0,0 +1,235 @@ +import kustomer from "../../kustomer.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "kustomer-update-conversation", + name: "Update Conversation", + description: "Updates an existing conversation in Kustomer. [See the documentation]().", + version: "0.0.{{ts}}", + type: "action", + props: { + kustomer, + conversationId: { + propDefinition: [ + kustomer, + "conversationId", + ], + }, + externalId: { + propDefinition: [ + kustomer, + "externalId", + ], + optional: true, + }, + name: { + propDefinition: [ + kustomer, + "name", + ], + optional: true, + }, + direction: { + propDefinition: [ + kustomer, + "direction", + ], + optional: true, + }, + priority: { + propDefinition: [ + kustomer, + "priority", + ], + optional: true, + }, + satisfaction: { + propDefinition: [ + kustomer, + "satisfaction", + ], + optional: true, + }, + satisfactionLevel: { + propDefinition: [ + kustomer, + "satisfactionLevel", + ], + optional: true, + }, + suggestedShortcuts: { + propDefinition: [ + kustomer, + "suggestedShortcuts", + ], + optional: true, + }, + status: { + propDefinition: [ + kustomer, + "status", + ], + optional: true, + }, + replyChannel: { + propDefinition: [ + kustomer, + "replyChannel", + ], + optional: true, + }, + subStatus: { + propDefinition: [ + kustomer, + "subStatus", + ], + optional: true, + }, + snooze: { + propDefinition: [ + kustomer, + "snooze", + ], + optional: true, + }, + tags: { + propDefinition: [ + kustomer, + "tags", + ], + optional: true, + }, + suggestedTags: { + propDefinition: [ + kustomer, + "suggestedTags", + ], + optional: true, + }, + sentiment: { + propDefinition: [ + kustomer, + "sentiment", + ], + optional: true, + }, + assignedUsers: { + propDefinition: [ + kustomer, + "assignedUsers", + ], + optional: true, + }, + assignedTeams: { + propDefinition: [ + kustomer, + "assignedTeams", + ], + optional: true, + }, + deleted: { + propDefinition: [ + kustomer, + "deleted", + ], + optional: true, + }, + ended: { + propDefinition: [ + kustomer, + "ended", + ], + optional: true, + }, + endedAt: { + propDefinition: [ + kustomer, + "endedAt", + ], + optional: true, + }, + endedReason: { + propDefinition: [ + kustomer, + "endedReason", + ], + optional: true, + }, + endedBy: { + propDefinition: [ + kustomer, + "endedBy", + ], + optional: true, + }, + endedByType: { + propDefinition: [ + kustomer, + "endedByType", + ], + optional: true, + }, + locked: { + propDefinition: [ + kustomer, + "locked", + ], + optional: true, + }, + rev: { + propDefinition: [ + kustomer, + "rev", + ], + optional: true, + }, + defaultLang: { + propDefinition: [ + kustomer, + "defaultLang", + ], + optional: true, + }, + queue: { + propDefinition: [ + kustomer, + "queue", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.kustomer.updateConversation({ + conversationId: this.conversationId, + externalId: this.externalId, + name: this.name, + direction: this.direction, + priority: this.priority, + satisfaction: this.satisfaction, + satisfactionLevel: this.satisfactionLevel, + suggestedShortcuts: this.suggestedShortcuts, + status: this.status, + replyChannel: this.replyChannel, + subStatus: this.subStatus, + snooze: this.snooze, + tags: this.tags, + suggestedTags: this.suggestedTags, + sentiment: this.sentiment, + assignedUsers: this.assignedUsers, + assignedTeams: this.assignedTeams, + deleted: this.deleted, + ended: this.ended, + endedAt: this.endedAt, + endedReason: this.endedReason, + endedBy: this.endedBy, + endedByType: this.endedByType, + locked: this.locked, + rev: this.rev, + defaultLang: this.defaultLang, + queue: this.queue, + }); + + $.export("$summary", `Conversation ${this.conversationId} updated successfully`); + return response; + }, +}; diff --git a/components/kustomer/actions/update-customer/update-customer.mjs b/components/kustomer/actions/update-customer/update-customer.mjs new file mode 100644 index 0000000000000..14479a3bcc614 --- /dev/null +++ b/components/kustomer/actions/update-customer/update-customer.mjs @@ -0,0 +1,290 @@ +import kustomer from "../../kustomer.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "kustomer-update-customer", + name: "Update Customer", + description: "Updates an existing customer in Kustomer. [See the documentation](https://developer.kustomer.com/kustomer-api-docs/reference/updatecustomer)", + version: "0.0.{{ts}}", + type: "action", + props: { + kustomer: { + type: "app", + app: "kustomer", + }, + customerId: { + propDefinition: [ + kustomer, + "customerId", + ], + }, + name: { + propDefinition: [ + kustomer, + "name", + ], + optional: true, + }, + company: { + propDefinition: [ + kustomer, + "company", + ], + optional: true, + }, + externalId: { + propDefinition: [ + kustomer, + "externalId", + ], + optional: true, + }, + username: { + propDefinition: [ + kustomer, + "username", + ], + optional: true, + }, + signedUpAt: { + propDefinition: [ + kustomer, + "signedUpAt", + ], + optional: true, + }, + lastActivityAt: { + propDefinition: [ + kustomer, + "lastActivityAt", + ], + optional: true, + }, + lastCustomerActivityAt: { + propDefinition: [ + kustomer, + "lastCustomerActivityAt", + ], + optional: true, + }, + lastSeenAt: { + propDefinition: [ + kustomer, + "lastSeenAt", + ], + optional: true, + }, + avatarUrl: { + propDefinition: [ + kustomer, + "avatarUrl", + ], + optional: true, + }, + externalIds: { + propDefinition: [ + kustomer, + "externalIds", + ], + optional: true, + }, + sharedExternalIds: { + propDefinition: [ + kustomer, + "sharedExternalIds", + ], + optional: true, + }, + emails: { + propDefinition: [ + kustomer, + "emails", + ], + optional: true, + }, + sharedEmails: { + propDefinition: [ + kustomer, + "sharedEmails", + ], + optional: true, + }, + phones: { + propDefinition: [ + kustomer, + "phones", + ], + optional: true, + }, + sharedPhones: { + propDefinition: [ + kustomer, + "sharedPhones", + ], + optional: true, + }, + whatsApps: { + propDefinition: [ + kustomer, + "whatsApps", + ], + optional: true, + }, + facebookIds: { + propDefinition: [ + kustomer, + "facebookIds", + ], + optional: true, + }, + instagramIds: { + propDefinition: [ + kustomer, + "instagramIds", + ], + optional: true, + }, + socials: { + propDefinition: [ + kustomer, + "socials", + ], + optional: true, + }, + sharedSocials: { + propDefinition: [ + kustomer, + "sharedSocials", + ], + optional: true, + }, + urls: { + propDefinition: [ + kustomer, + "urls", + ], + optional: true, + }, + locations: { + propDefinition: [ + kustomer, + "locations", + ], + optional: true, + }, + locale: { + propDefinition: [ + kustomer, + "locale", + ], + optional: true, + }, + timeZone: { + propDefinition: [ + kustomer, + "timeZone", + ], + optional: true, + }, + tags: { + propDefinition: [ + kustomer, + "tags", + ], + optional: true, + }, + sentiment: { + propDefinition: [ + kustomer, + "sentiment", + ], + optional: true, + }, + birthdayAt: { + propDefinition: [ + kustomer, + "birthdayAt", + ], + optional: true, + }, + gender: { + propDefinition: [ + kustomer, + "gender", + ], + optional: true, + }, + createdAt: { + propDefinition: [ + kustomer, + "createdAt", + ], + optional: true, + }, + importedAt: { + propDefinition: [ + kustomer, + "importedAt", + ], + optional: true, + }, + rev: { + propDefinition: [ + kustomer, + "rev", + ], + optional: true, + }, + defaultLang: { + propDefinition: [ + kustomer, + "defaultLang", + ], + optional: true, + }, + }, + async run({ $ }) { + const updateData = { + name: this.name, + company: this.company, + externalId: this.externalId, + username: this.username, + signedUpAt: this.signedUpAt, + lastActivityAt: this.lastActivityAt, + lastCustomerActivityAt: this.lastCustomerActivityAt, + lastSeenAt: this.lastSeenAt, + avatarUrl: this.avatarUrl, + externalIds: this.externalIds, + sharedExternalIds: this.sharedExternalIds, + emails: this.emails, + sharedEmails: this.sharedEmails, + phones: this.phones, + sharedPhones: this.sharedPhones, + whatsApps: this.whatsApps, + facebookIds: this.facebookIds, + instagramIds: this.instagramIds, + socials: this.socials, + sharedSocials: this.sharedSocials, + urls: this.urls, + locations: this.locations, + locale: this.locale, + timeZone: this.timeZone, + tags: this.tags, + sentiment: this.sentiment, + birthdayAt: this.birthdayAt, + gender: this.gender, + createdAt: this.createdAt, + importedAt: this.importedAt, + rev: this.rev, + defaultLang: this.defaultLang, + }; + + const response = await this.kustomer.updateCustomer({ + customerId: this.customerId, + ...updateData, + }); + + $.export("$summary", `Successfully updated customer with ID ${this.customerId}`); + return response; + }, +}; diff --git a/components/kustomer/kustomer.app.mjs b/components/kustomer/kustomer.app.mjs index 80387a512a261..1fa5b7de071fe 100644 --- a/components/kustomer/kustomer.app.mjs +++ b/components/kustomer/kustomer.app.mjs @@ -1,11 +1,682 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "kustomer", - propDefinitions: {}, + version: "0.0.{{ts}}", + propDefinitions: { + // Required Props + customerId: { + type: "string", + label: "Customer ID", + description: "Unique identifier for the customer", + }, + conversationId: { + type: "string", + label: "Conversation ID", + description: "Unique identifier for the conversation", + }, + // Optional Props for Conversation Creation + externalId: { + type: "string", + label: "External ID", + description: "External identifier", + optional: true, + }, + name: { + type: "string", + label: "Name", + description: "Name of the resource", + optional: true, + }, + status: { + type: "string", + label: "Status", + description: "Status of the conversation", + optional: true, + }, + priority: { + type: "integer", + label: "Priority", + description: "Priority level (1-5)", + optional: true, + min: 1, + max: 5, + }, + direction: { + type: "string", + label: "Direction", + description: "Direction of the conversation", + optional: true, + }, + replyChannel: { + type: "string", + label: "Reply Channel", + description: "Channel to reply to", + optional: true, + }, + tags: { + type: "string[]", + label: "Tags", + description: "Tags associated with the resource", + optional: true, + async options() { + const tags = await this.listTags(); + return tags.map((tag) => ({ + label: tag.name, + value: tag.id, + })); + }, + }, + assignedTeams: { + type: "string[]", + label: "Assigned Teams", + description: "Teams assigned to the resource", + optional: true, + async options() { + const teams = await this.listAssignedTeams(); + return teams.map((team) => ({ + label: team.name, + value: team.id, + })); + }, + }, + suggestedTags: { + type: "string[]", + label: "Suggested Tags", + description: "Suggested tags for the resource", + optional: true, + }, + sentiment: { + type: "string", + label: "Sentiment", + description: "Sentiment associated with the conversation", + optional: true, + }, + suggestedShortcuts: { + type: "string[]", + label: "Suggested Shortcuts", + description: "Suggested shortcuts for the conversation", + optional: true, + }, + subStatus: { + type: "string", + label: "Sub Status", + description: "Sub-status of the conversation", + optional: true, + }, + snooze: { + type: "object", + label: "Snooze", + description: "Snooze status", + optional: true, + }, + deleted: { + type: "boolean", + label: "Deleted", + description: "Whether the conversation is deleted", + optional: true, + }, + ended: { + type: "boolean", + label: "Ended", + description: "Whether the conversation has ended", + optional: true, + }, + endedAt: { + type: "string", + label: "Ended At", + description: "Datetime when the conversation ended", + optional: true, + }, + endedReason: { + type: "string", + label: "Ended Reason", + description: "Reason why the conversation ended", + optional: true, + }, + endedBy: { + type: "string", + label: "Ended By", + description: "Identifier of who ended the conversation", + optional: true, + }, + endedByType: { + type: "string", + label: "Ended By Type", + description: "Type of entity that ended the conversation", + optional: true, + }, + locked: { + type: "boolean", + label: "Locked", + description: "Whether the conversation is locked", + optional: true, + }, + rev: { + type: "integer", + label: "Revision", + description: "Revision number", + optional: true, + }, + defaultLang: { + type: "string", + label: "Default Language", + description: "Default language for the resource", + optional: true, + }, + queue: { + type: "string", + label: "Queue", + description: "Queue information", + optional: true, + }, + // Optional Props for Customer Creation and Update + company: { + type: "string", + label: "Company", + description: "Company of the customer", + optional: true, + }, + username: { + type: "string", + label: "Username", + description: "Username of the customer", + optional: true, + }, + signedUpAt: { + type: "string", + label: "Signed Up At", + description: "Signup datetime", + optional: true, + }, + lastActivityAt: { + type: "string", + label: "Last Activity At", + description: "Last activity datetime", + optional: true, + }, + lastCustomerActivityAt: { + type: "string", + label: "Last Customer Activity At", + description: "Last customer activity datetime", + optional: true, + }, + lastSeenAt: { + type: "string", + label: "Last Seen At", + description: "Last seen datetime", + optional: true, + }, + avatarUrl: { + type: "string", + label: "Avatar URL", + description: "URL to the avatar", + optional: true, + }, + externalIds: { + type: "string[]", + label: "External IDs", + description: "External identifiers", + optional: true, + }, + sharedExternalIds: { + type: "string[]", + label: "Shared External IDs", + description: "Shared external identifiers", + optional: true, + }, + emails: { + type: "string[]", + label: "Emails", + description: "Emails of the customer", + optional: true, + }, + sharedEmails: { + type: "string[]", + label: "Shared Emails", + description: "Shared emails", + optional: true, + }, + phones: { + type: "string[]", + label: "Phones", + description: "Phone numbers of the customer", + optional: true, + }, + sharedPhones: { + type: "string[]", + label: "Shared Phones", + description: "Shared phone numbers", + optional: true, + }, + whatsApps: { + type: "string[]", + label: "WhatsApps", + description: "WhatsApp numbers of the customer", + optional: true, + }, + facebookIds: { + type: "string[]", + label: "Facebook IDs", + description: "Facebook IDs of the customer", + optional: true, + }, + instagramIds: { + type: "string[]", + label: "Instagram IDs", + description: "Instagram IDs of the customer", + optional: true, + }, + socials: { + type: "string[]", + label: "Socials", + description: "Social media accounts", + optional: true, + }, + sharedSocials: { + type: "string[]", + label: "Shared Socials", + description: "Shared social media accounts", + optional: true, + }, + urls: { + type: "string[]", + label: "URLs", + description: "Associated URLs", + optional: true, + }, + locations: { + type: "string[]", + label: "Locations", + description: "Customer locations", + optional: true, + }, + locale: { + type: "string", + label: "Locale", + description: "Locale of the customer", + optional: true, + }, + timeZone: { + type: "string", + label: "Time Zone", + description: "Time zone of the customer", + optional: true, + }, + birthdayAt: { + type: "string", + label: "Birthday At", + description: "Birthday datetime", + optional: true, + }, + gender: { + type: "string", + label: "Gender", + description: "Gender of the customer", + optional: true, + }, + createdAt: { + type: "string", + label: "Created At", + description: "Creation datetime", + optional: true, + }, + importedAt: { + type: "string", + label: "Imported At", + description: "Import datetime", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.kustomerapp.com/v1"; + }, + async _makeRequest(opts = {}) { + const { + $, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + method, + url: this._baseUrl() + path, + headers: { + ...headers, + "Authorization": `Bearer ${this.$auth.access_token}`, + "Content-Type": "application/json", + }, + ...otherOpts, + }); + }, + // Conversation Methods + async createConversation(opts = {}) { + const { + customerId, + externalId, + name, + status, + priority, + direction, + replyChannel, + tags, + assignedTeams, + defaultLang, + queue, + } = opts; + const data = { + customer: customerId, + externalId, + name, + status, + priority, + direction, + replyChannel, + tags, + assignedTeams, + defaultLang, + queue, + }; + return this._makeRequest({ + method: "POST", + path: "/conversations", + data, + }); + }, + async updateConversation(opts = {}) { + const { + conversationId, + externalId, + name, + direction, + priority, + satisfaction, + satisfactionLevel, + suggestedShortcuts, + status, + replyChannel, + subStatus, + snooze, + tags, + suggestedTags, + sentiment, + assignedUsers, + assignedTeams, + deleted, + ended, + endedAt, + endedReason, + endedBy, + endedByType, + locked, + rev, + defaultLang, + queue, + } = opts; + const data = { + externalId, + name, + direction, + priority, + satisfaction, + satisfactionLevel, + suggestedShortcuts, + status, + replyChannel, + subStatus, + snooze, + tags, + suggestedTags, + sentiment, + assignedUsers, + assignedTeams, + deleted, + ended, + endedAt, + endedReason, + endedBy, + endedByType, + locked, + rev, + defaultLang, + queue, + }; + return this._makeRequest({ + method: "PUT", + path: `/conversations/${conversationId}`, + data, + }); + }, + // Customer Methods + async createCustomer(opts = {}) { + const { + name, + company, + externalId, + username, + signedUpAt, + lastActivityAt, + lastCustomerActivityAt, + lastSeenAt, + avatarUrl, + externalIds, + sharedExternalIds, + emails, + sharedEmails, + phones, + sharedPhones, + whatsApps, + facebookIds, + instagramIds, + socials, + sharedSocials, + urls, + locations, + locale, + timeZone, + tags, + sentiment, + birthdayAt, + gender, + createdAt, + importedAt, + rev, + defaultLang, + } = opts; + const data = { + name, + company, + externalId, + username, + signedUpAt, + lastActivityAt, + lastCustomerActivityAt, + lastSeenAt, + avatarUrl, + externalIds, + sharedExternalIds, + emails, + sharedEmails, + phones, + sharedPhones, + whatsApps, + facebookIds, + instagramIds, + socials, + sharedSocials, + urls, + locations, + locale, + timeZone, + tags, + sentiment, + birthdayAt, + gender, + createdAt, + importedAt, + rev, + defaultLang, + }; + return this._makeRequest({ + method: "POST", + path: "/customers", + data, + }); + }, + async updateCustomer(opts = {}) { + const { + customerId, + name, + company, + externalId, + username, + signedUpAt, + lastActivityAt, + lastCustomerActivityAt, + lastSeenAt, + avatarUrl, + externalIds, + sharedExternalIds, + emails, + sharedEmails, + phones, + sharedPhones, + whatsApps, + facebookIds, + instagramIds, + socials, + sharedSocials, + urls, + locations, + locale, + timeZone, + tags, + sentiment, + birthdayAt, + gender, + createdAt, + importedAt, + rev, + defaultLang, + } = opts; + const data = { + name, + company, + externalId, + username, + signedUpAt, + lastActivityAt, + lastCustomerActivityAt, + lastSeenAt, + avatarUrl, + externalIds, + sharedExternalIds, + emails, + sharedEmails, + phones, + sharedPhones, + whatsApps, + facebookIds, + instagramIds, + socials, + sharedSocials, + urls, + locations, + locale, + timeZone, + tags, + sentiment, + birthdayAt, + gender, + createdAt, + importedAt, + rev, + defaultLang, + }; + return this._makeRequest({ + method: "PUT", + path: `/customers/${customerId}`, + data, + }); + }, + // Listing Methods + async listTags(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/tags", + ...opts, + }); + }, + async listAssignedTeams(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/teams", + ...opts, + }); + }, + async listAssignedUsers(opts = {}) { + return this._makeRequest({ + method: "GET", + path: "/users", + ...opts, + }); + }, + // Event Emission Methods + async emitConversationCreateEvent(name, url) { + return { + name, + url, + event: "kustomer.conversation.create", + }; + }, + async emitCustomerCreateEvent(name, url) { + return { + name, + url, + event: "kustomer.customer.create", + }; + }, + async emitMessageCreateEvent(name, url) { + return { + name, + url, + event: "kustomer.message.create", + }; + }, + async emitConversationUpdateEvent(name, url) { + return { + name, + url, + event: "kustomer.conversation.update", + }; + }, + async emitCustomerUpdateEvent(name, url) { + return { + name, + url, + event: "kustomer.customer.update", + }; + }, + // Pagination Method + async paginate(fn, ...opts) { + let results = []; + let hasMore = true; + let page = 1; + while (hasMore) { + const response = await fn({ + page, + ...opts, + }); + if (response.length === 0) { + hasMore = false; + } else { + results = results.concat(response); + page += 1; + } + } + return results; + }, }, -}; \ No newline at end of file +}; diff --git a/components/kustomer/package.json b/components/kustomer/package.json index c6b02118b4303..f90461f32493d 100644 --- a/components/kustomer/package.json +++ b/components/kustomer/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/kustomer/sources/new-conversation-instant/new-conversation-instant.mjs b/components/kustomer/sources/new-conversation-instant/new-conversation-instant.mjs new file mode 100644 index 0000000000000..539a91dcbff76 --- /dev/null +++ b/components/kustomer/sources/new-conversation-instant/new-conversation-instant.mjs @@ -0,0 +1,84 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import kustomer from "../../kustomer.app.mjs"; + +export default { + key: "kustomer-new-conversation-instant", + name: "New Conversation Created", + description: "Emit new event when a conversation is created in Kustomer. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + kustomer: { + type: "app", + app: "kustomer", + }, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + const lastRun = (await this.db.get("lastRun")) || new Date(0).toISOString(); + const conversations = await this.kustomer.listConversations(lastRun); + + const sortedConversations = conversations.sort( + (a, b) => new Date(a.createdAt) - new Date(b.createdAt), + ); + + const limitedConversations = sortedConversations.slice(-50); + + for (const conv of limitedConversations) { + const eventData = await this.kustomer.emitConversationCreateEvent(conv.name, conv.url); + this.$emit(eventData, { + id: conv.id || conv.createdAt, + summary: `New Conversation: ${conv.name}`, + ts: Date.parse(conv.createdAt) || Date.now(), + }); + } + + if (limitedConversations.length > 0) { + const latestCreatedAt = Math.max(...limitedConversations.map((conv) => Date.parse(conv.createdAt))); + await this.db.set("lastRun", new Date(latestCreatedAt).toISOString()); + } + }, + async activate() { + // Implement webhook subscription if supported by Kustomer + // Example: + // await this.kustomer.subscribeToConversations(); + }, + async deactivate() { + // Implement webhook unsubscription if supported by Kustomer + // Example: + // await this.kustomer.unsubscribeFromConversations(); + }, + }, + async run() { + const lastRun = (await this.db.get("lastRun")) || new Date(0).toISOString(); + const conversations = await this.kustomer.listConversations(lastRun); + + const sortedConversations = conversations.sort( + (a, b) => new Date(a.createdAt) - new Date(b.createdAt), + ); + + for (const conv of sortedConversations) { + const eventData = await this.kustomer.emitConversationCreateEvent(conv.name, conv.url); + this.$emit(eventData, { + id: conv.id || conv.createdAt, + summary: `New Conversation: ${conv.name}`, + ts: Date.parse(conv.createdAt) || Date.now(), + }); + } + + if (conversations.length > 0) { + const latestCreatedAt = Math.max(...conversations.map((conv) => Date.parse(conv.createdAt))); + await this.db.set("lastRun", new Date(latestCreatedAt).toISOString()); + } + }, +}; diff --git a/components/kustomer/sources/new-customer-instant/new-customer-instant.mjs b/components/kustomer/sources/new-customer-instant/new-customer-instant.mjs new file mode 100644 index 0000000000000..31abb5284198a --- /dev/null +++ b/components/kustomer/sources/new-customer-instant/new-customer-instant.mjs @@ -0,0 +1,81 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import kustomer from "../../kustomer.app.mjs"; + +export default { + key: "kustomer-new-customer-instant", + name: "New Customer Created", + description: "Emit new event when a new customer is added to Kustomer. [See the documentation]().", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + kustomer: { + type: "app", + app: "kustomer", + }, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + const customers = await this.kustomer.listCustomers({ + limit: 50, + sort: "-createdAt", + }); + for (const customer of customers) { + this.$emit( + { + name: customer.name, + url: customer.urls?.[0] || "", + }, + { + id: customer.id, + summary: `New Kustomer Customer: ${customer.name}`, + ts: Date.parse(customer.createdAt) || Date.now(), + }, + ); + } + if (customers.length > 0) { + this.db.set("last_created_at", customers[0].createdAt); + } + }, + async activate() { + // No activation steps needed for polling source + }, + async deactivate() { + // No deactivation steps needed for polling source + }, + }, + async run() { + const lastCreatedAt = (await this.db.get("last_created_at")) || new Date(0).toISOString(); + const newCustomers = await this.kustomer.listCustomers({ + filter: `createdAt > "${lastCreatedAt}"`, + sort: "createdAt", + limit: 50, + }); + for (const customer of newCustomers) { + this.$emit( + { + name: customer.name, + url: customer.urls?.[0] || "", + }, + { + id: customer.id, + summary: `New Kustomer Customer: ${customer.name}`, + ts: Date.parse(customer.createdAt) || Date.now(), + }, + ); + } + if (newCustomers.length > 0) { + const latestCreatedAt = newCustomers[newCustomers.length - 1].createdAt; + this.db.set("last_created_at", latestCreatedAt); + } + }, +}; diff --git a/components/kustomer/sources/new-message-instant/new-message-instant.mjs b/components/kustomer/sources/new-message-instant/new-message-instant.mjs new file mode 100644 index 0000000000000..78ec19db3b912 --- /dev/null +++ b/components/kustomer/sources/new-message-instant/new-message-instant.mjs @@ -0,0 +1,122 @@ +import { axios } from "@pipedream/platform"; +import kustomer from "../../kustomer.app.mjs"; + +export default { + key: "kustomer-new-message-instant", + name: "New Message Created in Conversation", + description: + "Emit new event when a new message is created in a conversation. [See the documentation]().", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + kustomer: { + type: "app", + app: "kustomer", + }, + name: { + type: "string", + label: "Name", + description: "Name associated with the message event.", + }, + url: { + type: "string", + label: "URL", + description: "URL associated with the message event.", + }, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + }, + hooks: { + async deploy() { + const lastTs = 0; + await this.db.set("last_ts", lastTs); + const messages = await this.kustomer.listMessages({ + perpage: 50, + sort: "desc", + }); + for (const message of messages.reverse()) { + const eventData = { + message, + name: this.name, + url: this.url, + }; + this.$emit( + eventData, + { + id: message.id, + summary: `New Kustomer message: ${message.content + ? message.content.substring(0, 20) + "..." + : "New message created."}`, + ts: Date.parse(message.createdAt) || Date.now(), + event: "kustomer.message.create", + }, + ); + } + if (messages.length > 0) { + const latestMessage = messages[0]; + const latestTimestamp = Date.parse(latestMessage.createdAt); + await this.db.set("last_ts", latestTimestamp); + } + }, + async activate() { + // Code to create webhook subscription if needed + }, + async deactivate() { + // Code to delete webhook subscription if needed + }, + }, + methods: { + async listMessages(params = {}) { + return await this.kustomer._makeRequest({ + method: "GET", + path: "/messages", + params, + }); + }, + async getLastTimestamp() { + return this.db.get("last_ts") || 0; + }, + async setLastTimestamp(timestamp) { + await this.db.set("last_ts", timestamp); + }, + }, + async run() { + const lastTimestamp = await this.getLastTimestamp(); + const newMessages = await this.listMessages({ + since: lastTimestamp, + perpage: 100, + sort: "asc", + }); + + for (const message of newMessages) { + const eventData = { + message, + name: this.name, + url: this.url, + }; + this.$emit( + eventData, + { + id: message.id, + summary: `New Kustomer message: ${message.content + ? message.content.substring(0, 20) + "..." + : "New message created."}`, + ts: Date.parse(message.createdAt) || Date.now(), + event: "kustomer.message.create", + }, + ); + } + + if (newMessages.length > 0) { + const latestMessage = newMessages[newMessages.length - 1]; + const latestTimestamp = Date.parse(latestMessage.createdAt); + await this.setLastTimestamp(latestTimestamp); + } + }, +}; diff --git a/components/kustomer/sources/updated-conversation-instant/updated-conversation-instant.mjs b/components/kustomer/sources/updated-conversation-instant/updated-conversation-instant.mjs new file mode 100644 index 0000000000000..a3d1836668b05 --- /dev/null +++ b/components/kustomer/sources/updated-conversation-instant/updated-conversation-instant.mjs @@ -0,0 +1,113 @@ +import { axios } from "@pipedream/platform"; +import kustomer from "../../kustomer.app.mjs"; + +export default { + key: "kustomer-updated-conversation-instant", + name: "Updated Conversation Instant", + description: "Emit new event when an existing conversation is updated in Kustomer. [See the documentation]().", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + kustomer, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + name: { + type: "string", + label: "Name", + description: "Name of the resource", + }, + url: { + type: "string", + label: "URL", + description: "Webhook URL or similar", + }, + }, + hooks: { + async deploy() { + const lastTimestamp = await this.db.get("lastTimestamp") || 0; + const conversations = await this.kustomer.paginate(this.kustomer.listConversations, { + query: { + updated_at_gt: lastTimestamp, + }, + perpage: 50, + }); + + // Sort conversations by updatedAt in descending order + conversations.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); + + const latestConvos = conversations.slice(0, 50); + let latestTimestamp = lastTimestamp; + + for (const convo of latestConvos) { + const updatedAt = Date.parse(convo.updatedAt) || Date.now(); + const eventData = await this.kustomer.emitConversationUpdateEvent(this.name, this.url); + + this.$emit( + { + name: convo.name, + url: convo.url, + ...eventData, + }, + { + id: convo.id || updatedAt, + summary: `Conversation Updated: ${convo.name}`, + ts: updatedAt, + }, + ); + + if (updatedAt > latestTimestamp) { + latestTimestamp = updatedAt; + } + } + + await this.db.set("lastTimestamp", latestTimestamp); + }, + async activate() { + // No activation steps required for polling source + }, + async deactivate() { + // No deactivation steps required for polling source + }, + }, + async run() { + const lastTimestamp = await this.db.get("lastTimestamp") || 0; + const conversations = await this.kustomer.paginate(this.kustomer.listConversations, { + query: { + updated_at_gt: lastTimestamp, + }, + perpage: 50, + }); + + let latestTimestamp = lastTimestamp; + + for (const convo of conversations) { + const updatedAt = Date.parse(convo.updatedAt) || Date.now(); + const eventData = await this.kustomer.emitConversationUpdateEvent(this.name, this.url); + + this.$emit( + { + name: convo.name, + url: convo.url, + ...eventData, + }, + { + id: convo.id || updatedAt, + summary: `Conversation Updated: ${convo.name}`, + ts: updatedAt, + }, + ); + + if (updatedAt > latestTimestamp) { + latestTimestamp = updatedAt; + } + } + + await this.db.set("lastTimestamp", latestTimestamp); + }, +}; diff --git a/components/kustomer/sources/updated-customer-instant/updated-customer-instant.mjs b/components/kustomer/sources/updated-customer-instant/updated-customer-instant.mjs new file mode 100644 index 0000000000000..528e2ad008bb3 --- /dev/null +++ b/components/kustomer/sources/updated-customer-instant/updated-customer-instant.mjs @@ -0,0 +1,111 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import kustomer from "../../kustomer.app.mjs"; + +export default { + key: "kustomer-updated-customer-instant", + name: "Updated Customer", + description: "Emit a new event when an existing customer's details are updated in Kustomer. [See the documentation]()", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + kustomer, + db: { + type: "$.service.db", + secret: false, + }, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + const customers = await this._getCustomers({ + limit: 50, + sort: "-updatedAt", + }); + + for (const customer of customers) { + this.$emit( + { + name: customer.name, + url: customer.url, + event: "kustomer.customer.update", + }, + { + id: customer.id, + summary: `Updated customer: ${customer.name}`, + ts: new Date(customer.updatedAt).getTime(), + }, + ); + } + + if (customers.length > 0) { + const latestUpdatedAt = Math.max(...customers.map((c) => new Date(c.updatedAt).getTime())); + this.db.set("lastUpdatedAt", latestUpdatedAt); + } + }, + async activate() { + // No activation logic required for polling source + }, + async deactivate() { + // No deactivation logic required for polling source + }, + }, + methods: { + async _getCustomers({ + limit = 50, sort = "-updatedAt", since = null, + }) { + const params = { + limit, + sort, + }; + if (since) { + params.filter = { + updatedAt: { + gte: new Date(since).toISOString(), + }, + }; + } + + return await this.kustomer._makeRequest({ + path: "/customers", + params, + }); + }, + }, + async run() { + const lastUpdatedAt = this.db.get("lastUpdatedAt") || 0; + const customers = await this._getCustomers({ + limit: 50, + sort: "updatedAt", + since: lastUpdatedAt, + }); + + for (const customer of customers) { + this.$emit( + { + name: customer.name, + url: customer.url, + event: "kustomer.customer.update", + }, + { + id: customer.id, + summary: `Updated customer: ${customer.name}`, + ts: new Date(customer.updatedAt).getTime(), + }, + ); + + const customerUpdatedAt = new Date(customer.updatedAt).getTime(); + + if (customerUpdatedAt > lastUpdatedAt) { + this.db.set("lastUpdatedAt", customerUpdatedAt); + } + } + }, +}; From 396dabd843e91e561548dfa4291ab281ab1daa6c Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 21 Mar 2025 17:28:38 -0300 Subject: [PATCH 2/4] [Components] kustomer #15920 Sources - New Conversation (Instant) - New Customer (Instant) - New Message (Instant) - Updated Conversation (Instant) - Updated Customer (Instant) Actions - Create Conversation - Update Conversation - Create Customer - Update Customer --- .../create-conversation.mjs | 119 +-- .../create-customer/create-customer.mjs | 249 +++--- .../update-conversation.mjs | 190 +---- .../update-customer/update-customer.mjs | 208 ++---- components/kustomer/common/constants.mjs | 337 +++++++++ components/kustomer/common/utils.mjs | 34 + components/kustomer/kustomer.app.mjs | 706 +++++------------- components/kustomer/package.json | 5 +- components/kustomer/sources/common/base.mjs | 49 ++ .../new-conversation-instant.mjs | 90 +-- .../new-conversation-instant/test-event.mjs | 192 +++++ .../new-customer-instant.mjs | 87 +-- .../new-customer-instant/test-event.mjs | 111 +++ .../new-message-instant.mjs | 126 +--- .../new-message-instant/test-event.mjs | 115 +++ .../test-event.mjs | 250 +++++++ .../updated-conversation-instant.mjs | 119 +-- .../updated-customer-instant/test-event.mjs | 222 ++++++ .../updated-customer-instant.mjs | 117 +-- 19 files changed, 1839 insertions(+), 1487 deletions(-) create mode 100644 components/kustomer/common/constants.mjs create mode 100644 components/kustomer/common/utils.mjs create mode 100644 components/kustomer/sources/common/base.mjs create mode 100644 components/kustomer/sources/new-conversation-instant/test-event.mjs create mode 100644 components/kustomer/sources/new-customer-instant/test-event.mjs create mode 100644 components/kustomer/sources/new-message-instant/test-event.mjs create mode 100644 components/kustomer/sources/updated-conversation-instant/test-event.mjs create mode 100644 components/kustomer/sources/updated-customer-instant/test-event.mjs diff --git a/components/kustomer/actions/create-conversation/create-conversation.mjs b/components/kustomer/actions/create-conversation/create-conversation.mjs index b48458a916c14..68c3416e212fe 100644 --- a/components/kustomer/actions/create-conversation/create-conversation.mjs +++ b/components/kustomer/actions/create-conversation/create-conversation.mjs @@ -1,59 +1,56 @@ +import { + parseObject, throwError, +} from "../../common/utils.mjs"; import kustomer from "../../kustomer.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "kustomer-create-conversation", name: "Create Conversation", - description: "Creates a new conversation in Kustomer. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Creates a new conversation in Kustomer. [See the documentation](https://developer.kustomer.com/kustomer-api-docs/reference/createaconversation)", + version: "0.0.1", type: "action", props: { kustomer, - - // Required prop - customerId: { - type: "string", - label: "Customer ID", - description: "Unique identifier for the customer", + customer: { + propDefinition: [ + kustomer, + "customerId", + ], }, - - // Optional props externalId: { - type: "string", - label: "External ID", - description: "External identifier", + propDefinition: [ + kustomer, + "externalId", + ], optional: true, }, name: { - type: "string", - label: "Name", + propDefinition: [ + kustomer, + "name", + ], description: "Name of the conversation", optional: true, }, status: { - type: "string", - label: "Status", - description: "Status of the conversation", + propDefinition: [ + kustomer, + "status", + ], optional: true, }, priority: { - type: "integer", - label: "Priority", - description: "Priority level (1-5)", + propDefinition: [ + kustomer, + "priority", + ], optional: true, - min: 1, - max: 5, }, direction: { - type: "string", - label: "Direction", - description: "Direction of the conversation", - optional: true, - }, - replyChannel: { - type: "string", - label: "Reply Channel", - description: "Channel to reply to", + propDefinition: [ + kustomer, + "direction", + ], optional: true, }, tags: { @@ -78,35 +75,45 @@ export default { optional: true, }, defaultLang: { - type: "string", - label: "Default Language", + propDefinition: [ + kustomer, + "defaultLang", + ], description: "Default language for the conversation", optional: true, }, - queue: { - type: "string", - label: "Queue", - description: "Queue information", - optional: true, + queueId: { + propDefinition: [ + kustomer, + "queueId", + ], }, }, async run({ $ }) { - const response = await this.kustomer.createConversation({ - customerId: this.customerId, - externalId: this.externalId, - name: this.name, - status: this.status, - priority: this.priority, - direction: this.direction, - replyChannel: this.replyChannel, - tags: this.tags, - assignedUsers: this.assignedUsers, - assignedTeams: this.assignedTeams, - defaultLang: this.defaultLang, - queue: this.queue, - }); + try { + const response = await this.kustomer.createConversation({ + $, + data: { + customer: this.customer, + externalId: this.externalId, + name: this.name, + status: this.status, + priority: this.priority, + direction: this.direction, + tags: parseObject(this.tags), + assignedUsers: parseObject(this.assignedUsers), + assignedTeams: parseObject(this.assignedTeams), + defaultLang: this.defaultLang, + queue: { + id: this.queueId, + }, + }, + }); - $.export("$summary", `Created conversation with ID ${response.id}`); - return response; + $.export("$summary", `Created conversation with ID ${response.data.id}`); + return response; + } catch ({ message }) { + throwError(message); + } }, }; diff --git a/components/kustomer/actions/create-customer/create-customer.mjs b/components/kustomer/actions/create-customer/create-customer.mjs index 85c9558253715..a2987830f4e81 100644 --- a/components/kustomer/actions/create-customer/create-customer.mjs +++ b/components/kustomer/actions/create-customer/create-customer.mjs @@ -1,248 +1,203 @@ -import { axios } from "@pipedream/platform"; +import { + parseObject, throwError, +} from "../../common/utils.mjs"; import kustomer from "../../kustomer.app.mjs"; export default { key: "kustomer-create-customer", name: "Create Customer", - description: "Creates a new customer in Kustomer. [See the documentation]()", - version: "0.0.{{ts}}", + description: "Creates a new customer in Kustomer. [See the documentation](https://developer.kustomer.com/kustomer-api-docs/reference/createacustomer)", + version: "0.0.1", type: "action", props: { - kustomer: { - type: "app", - app: "kustomer", - }, + kustomer, name: { propDefinition: [ - "kustomer", + kustomer, "name", ], + optional: true, }, company: { propDefinition: [ - "kustomer", + kustomer, "company", ], + optional: true, }, externalId: { propDefinition: [ - "kustomer", + kustomer, "externalId", ], + optional: true, }, username: { propDefinition: [ - "kustomer", + kustomer, "username", ], - }, - signedUpAt: { - propDefinition: [ - "kustomer", - "signedUpAt", - ], - }, - lastActivityAt: { - propDefinition: [ - "kustomer", - "lastActivityAt", - ], - }, - lastCustomerActivityAt: { - propDefinition: [ - "kustomer", - "lastCustomerActivityAt", - ], - }, - lastSeenAt: { - propDefinition: [ - "kustomer", - "lastSeenAt", - ], + optional: true, }, avatarUrl: { propDefinition: [ - "kustomer", + kustomer, "avatarUrl", ], + optional: true, }, externalIds: { propDefinition: [ - "kustomer", + kustomer, "externalIds", ], + optional: true, }, sharedExternalIds: { propDefinition: [ - "kustomer", + kustomer, "sharedExternalIds", ], + optional: true, }, emails: { propDefinition: [ - "kustomer", + kustomer, "emails", ], + optional: true, }, sharedEmails: { propDefinition: [ - "kustomer", + kustomer, "sharedEmails", ], + optional: true, }, phones: { propDefinition: [ - "kustomer", + kustomer, "phones", ], + optional: true, }, sharedPhones: { propDefinition: [ - "kustomer", + kustomer, "sharedPhones", ], + optional: true, }, whatsApps: { propDefinition: [ - "kustomer", + kustomer, "whatsApps", ], - }, - facebookIds: { - propDefinition: [ - "kustomer", - "facebookIds", - ], - }, - instagramIds: { - propDefinition: [ - "kustomer", - "instagramIds", - ], - }, - socials: { - propDefinition: [ - "kustomer", - "socials", - ], - }, - sharedSocials: { - propDefinition: [ - "kustomer", - "sharedSocials", - ], + optional: true, }, urls: { propDefinition: [ - "kustomer", + kustomer, "urls", ], + optional: true, }, - locations: { - propDefinition: [ - "kustomer", - "locations", - ], - }, - locale: { - propDefinition: [ - "kustomer", - "locale", - ], - }, - timeZone: { + tags: { propDefinition: [ - "kustomer", - "timeZone", + kustomer, + "tags", ], + optional: true, }, - tags: { + sentimentPolarity: { propDefinition: [ - "kustomer", - "tags", + kustomer, + "sentimentPolarity", ], + optional: true, }, - sentiment: { + sentimentConfidence: { propDefinition: [ - "kustomer", - "sentiment", + kustomer, + "sentimentConfidence", ], + optional: true, }, birthdayAt: { propDefinition: [ - "kustomer", + kustomer, "birthdayAt", ], + optional: true, }, gender: { propDefinition: [ - "kustomer", + kustomer, "gender", ], - }, - createdAt: { - propDefinition: [ - "kustomer", - "createdAt", - ], - }, - importedAt: { - propDefinition: [ - "kustomer", - "importedAt", - ], - }, - rev: { - propDefinition: [ - "kustomer", - "rev", - ], + optional: true, }, defaultLang: { propDefinition: [ - "kustomer", + kustomer, "defaultLang", ], + optional: true, }, }, async run({ $ }) { - const customerData = { - name: this.name, - company: this.company, - externalId: this.externalId, - username: this.username, - signedUpAt: this.signedUpAt, - lastActivityAt: this.lastActivityAt, - lastCustomerActivityAt: this.lastCustomerActivityAt, - lastSeenAt: this.lastSeenAt, - avatarUrl: this.avatarUrl, - externalIds: this.externalIds, - sharedExternalIds: this.sharedExternalIds, - emails: this.emails, - sharedEmails: this.sharedEmails, - phones: this.phones, - sharedPhones: this.sharedPhones, - whatsApps: this.whatsApps, - facebookIds: this.facebookIds, - instagramIds: this.instagramIds, - socials: this.socials, - sharedSocials: this.sharedSocials, - urls: this.urls, - locations: this.locations, - locale: this.locale, - timeZone: this.timeZone, - tags: this.tags, - sentiment: this.sentiment, - birthdayAt: this.birthdayAt, - gender: this.gender, - createdAt: this.createdAt, - importedAt: this.importedAt, - rev: this.rev, - defaultLang: this.defaultLang, - }; + try { + const sentiment = {}; + if (this.sentimentConfidence) sentiment.confidence = parseInt(this.sentimentConfidence); + if (this.sentimentPolarity) sentiment.polarity = parseInt(this.sentimentPolarity); - const response = await this.kustomer.createCustomer(customerData); - $.export("$summary", `Created customer with ID ${response.id}`); - return response; + const response = await this.kustomer.createCustomer({ + $, + data: { + name: this.name, + company: this.company, + externalId: this.externalId, + username: this.username, + avatarUrl: this.avatarUrl, + externalIds: parseObject(this.externalIds)?.map((id) => ({ + externalId: id, + })), + sharedExternalIds: parseObject(this.sharedExternalIds)?.map((id) => ({ + externalId: id, + })), + emails: parseObject(this.emails)?.map((email) => ({ + email, + })), + sharedEmails: parseObject(this.sharedEmails)?.map((email) => ({ + email, + })), + phones: parseObject(this.phones)?.map((phone) => ({ + phone: `${phone}`, + })), + sharedPhones: parseObject(this.sharedPhones)?.map((phone) => ({ + phone: `${phone}`, + })), + whatsapps: parseObject(this.whatsApps)?.map((phone) => ({ + phone: `${phone}`, + })), + urls: parseObject(this.urls)?.map((url) => ({ + url, + })), + tags: parseObject(this.tags), + sentiment: Object.entries(sentiment).length + ? sentiment + : undefined, + birthdayAt: this.birthdayAt, + gender: this.gender, + createdAt: this.createdAt, + importedAt: this.importedAt, + defaultLang: this.defaultLang, + }, + }); + $.export("$summary", `Created customer with ID ${response.data.id}`); + return response; + } catch ({ message }) { + throwError(message); + } }, }; diff --git a/components/kustomer/actions/update-conversation/update-conversation.mjs b/components/kustomer/actions/update-conversation/update-conversation.mjs index 6be6df703d841..2e2a1c486dfd6 100644 --- a/components/kustomer/actions/update-conversation/update-conversation.mjs +++ b/components/kustomer/actions/update-conversation/update-conversation.mjs @@ -1,11 +1,13 @@ +import { + parseObject, throwError, +} from "../../common/utils.mjs"; import kustomer from "../../kustomer.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "kustomer-update-conversation", name: "Update Conversation", - description: "Updates an existing conversation in Kustomer. [See the documentation]().", - version: "0.0.{{ts}}", + description: "Updates an existing conversation in Kustomer. [See the documentation](https://developer.kustomer.com/kustomer-api-docs/reference/updateconversation).", + version: "0.0.1", type: "action", props: { kustomer, @@ -27,41 +29,7 @@ export default { kustomer, "name", ], - optional: true, - }, - direction: { - propDefinition: [ - kustomer, - "direction", - ], - optional: true, - }, - priority: { - propDefinition: [ - kustomer, - "priority", - ], - optional: true, - }, - satisfaction: { - propDefinition: [ - kustomer, - "satisfaction", - ], - optional: true, - }, - satisfactionLevel: { - propDefinition: [ - kustomer, - "satisfactionLevel", - ], - optional: true, - }, - suggestedShortcuts: { - propDefinition: [ - kustomer, - "suggestedShortcuts", - ], + description: "Name of the conversation", optional: true, }, status: { @@ -71,24 +39,17 @@ export default { ], optional: true, }, - replyChannel: { - propDefinition: [ - kustomer, - "replyChannel", - ], - optional: true, - }, - subStatus: { + priority: { propDefinition: [ kustomer, - "subStatus", + "priority", ], optional: true, }, - snooze: { + direction: { propDefinition: [ kustomer, - "snooze", + "direction", ], optional: true, }, @@ -99,20 +60,6 @@ export default { ], optional: true, }, - suggestedTags: { - propDefinition: [ - kustomer, - "suggestedTags", - ], - optional: true, - }, - sentiment: { - propDefinition: [ - kustomer, - "sentiment", - ], - optional: true, - }, assignedUsers: { propDefinition: [ kustomer, @@ -127,109 +74,48 @@ export default { ], optional: true, }, - deleted: { - propDefinition: [ - kustomer, - "deleted", - ], - optional: true, - }, - ended: { - propDefinition: [ - kustomer, - "ended", - ], - optional: true, - }, - endedAt: { - propDefinition: [ - kustomer, - "endedAt", - ], - optional: true, - }, - endedReason: { - propDefinition: [ - kustomer, - "endedReason", - ], - optional: true, - }, - endedBy: { - propDefinition: [ - kustomer, - "endedBy", - ], - optional: true, - }, - endedByType: { - propDefinition: [ - kustomer, - "endedByType", - ], - optional: true, - }, - locked: { - propDefinition: [ - kustomer, - "locked", - ], - optional: true, - }, - rev: { - propDefinition: [ - kustomer, - "rev", - ], - optional: true, - }, defaultLang: { propDefinition: [ kustomer, "defaultLang", ], + description: "Default language for the conversation", optional: true, }, - queue: { + queueId: { propDefinition: [ kustomer, - "queue", + "queueId", ], optional: true, }, }, async run({ $ }) { - const response = await this.kustomer.updateConversation({ - conversationId: this.conversationId, - externalId: this.externalId, - name: this.name, - direction: this.direction, - priority: this.priority, - satisfaction: this.satisfaction, - satisfactionLevel: this.satisfactionLevel, - suggestedShortcuts: this.suggestedShortcuts, - status: this.status, - replyChannel: this.replyChannel, - subStatus: this.subStatus, - snooze: this.snooze, - tags: this.tags, - suggestedTags: this.suggestedTags, - sentiment: this.sentiment, - assignedUsers: this.assignedUsers, - assignedTeams: this.assignedTeams, - deleted: this.deleted, - ended: this.ended, - endedAt: this.endedAt, - endedReason: this.endedReason, - endedBy: this.endedBy, - endedByType: this.endedByType, - locked: this.locked, - rev: this.rev, - defaultLang: this.defaultLang, - queue: this.queue, - }); + try { + const data = { + externalId: this.externalId, + name: this.name, + status: this.status, + priority: this.priority, + direction: this.direction, + tags: parseObject(this.tags), + assignedUsers: parseObject(this.assignedUsers), + assignedTeams: parseObject(this.assignedTeams), + defaultLang: this.defaultLang, + }; + + if (this.queue) data.queue = this.queue; + + const response = await this.kustomer.updateConversation({ + $, + conversationId: this.conversationId, + data, + }); - $.export("$summary", `Conversation ${this.conversationId} updated successfully`); - return response; + $.export("$summary", `Conversation ${this.conversationId} updated successfully`); + return response; + } catch ({ message }) { + throwError(message); + } }, }; diff --git a/components/kustomer/actions/update-customer/update-customer.mjs b/components/kustomer/actions/update-customer/update-customer.mjs index 14479a3bcc614..22227af9e6843 100644 --- a/components/kustomer/actions/update-customer/update-customer.mjs +++ b/components/kustomer/actions/update-customer/update-customer.mjs @@ -1,17 +1,16 @@ +import { + parseObject, throwError, +} from "../../common/utils.mjs"; import kustomer from "../../kustomer.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "kustomer-update-customer", name: "Update Customer", description: "Updates an existing customer in Kustomer. [See the documentation](https://developer.kustomer.com/kustomer-api-docs/reference/updatecustomer)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "action", props: { - kustomer: { - type: "app", - app: "kustomer", - }, + kustomer, customerId: { propDefinition: [ kustomer, @@ -46,34 +45,6 @@ export default { ], optional: true, }, - signedUpAt: { - propDefinition: [ - kustomer, - "signedUpAt", - ], - optional: true, - }, - lastActivityAt: { - propDefinition: [ - kustomer, - "lastActivityAt", - ], - optional: true, - }, - lastCustomerActivityAt: { - propDefinition: [ - kustomer, - "lastCustomerActivityAt", - ], - optional: true, - }, - lastSeenAt: { - propDefinition: [ - kustomer, - "lastSeenAt", - ], - optional: true, - }, avatarUrl: { propDefinition: [ kustomer, @@ -130,34 +101,6 @@ export default { ], optional: true, }, - facebookIds: { - propDefinition: [ - kustomer, - "facebookIds", - ], - optional: true, - }, - instagramIds: { - propDefinition: [ - kustomer, - "instagramIds", - ], - optional: true, - }, - socials: { - propDefinition: [ - kustomer, - "socials", - ], - optional: true, - }, - sharedSocials: { - propDefinition: [ - kustomer, - "sharedSocials", - ], - optional: true, - }, urls: { propDefinition: [ kustomer, @@ -165,38 +108,24 @@ export default { ], optional: true, }, - locations: { - propDefinition: [ - kustomer, - "locations", - ], - optional: true, - }, - locale: { + tags: { propDefinition: [ kustomer, - "locale", + "tags", ], optional: true, }, - timeZone: { + sentimentPolarity: { propDefinition: [ kustomer, - "timeZone", + "sentimentPolarity", ], optional: true, }, - tags: { + sentimentConfidence: { propDefinition: [ kustomer, - "tags", - ], - optional: true, - }, - sentiment: { - propDefinition: [ - kustomer, - "sentiment", + "sentimentConfidence", ], optional: true, }, @@ -214,27 +143,6 @@ export default { ], optional: true, }, - createdAt: { - propDefinition: [ - kustomer, - "createdAt", - ], - optional: true, - }, - importedAt: { - propDefinition: [ - kustomer, - "importedAt", - ], - optional: true, - }, - rev: { - propDefinition: [ - kustomer, - "rev", - ], - optional: true, - }, defaultLang: { propDefinition: [ kustomer, @@ -244,47 +152,59 @@ export default { }, }, async run({ $ }) { - const updateData = { - name: this.name, - company: this.company, - externalId: this.externalId, - username: this.username, - signedUpAt: this.signedUpAt, - lastActivityAt: this.lastActivityAt, - lastCustomerActivityAt: this.lastCustomerActivityAt, - lastSeenAt: this.lastSeenAt, - avatarUrl: this.avatarUrl, - externalIds: this.externalIds, - sharedExternalIds: this.sharedExternalIds, - emails: this.emails, - sharedEmails: this.sharedEmails, - phones: this.phones, - sharedPhones: this.sharedPhones, - whatsApps: this.whatsApps, - facebookIds: this.facebookIds, - instagramIds: this.instagramIds, - socials: this.socials, - sharedSocials: this.sharedSocials, - urls: this.urls, - locations: this.locations, - locale: this.locale, - timeZone: this.timeZone, - tags: this.tags, - sentiment: this.sentiment, - birthdayAt: this.birthdayAt, - gender: this.gender, - createdAt: this.createdAt, - importedAt: this.importedAt, - rev: this.rev, - defaultLang: this.defaultLang, - }; - - const response = await this.kustomer.updateCustomer({ - customerId: this.customerId, - ...updateData, - }); + try { + const sentiment = {}; + if (this.sentimentConfidence) sentiment.confidence = parseInt(this.sentimentConfidence); + if (this.sentimentPolarity) sentiment.polarity = parseInt(this.sentimentPolarity); - $.export("$summary", `Successfully updated customer with ID ${this.customerId}`); - return response; + const response = await this.kustomer.updateCustomer({ + $, + customerId: this.customerId, + data: { + name: this.name, + company: this.company, + externalId: this.externalId, + username: this.username, + avatarUrl: this.avatarUrl, + externalIds: parseObject(this.externalIds)?.map((id) => ({ + externalId: id, + })), + sharedExternalIds: parseObject(this.sharedExternalIds)?.map((id) => ({ + externalId: id, + })), + emails: parseObject(this.emails)?.map((email) => ({ + email, + })), + sharedEmails: parseObject(this.sharedEmails)?.map((email) => ({ + email, + })), + phones: parseObject(this.phones)?.map((phone) => ({ + phone: `${phone}`, + })), + sharedPhones: parseObject(this.sharedPhones)?.map((phone) => ({ + phone: `${phone}`, + })), + whatsapps: parseObject(this.whatsApps)?.map((phone) => ({ + phone: `${phone}`, + })), + urls: parseObject(this.urls)?.map((url) => ({ + url, + })), + tags: parseObject(this.tags), + sentiment: Object.entries(sentiment).length + ? sentiment + : undefined, + birthdayAt: this.birthdayAt, + gender: this.gender, + createdAt: this.createdAt, + importedAt: this.importedAt, + defaultLang: this.defaultLang, + }, + }); + $.export("$summary", `Successfully updated customer with ID ${this.customerId}`); + return response; + } catch ({ message }) { + throwError(message); + } }, }; diff --git a/components/kustomer/common/constants.mjs b/components/kustomer/common/constants.mjs new file mode 100644 index 0000000000000..5d0189acecfc0 --- /dev/null +++ b/components/kustomer/common/constants.mjs @@ -0,0 +1,337 @@ +export const LIMIT = 100; + +export const STATUS_OPTIONS = [ + "open", + "done", +]; + +export const DIRECTION_OPTIONS = [ + "in", + "out", +]; + +export const GENDER_OPTIONS = [ + "m", + "f", +]; + +export const SENTIMENT_OPTIONS = [ + "-1", + "0", + "1", +]; + +export const DEFAULT_LANG_OPTIONS = [ + { + label: "Afrikaans", + value: "af", + }, + { + label: "Arabic", + value: "ar", + }, + { + label: "Arabic (EG)", + value: "ar_eg", + }, + { + label: "Arabic (MA)", + value: "ar_ma", + }, + { + label: "Arabic (TN)", + value: "ar_tn", + }, + { + label: "Azerbaijani", + value: "az", + }, + { + label: "Belarusian", + value: "be", + }, + { + label: "Bulgarian", + value: "bg", + }, + { + label: "Bosnian", + value: "bs", + }, + { + label: "Catalan", + value: "ca", + }, + { + label: "Montenegrin", + value: "cnr", + }, + { + label: "Czech", + value: "cs", + }, + { + label: "Danish", + value: "da", + }, + { + label: "German", + value: "de", + }, + { + label: "Dutch", + value: "nl", + }, + { + label: "Dutch (BE)", + value: "nl_be", + }, + { + label: "Greek", + value: "el", + }, + { + label: "English (US)", + value: "en_us", + }, + { + label: "English (CA)", + value: "en_ca", + }, + { + label: "English (UK)", + value: "en_gb", + }, + { + label: "Spanish", + value: "es", + }, + { + label: "Spanish (AR)", + value: "es_ar", + }, + { + label: "Spanish (Spain)", + value: "es_es", + }, + { + label: "Spanish (PA)", + value: "es_pa", + }, + { + label: "Spanish (PE)", + value: "es_pe", + }, + { + label: "Estonian", + value: "et", + }, + { + label: "Persian", + value: "fa", + }, + { + label: "Finnish", + value: "fi", + }, + { + label: "Filipino", + value: "fil", + }, + { + label: "French", + value: "fr", + }, + { + label: "French (BE)", + value: "fr_be", + }, + { + label: "French (CA)", + value: "fr_ca", + }, + { + label: "French (MA)", + value: "fr_ma", + }, + { + label: "Hebrew", + value: "he", + }, + { + label: "Hindi", + value: "hi", + }, + { + label: "Croatian", + value: "hr", + }, + { + label: "Hungarian", + value: "hu", + }, + { + label: "Armenian", + value: "hy", + }, + { + label: "Indonesian", + value: "id", + }, + { + label: "Icelandic", + value: "is", + }, + { + label: "Italian", + value: "it", + }, + { + label: "Japanese", + value: "ja", + }, + { + label: "Georgian", + value: "ka", + }, + { + label: "Kazakh", + value: "kk", + }, + { + label: "Kannada", + value: "kn", + }, + { + label: "Korean", + value: "ko", + }, + { + label: "Kyrgyz", + value: "ky", + }, + { + label: "Luxembourgish", + value: "lb", + }, + { + label: "Lithuanian", + value: "lt", + }, + { + label: "Latvian", + value: "lv", + }, + { + label: "Mongolian", + value: "mn", + }, + { + label: "Norwegian", + value: "no", + }, + { + label: "Norwegian (NB)", + value: "nb", + }, + { + label: "Punjabi", + value: "pa", + }, + { + label: "Polish", + value: "pl", + }, + { + label: "Portuguese (PT)", + value: "pt", + }, + { + label: "Portuguese (BR)", + value: "pt_br", + }, + { + label: "Romanian", + value: "ro", + }, + { + label: "Russian", + value: "ru", + }, + { + label: "Sinhala", + value: "si", + }, + { + label: "Slovakian", + value: "sk", + }, + { + label: "Slovenian", + value: "sl", + }, + { + label: "Albanian", + value: "sq", + }, + { + label: "Serbian", + value: "sr", + }, + { + label: "Serbian (Montenegro)", + value: "sr_me", + }, + { + label: "Swedish", + value: "sv", + }, + { + label: "Swahili", + value: "sw", + }, + { + label: "Tamil", + value: "ta", + }, + { + label: "Telugu", + value: "te", + }, + { + label: "Thai", + value: "th", + }, + { + label: "Turkish", + value: "tr", + }, + { + label: "Twi", + value: "tw", + }, + { + label: "Ukrainian", + value: "uk", + }, + { + label: "Urdu", + value: "ur", + }, + { + label: "Uzbek", + value: "uz", + }, + { + label: "Vietnamese", + value: "vi", + }, + { + label: "Simplified Chinese", + value: "zh_cn", + }, + { + label: "Traditional Chinese", + value: "zh_tw", + }, +]; diff --git a/components/kustomer/common/utils.mjs b/components/kustomer/common/utils.mjs new file mode 100644 index 0000000000000..ff447f0ae1e7b --- /dev/null +++ b/components/kustomer/common/utils.mjs @@ -0,0 +1,34 @@ +import { ConfigurationError } from "@pipedream/platform"; + +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; + +export const throwError = (message) => { + const parsedMessage = JSON.parse(message); + const error = parsedMessage.errors[0]; + const errorParameter = error.meta.subErrors[0].source.pointer || error.source.parameter; + const errorMessage = error.detail || error.meta.subErrors[0].title; + throw new ConfigurationError(`${errorParameter} - ${errorMessage}`); +}; diff --git a/components/kustomer/kustomer.app.mjs b/components/kustomer/kustomer.app.mjs index 1fa5b7de071fe..02350afd565d9 100644 --- a/components/kustomer/kustomer.app.mjs +++ b/components/kustomer/kustomer.app.mjs @@ -1,45 +1,152 @@ import { axios } from "@pipedream/platform"; +import { + DEFAULT_LANG_OPTIONS, + DIRECTION_OPTIONS, + GENDER_OPTIONS, + LIMIT, + SENTIMENT_OPTIONS, + STATUS_OPTIONS, +} from "./common/constants.mjs"; export default { type: "app", app: "kustomer", - version: "0.0.{{ts}}", propDefinitions: { - // Required Props customerId: { type: "string", label: "Customer ID", description: "Unique identifier for the customer", + async options({ page }) { + const { data } = await this.listCustomers({ + params: { + page: page + 1, + pageSize: LIMIT * page, + }, + }); + + return data.map(({ + id: value, attributes: { displayName: label }, + }) => ({ + label, + value, + })); + }, + }, + tags: { + type: "string[]", + label: "Tags", + description: "Tags associated with the conversation", + async options({ page }) { + const { data } = await this.listTags({ + params: { + page: page + 1, + pageSize: LIMIT * page, + }, + }); + return data.map(({ + id: value, attributes: { name: label }, + }) => ({ + label, + value, + })); + }, + }, + assignedUsers: { + type: "string[]", + label: "Assigned Users", + description: "Users assigned to the conversation", + async options({ page }) { + const { data } = await this.listUsers({ + params: { + page: page + 1, + pageSize: LIMIT * page, + }, + }); + return data.map(({ + id: value, attributes: { displayName: label }, + }) => ({ + label, + value, + })); + }, + }, + assignedTeams: { + type: "string[]", + label: "Assigned Teams", + description: "Teams assigned to the resource", + async options({ page }) { + const { data } = await this.listTeams({ + params: { + page: page + 1, + pageSize: LIMIT * page, + }, + }); + return data.map(({ + id: value, attributes: { name: label }, + }) => ({ + label, + value, + })); + }, + }, + queueId: { + type: "string", + label: "Queue Id", + description: "Queue ID assigned to the conversation", + async options({ page }) { + const { data } = await this.listQueues({ + params: { + page: page + 1, + pageSize: LIMIT * page, + }, + }); + return data.map(({ + id: value, attributes: { displayName: label }, + }) => ({ + label, + value, + })); + }, }, conversationId: { type: "string", label: "Conversation ID", description: "Unique identifier for the conversation", + async options({ page }) { + const { data } = await this.listConversations({ + params: { + page: page + 1, + pageSize: LIMIT * page, + }, + }); + return data.map(({ + id: value, attributes: { name: label }, + }) => ({ + label, + value, + })); + }, }, - // Optional Props for Conversation Creation externalId: { type: "string", label: "External ID", description: "External identifier", - optional: true, }, name: { type: "string", label: "Name", - description: "Name of the resource", - optional: true, + description: "Name of the customer", }, status: { type: "string", label: "Status", description: "Status of the conversation", - optional: true, + options: STATUS_OPTIONS, }, priority: { type: "integer", label: "Priority", description: "Priority level (1-5)", - optional: true, min: 1, max: 5, }, @@ -47,636 +154,191 @@ export default { type: "string", label: "Direction", description: "Direction of the conversation", - optional: true, + options: DIRECTION_OPTIONS, }, - replyChannel: { + sentimentPolarity: { type: "string", - label: "Reply Channel", - description: "Channel to reply to", - optional: true, + label: "Sentiment Polarity", + description: "Sentiment polarity associated with the conversation", + options: SENTIMENT_OPTIONS, }, - tags: { - type: "string[]", - label: "Tags", - description: "Tags associated with the resource", - optional: true, - async options() { - const tags = await this.listTags(); - return tags.map((tag) => ({ - label: tag.name, - value: tag.id, - })); - }, - }, - assignedTeams: { - type: "string[]", - label: "Assigned Teams", - description: "Teams assigned to the resource", - optional: true, - async options() { - const teams = await this.listAssignedTeams(); - return teams.map((team) => ({ - label: team.name, - value: team.id, - })); - }, - }, - suggestedTags: { - type: "string[]", - label: "Suggested Tags", - description: "Suggested tags for the resource", - optional: true, - }, - sentiment: { - type: "string", - label: "Sentiment", - description: "Sentiment associated with the conversation", - optional: true, - }, - suggestedShortcuts: { - type: "string[]", - label: "Suggested Shortcuts", - description: "Suggested shortcuts for the conversation", - optional: true, - }, - subStatus: { - type: "string", - label: "Sub Status", - description: "Sub-status of the conversation", - optional: true, - }, - snooze: { - type: "object", - label: "Snooze", - description: "Snooze status", - optional: true, - }, - deleted: { - type: "boolean", - label: "Deleted", - description: "Whether the conversation is deleted", - optional: true, - }, - ended: { - type: "boolean", - label: "Ended", - description: "Whether the conversation has ended", - optional: true, - }, - endedAt: { + sentimentConfidence: { type: "string", - label: "Ended At", - description: "Datetime when the conversation ended", - optional: true, - }, - endedReason: { - type: "string", - label: "Ended Reason", - description: "Reason why the conversation ended", - optional: true, - }, - endedBy: { - type: "string", - label: "Ended By", - description: "Identifier of who ended the conversation", - optional: true, - }, - endedByType: { - type: "string", - label: "Ended By Type", - description: "Type of entity that ended the conversation", - optional: true, - }, - locked: { - type: "boolean", - label: "Locked", - description: "Whether the conversation is locked", - optional: true, - }, - rev: { - type: "integer", - label: "Revision", - description: "Revision number", - optional: true, + label: "Sentiment Confidence", + description: "Sentiment confidence associated with the conversation", + options: SENTIMENT_OPTIONS, }, defaultLang: { type: "string", label: "Default Language", - description: "Default language for the resource", - optional: true, + description: "Default language for the customer", + options: DEFAULT_LANG_OPTIONS, }, - queue: { - type: "string", - label: "Queue", - description: "Queue information", - optional: true, - }, - // Optional Props for Customer Creation and Update company: { type: "string", label: "Company", description: "Company of the customer", - optional: true, }, username: { type: "string", label: "Username", description: "Username of the customer", - optional: true, - }, - signedUpAt: { - type: "string", - label: "Signed Up At", - description: "Signup datetime", - optional: true, - }, - lastActivityAt: { - type: "string", - label: "Last Activity At", - description: "Last activity datetime", - optional: true, - }, - lastCustomerActivityAt: { - type: "string", - label: "Last Customer Activity At", - description: "Last customer activity datetime", - optional: true, - }, - lastSeenAt: { - type: "string", - label: "Last Seen At", - description: "Last seen datetime", - optional: true, }, avatarUrl: { type: "string", label: "Avatar URL", description: "URL to the avatar", - optional: true, }, externalIds: { type: "string[]", label: "External IDs", description: "External identifiers", - optional: true, }, sharedExternalIds: { type: "string[]", label: "Shared External IDs", description: "Shared external identifiers", - optional: true, }, emails: { type: "string[]", label: "Emails", description: "Emails of the customer", - optional: true, }, sharedEmails: { type: "string[]", label: "Shared Emails", description: "Shared emails", - optional: true, }, phones: { type: "string[]", label: "Phones", description: "Phone numbers of the customer", - optional: true, }, sharedPhones: { type: "string[]", label: "Shared Phones", description: "Shared phone numbers", - optional: true, }, whatsApps: { type: "string[]", label: "WhatsApps", description: "WhatsApp numbers of the customer", - optional: true, - }, - facebookIds: { - type: "string[]", - label: "Facebook IDs", - description: "Facebook IDs of the customer", - optional: true, - }, - instagramIds: { - type: "string[]", - label: "Instagram IDs", - description: "Instagram IDs of the customer", - optional: true, - }, - socials: { - type: "string[]", - label: "Socials", - description: "Social media accounts", - optional: true, - }, - sharedSocials: { - type: "string[]", - label: "Shared Socials", - description: "Shared social media accounts", - optional: true, }, urls: { type: "string[]", label: "URLs", description: "Associated URLs", - optional: true, - }, - locations: { - type: "string[]", - label: "Locations", - description: "Customer locations", - optional: true, - }, - locale: { - type: "string", - label: "Locale", - description: "Locale of the customer", - optional: true, - }, - timeZone: { - type: "string", - label: "Time Zone", - description: "Time zone of the customer", - optional: true, }, birthdayAt: { type: "string", label: "Birthday At", - description: "Birthday datetime", - optional: true, + description: "A valid [ISO 8601](https://pt.wikipedia.org/wiki/ISO_8601) date/time", }, gender: { type: "string", label: "Gender", description: "Gender of the customer", - optional: true, - }, - createdAt: { - type: "string", - label: "Created At", - description: "Creation datetime", - optional: true, - }, - importedAt: { - type: "string", - label: "Imported At", - description: "Import datetime", - optional: true, + options: GENDER_OPTIONS, }, }, methods: { - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { - return "https://api.kustomerapp.com/v1"; + return `https://${this.$auth.subdomain}.api.kustomerapp.com/v1`; }, - async _makeRequest(opts = {}) { - const { - $, method = "GET", path = "/", headers, ...otherOpts - } = opts; + _headers() { + return { + Authorization: `Bearer ${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - method, url: this._baseUrl() + path, - headers: { - ...headers, - "Authorization": `Bearer ${this.$auth.access_token}`, - "Content-Type": "application/json", - }, - ...otherOpts, + headers: this._headers(), + ...opts, }); }, - // Conversation Methods - async createConversation(opts = {}) { - const { - customerId, - externalId, - name, - status, - priority, - direction, - replyChannel, - tags, - assignedTeams, - defaultLang, - queue, - } = opts; - const data = { - customer: customerId, - externalId, - name, - status, - priority, - direction, - replyChannel, - tags, - assignedTeams, - defaultLang, - queue, - }; + createConversation(opts = {}) { return this._makeRequest({ method: "POST", path: "/conversations", - data, + ...opts, }); }, - async updateConversation(opts = {}) { - const { - conversationId, - externalId, - name, - direction, - priority, - satisfaction, - satisfactionLevel, - suggestedShortcuts, - status, - replyChannel, - subStatus, - snooze, - tags, - suggestedTags, - sentiment, - assignedUsers, - assignedTeams, - deleted, - ended, - endedAt, - endedReason, - endedBy, - endedByType, - locked, - rev, - defaultLang, - queue, - } = opts; - const data = { - externalId, - name, - direction, - priority, - satisfaction, - satisfactionLevel, - suggestedShortcuts, - status, - replyChannel, - subStatus, - snooze, - tags, - suggestedTags, - sentiment, - assignedUsers, - assignedTeams, - deleted, - ended, - endedAt, - endedReason, - endedBy, - endedByType, - locked, - rev, - defaultLang, - queue, - }; + updateConversation({ + conversationId, ...opts + }) { return this._makeRequest({ - method: "PUT", + method: "PATCH", path: `/conversations/${conversationId}`, - data, + ...opts, }); }, - // Customer Methods - async createCustomer(opts = {}) { - const { - name, - company, - externalId, - username, - signedUpAt, - lastActivityAt, - lastCustomerActivityAt, - lastSeenAt, - avatarUrl, - externalIds, - sharedExternalIds, - emails, - sharedEmails, - phones, - sharedPhones, - whatsApps, - facebookIds, - instagramIds, - socials, - sharedSocials, - urls, - locations, - locale, - timeZone, - tags, - sentiment, - birthdayAt, - gender, - createdAt, - importedAt, - rev, - defaultLang, - } = opts; - const data = { - name, - company, - externalId, - username, - signedUpAt, - lastActivityAt, - lastCustomerActivityAt, - lastSeenAt, - avatarUrl, - externalIds, - sharedExternalIds, - emails, - sharedEmails, - phones, - sharedPhones, - whatsApps, - facebookIds, - instagramIds, - socials, - sharedSocials, - urls, - locations, - locale, - timeZone, - tags, - sentiment, - birthdayAt, - gender, - createdAt, - importedAt, - rev, - defaultLang, - }; + createCustomer(opts = {}) { return this._makeRequest({ method: "POST", path: "/customers", - data, + ...opts, }); }, - async updateCustomer(opts = {}) { - const { - customerId, - name, - company, - externalId, - username, - signedUpAt, - lastActivityAt, - lastCustomerActivityAt, - lastSeenAt, - avatarUrl, - externalIds, - sharedExternalIds, - emails, - sharedEmails, - phones, - sharedPhones, - whatsApps, - facebookIds, - instagramIds, - socials, - sharedSocials, - urls, - locations, - locale, - timeZone, - tags, - sentiment, - birthdayAt, - gender, - createdAt, - importedAt, - rev, - defaultLang, - } = opts; - const data = { - name, - company, - externalId, - username, - signedUpAt, - lastActivityAt, - lastCustomerActivityAt, - lastSeenAt, - avatarUrl, - externalIds, - sharedExternalIds, - emails, - sharedEmails, - phones, - sharedPhones, - whatsApps, - facebookIds, - instagramIds, - socials, - sharedSocials, - urls, - locations, - locale, - timeZone, - tags, - sentiment, - birthdayAt, - gender, - createdAt, - importedAt, - rev, - defaultLang, - }; + updateCustomer({ + customerId, ...opts + }) { return this._makeRequest({ method: "PUT", path: `/customers/${customerId}`, - data, + ...opts, }); }, - // Listing Methods - async listTags(opts = {}) { + listConversations(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/tags", + path: "/conversations", ...opts, }); }, - async listAssignedTeams(opts = {}) { + listCustomers(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/teams", + path: "/customers", ...opts, }); }, - async listAssignedUsers(opts = {}) { + listQueues(opts = {}) { return this._makeRequest({ - method: "GET", - path: "/users", + path: "/routing/queues", ...opts, }); }, - // Event Emission Methods - async emitConversationCreateEvent(name, url) { - return { - name, - url, - event: "kustomer.conversation.create", - }; - }, - async emitCustomerCreateEvent(name, url) { - return { - name, - url, - event: "kustomer.customer.create", - }; + listTags(opts = {}) { + return this._makeRequest({ + path: "/tags", + ...opts, + }); }, - async emitMessageCreateEvent(name, url) { - return { - name, - url, - event: "kustomer.message.create", - }; + listUsers(opts = {}) { + return this._makeRequest({ + path: "/users", + ...opts, + }); }, - async emitConversationUpdateEvent(name, url) { - return { - name, - url, - event: "kustomer.conversation.update", - }; + listTeams(opts = {}) { + return this._makeRequest({ + path: "/teams", + ...opts, + }); }, - async emitCustomerUpdateEvent(name, url) { - return { - name, - url, - event: "kustomer.customer.update", - }; + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/outbound-webhooks", + ...opts, + }); }, - // Pagination Method - async paginate(fn, ...opts) { - let results = []; - let hasMore = true; - let page = 1; - while (hasMore) { - const response = await fn({ - page, - ...opts, - }); - if (response.length === 0) { - hasMore = false; - } else { - results = results.concat(response); - page += 1; - } - } - return results; + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/outbound-webhooks/${webhookId}`, + }); }, }, }; diff --git a/components/kustomer/package.json b/components/kustomer/package.json index f90461f32493d..461537a2f3dad 100644 --- a/components/kustomer/package.json +++ b/components/kustomer/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/kustomer", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Kustomer Components", "main": "kustomer.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/kustomer/sources/common/base.mjs b/components/kustomer/sources/common/base.mjs new file mode 100644 index 0000000000000..c0cf615ed9654 --- /dev/null +++ b/components/kustomer/sources/common/base.mjs @@ -0,0 +1,49 @@ +import kustomer from "../../kustomer.app.mjs"; + +export default { + props: { + kustomer, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + name: { + type: "string", + label: "Webhook Name", + description: "The name of the webhook to be identified in the UI", + }, + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + }, + hooks: { + async activate() { + const response = await this.kustomer.createWebhook({ + data: { + name: this.name, + url: this.http.endpoint, + events: this.getEventType(), + }, + }); + this._setHookId(response.data.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.kustomer.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + const ts = Date.parse(body.createdAt); + this.$emit(body, { + id: body.id, + summary: this.getSummary(body), + ts: ts, + }); + }, +}; diff --git a/components/kustomer/sources/new-conversation-instant/new-conversation-instant.mjs b/components/kustomer/sources/new-conversation-instant/new-conversation-instant.mjs index 539a91dcbff76..3e43317b7df50 100644 --- a/components/kustomer/sources/new-conversation-instant/new-conversation-instant.mjs +++ b/components/kustomer/sources/new-conversation-instant/new-conversation-instant.mjs @@ -1,84 +1,24 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import kustomer from "../../kustomer.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "kustomer-new-conversation-instant", - name: "New Conversation Created", - description: "Emit new event when a conversation is created in Kustomer. [See the documentation]()", - version: "0.0.{{ts}}", + name: "New Conversation Created (Instant)", + description: "Emit new event when a conversation is created in Kustomer.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - kustomer: { - type: "app", - app: "kustomer", + methods: { + ...common.methods, + getEventType() { + return [ + "kustomer.conversation.create", + ]; }, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + getSummary(body) { + return `New Conversation: ${body.data.attributes.name}`; }, }, - hooks: { - async deploy() { - const lastRun = (await this.db.get("lastRun")) || new Date(0).toISOString(); - const conversations = await this.kustomer.listConversations(lastRun); - - const sortedConversations = conversations.sort( - (a, b) => new Date(a.createdAt) - new Date(b.createdAt), - ); - - const limitedConversations = sortedConversations.slice(-50); - - for (const conv of limitedConversations) { - const eventData = await this.kustomer.emitConversationCreateEvent(conv.name, conv.url); - this.$emit(eventData, { - id: conv.id || conv.createdAt, - summary: `New Conversation: ${conv.name}`, - ts: Date.parse(conv.createdAt) || Date.now(), - }); - } - - if (limitedConversations.length > 0) { - const latestCreatedAt = Math.max(...limitedConversations.map((conv) => Date.parse(conv.createdAt))); - await this.db.set("lastRun", new Date(latestCreatedAt).toISOString()); - } - }, - async activate() { - // Implement webhook subscription if supported by Kustomer - // Example: - // await this.kustomer.subscribeToConversations(); - }, - async deactivate() { - // Implement webhook unsubscription if supported by Kustomer - // Example: - // await this.kustomer.unsubscribeFromConversations(); - }, - }, - async run() { - const lastRun = (await this.db.get("lastRun")) || new Date(0).toISOString(); - const conversations = await this.kustomer.listConversations(lastRun); - - const sortedConversations = conversations.sort( - (a, b) => new Date(a.createdAt) - new Date(b.createdAt), - ); - - for (const conv of sortedConversations) { - const eventData = await this.kustomer.emitConversationCreateEvent(conv.name, conv.url); - this.$emit(eventData, { - id: conv.id || conv.createdAt, - summary: `New Conversation: ${conv.name}`, - ts: Date.parse(conv.createdAt) || Date.now(), - }); - } - - if (conversations.length > 0) { - const latestCreatedAt = Math.max(...conversations.map((conv) => Date.parse(conv.createdAt))); - await this.db.set("lastRun", new Date(latestCreatedAt).toISOString()); - } - }, + sampleEmit, }; diff --git a/components/kustomer/sources/new-conversation-instant/test-event.mjs b/components/kustomer/sources/new-conversation-instant/test-event.mjs new file mode 100644 index 0000000000000..00d007cf663d2 --- /dev/null +++ b/components/kustomer/sources/new-conversation-instant/test-event.mjs @@ -0,0 +1,192 @@ +export default { + "data": { + "id": "67dd47dfbc632c1804db1bf1", + "name": "kustomer.conversation.create", + "org": "67dd47dfbc632c1804db1bf1", + "partition": "67dd47dfbc632c1804db1bf1", + "data": { + "type": "conversation", + "id": "67dd47dfbc632c1804db1bf1", + "attributes": { + "externalId": "", + "name": "Conversation Name", + "channels": [], + "status": "open", + "open": { + "statusAt": "2025-03-21T20:04:04.375Z" + }, + "messageCount": 0, + "noteCount": 0, + "satisfaction": 0, + "satisfactionLevel": { + "sentByTeams": [], + "answers": [] + }, + "createdAt": "2025-03-21T20:04:04.375Z", + "updatedAt": "2025-03-21T20:04:04.375Z", + "modifiedAt": "2025-03-21T20:04:04.375Z", + "lastActivityAt": "2025-03-21T20:04:04.377Z", + "spam": false, + "ended": false, + "tags": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "suggestedTags": [], + "predictions": [], + "suggestedShortcuts": [], + "firstMessageIn": {}, + "firstMessageOut": { + "createdByTeams": [] + }, + "lastMessageIn": {}, + "lastMessageOut": {}, + "lastMessageUnrespondedTo": {}, + "lastMessageUnrespondedToSinceLastDone": {}, + "assignedUsers": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "assignedTeams": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "firstResponse": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "firstResponseSinceLastDone": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "lastResponse": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "firstDone": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "lastDone": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "direction": "in", + "outboundMessageCount": 0, + "inboundMessageCount": 0, + "rev": 1, + "priority": 5, + "defaultLang": "en_us", + "locale": "US", + "roleGroupVersions": [], + "accessOverride": [], + "modificationHistory": { + "nameAt": "2025-03-21T20:04:04.375Z", + "priorityAt": "2025-03-21T20:04:04.375Z", + "channelAt": "2025-03-21T20:04:04.375Z", + "assignedTeamsAt": "2025-03-21T20:04:04.375Z", + "assignedUsersAt": "2025-03-21T20:04:04.375Z", + "brandAt": null, + "defaultLangAt": null, + "statusAt": "2025-03-21T20:04:04.375Z", + "tagsAt": "2025-03-21T20:04:04.375Z", + "customAt": null + }, + "billingStatus": "no_messages", + "assistant": { + "fac": { + "reasons": [], + "exclusions": [], + "source": {} + }, + "assistantId": [] + }, + "aiAgent": { + "automationId": [] + }, + "phase": "initial", + "matchedTimeBasedRules": [] + }, + "relationships": { + "messages": { + "links": { + "self": "/v1/conversations/67dd47dfbc632c1804db1bf1/messages" + } + }, + "createdBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "modifiedBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "org": { + "links": { + "self": "/v1/orgs/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "org", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "customer": { + "data": { + "type": "customer", + "id": "67dd47dfbc632c1804db1bf1" + }, + "links": { + "self": "/v1/customers/67dd47dfbc632c1804db1bf1" + } + }, + "queue": { + "data": { + "type": "queue", + "id": "67dd47dfbc632c1804db1bf1" + }, + "links": { + "self": "/v1/routing/queues/67dd47dfbc632c1804db1bf1" + } + }, + "brand": { + "data": { + "type": "brand", + "id": "67dd47dfbc632c1804db1bf1" + }, + "links": { + "self": "/v1/brands/67dd47dfbc632c1804db1bf1" + } + } + }, + "links": { + "self": "/v1/conversations/67dd47dfbc632c1804db1bf1" + } + }, + "createdAt": "2025-03-21T20:04:04.399Z", + "persist": true, + "dataId": "67dd47dfbc632c1804db1bf1", + "meta": { + "customChanged": false + }, + "snsFilters": [ + "csat" + ] + } +} \ No newline at end of file diff --git a/components/kustomer/sources/new-customer-instant/new-customer-instant.mjs b/components/kustomer/sources/new-customer-instant/new-customer-instant.mjs index 31abb5284198a..f95c61d64135b 100644 --- a/components/kustomer/sources/new-customer-instant/new-customer-instant.mjs +++ b/components/kustomer/sources/new-customer-instant/new-customer-instant.mjs @@ -1,81 +1,24 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import kustomer from "../../kustomer.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "kustomer-new-customer-instant", - name: "New Customer Created", - description: "Emit new event when a new customer is added to Kustomer. [See the documentation]().", - version: "0.0.{{ts}}", + name: "New Customer Created (Instant)", + description: "Emit new event when a new customer is added to Kustomer.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - kustomer: { - type: "app", - app: "kustomer", + methods: { + ...common.methods, + getEventType() { + return [ + "kustomer.customer.create", + ]; }, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + getSummary(body) { + return `New Customer: ${body.data.attributes.name}`; }, }, - hooks: { - async deploy() { - const customers = await this.kustomer.listCustomers({ - limit: 50, - sort: "-createdAt", - }); - for (const customer of customers) { - this.$emit( - { - name: customer.name, - url: customer.urls?.[0] || "", - }, - { - id: customer.id, - summary: `New Kustomer Customer: ${customer.name}`, - ts: Date.parse(customer.createdAt) || Date.now(), - }, - ); - } - if (customers.length > 0) { - this.db.set("last_created_at", customers[0].createdAt); - } - }, - async activate() { - // No activation steps needed for polling source - }, - async deactivate() { - // No deactivation steps needed for polling source - }, - }, - async run() { - const lastCreatedAt = (await this.db.get("last_created_at")) || new Date(0).toISOString(); - const newCustomers = await this.kustomer.listCustomers({ - filter: `createdAt > "${lastCreatedAt}"`, - sort: "createdAt", - limit: 50, - }); - for (const customer of newCustomers) { - this.$emit( - { - name: customer.name, - url: customer.urls?.[0] || "", - }, - { - id: customer.id, - summary: `New Kustomer Customer: ${customer.name}`, - ts: Date.parse(customer.createdAt) || Date.now(), - }, - ); - } - if (newCustomers.length > 0) { - const latestCreatedAt = newCustomers[newCustomers.length - 1].createdAt; - this.db.set("last_created_at", latestCreatedAt); - } - }, + sampleEmit, }; diff --git a/components/kustomer/sources/new-customer-instant/test-event.mjs b/components/kustomer/sources/new-customer-instant/test-event.mjs new file mode 100644 index 0000000000000..b6e875cb95e13 --- /dev/null +++ b/components/kustomer/sources/new-customer-instant/test-event.mjs @@ -0,0 +1,111 @@ +export default { + "id": "67dd47dfbc632c1804db1bf1", + "name": "kustomer.customer.create", + "org": "67dd47dfbc632c1804db1bf1", + "partition": "67dd47dfbc632c1804db1bf1", + "orgName": "par-pipedream-berta", + "data": { + "type": "customer", + "id": "67dd47dfbc632c1804db1bf1", + "attributes": { + "displayName": "Silver Scissors", + "displayColor": "silver", + "displayIcon": "scissors", + "externalIds": [], + "sharedExternalIds": [], + "emails": [], + "sharedEmails": [], + "phones": [], + "sharedPhones": [], + "whatsapps": [], + "sharedWhatsapps": [], + "facebookIds": [], + "instagramIds": [], + "socials": [], + "sharedSocials": [], + "urls": [], + "locations": [], + "activeUsers": [], + "watchers": [], + "recentLocation": { + "updatedAt": "2025-03-21T20:10:26.482Z" + }, + "createdAt": "2025-03-21T20:10:26.481Z", + "updatedAt": "2025-03-21T20:10:26.481Z", + "modifiedAt": "2025-03-21T20:10:26.481Z", + "lastActivityAt": "2025-03-21T20:10:26.481Z", + "deleted": false, + "lastConversation": { + "channels": [], + "tags": [] + }, + "conversationCounts": { + "done": 0, + "open": 0, + "snoozed": 0, + "all": 0 + }, + "preview": {}, + "tags": [], + "progressiveStatus": null, + "verified": false, + "rev": 1, + "recentItems": [], + "satisfactionLevel": { + "firstSatisfaction": { + "sentByTeams": [] + }, + "lastSatisfaction": { + "sentByTeams": [] + } + }, + "roleGroupVersions": [], + "accessOverride": [] + }, + "relationships": { + "messages": { + "links": { + "self": "/v1/customers/67dd47dfbc632c1804db1bf1/messages" + } + }, + "createdBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "modifiedBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "org": { + "data": { + "type": "org", + "id": "67dd47dfbc632c1804db1bf1" + }, + "links": { + "self": "/v1/orgs/67dd47dfbc632c1804db1bf1" + } + } + }, + "links": { + "self": "/v1/customers/67dd47dfbc632c1804db1bf1" + } + }, + "createdAt": "2025-03-21T20:10:26.494Z", + "persist": true, + "client": "api-gw", + "dataId": "67dd47dfbc632c1804db1bf1", + "meta": { + "customChanged": false + }, + "snsFilters": [] +} \ No newline at end of file diff --git a/components/kustomer/sources/new-message-instant/new-message-instant.mjs b/components/kustomer/sources/new-message-instant/new-message-instant.mjs index 78ec19db3b912..6a1dc899c41fb 100644 --- a/components/kustomer/sources/new-message-instant/new-message-instant.mjs +++ b/components/kustomer/sources/new-message-instant/new-message-instant.mjs @@ -1,122 +1,24 @@ -import { axios } from "@pipedream/platform"; -import kustomer from "../../kustomer.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "kustomer-new-message-instant", - name: "New Message Created in Conversation", - description: - "Emit new event when a new message is created in a conversation. [See the documentation]().", - version: "0.0.{{ts}}", + name: "New Message Created in Conversation (Instant)", + description: "Emit new event when a new message is created in a conversation.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - kustomer: { - type: "app", - app: "kustomer", - }, - name: { - type: "string", - label: "Name", - description: "Name associated with the message event.", - }, - url: { - type: "string", - label: "URL", - description: "URL associated with the message event.", - }, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, - }, - }, - hooks: { - async deploy() { - const lastTs = 0; - await this.db.set("last_ts", lastTs); - const messages = await this.kustomer.listMessages({ - perpage: 50, - sort: "desc", - }); - for (const message of messages.reverse()) { - const eventData = { - message, - name: this.name, - url: this.url, - }; - this.$emit( - eventData, - { - id: message.id, - summary: `New Kustomer message: ${message.content - ? message.content.substring(0, 20) + "..." - : "New message created."}`, - ts: Date.parse(message.createdAt) || Date.now(), - event: "kustomer.message.create", - }, - ); - } - if (messages.length > 0) { - const latestMessage = messages[0]; - const latestTimestamp = Date.parse(latestMessage.createdAt); - await this.db.set("last_ts", latestTimestamp); - } - }, - async activate() { - // Code to create webhook subscription if needed - }, - async deactivate() { - // Code to delete webhook subscription if needed - }, - }, methods: { - async listMessages(params = {}) { - return await this.kustomer._makeRequest({ - method: "GET", - path: "/messages", - params, - }); - }, - async getLastTimestamp() { - return this.db.get("last_ts") || 0; + ...common.methods, + getEventType() { + return [ + "kustomer.message.create", + ]; }, - async setLastTimestamp(timestamp) { - await this.db.set("last_ts", timestamp); + getSummary(body) { + return `New message created: ${body.dataId}`; }, }, - async run() { - const lastTimestamp = await this.getLastTimestamp(); - const newMessages = await this.listMessages({ - since: lastTimestamp, - perpage: 100, - sort: "asc", - }); - - for (const message of newMessages) { - const eventData = { - message, - name: this.name, - url: this.url, - }; - this.$emit( - eventData, - { - id: message.id, - summary: `New Kustomer message: ${message.content - ? message.content.substring(0, 20) + "..." - : "New message created."}`, - ts: Date.parse(message.createdAt) || Date.now(), - event: "kustomer.message.create", - }, - ); - } - - if (newMessages.length > 0) { - const latestMessage = newMessages[newMessages.length - 1]; - const latestTimestamp = Date.parse(latestMessage.createdAt); - await this.setLastTimestamp(latestTimestamp); - } - }, + sampleEmit, }; diff --git a/components/kustomer/sources/new-message-instant/test-event.mjs b/components/kustomer/sources/new-message-instant/test-event.mjs new file mode 100644 index 0000000000000..d233e4eededcb --- /dev/null +++ b/components/kustomer/sources/new-message-instant/test-event.mjs @@ -0,0 +1,115 @@ +export default { + "id": "67dd47dfbc632c1804db1bf1", + "name": "kustomer.message.create", + "org": "67dd47dfbc632c1804db1bf1", + "partition": "67dd47dfbc632c1804db1bf1", + "data": { + "type": "message", + "id": "67dd47dfbc632c1804db1bf1", + "attributes": { + "channel": "email", + "app": "postmark", + "size": 0, + "direction": "out", + "directionType": "initial-out", + "preview": "message...", + "meta": { + "from": "support@subdomain.mail.kustomerapp.com", + "to": [ + { + "email": "moishe@example.com" + } + ] + }, + "status": "error", + "assignedTeams": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "assignedUsers": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "error": { + "status": 400, + "code": "invalid-recipient", + "title": "You tried to send to recipient(s) that have been marked as inactive. Found inactive addresses: . Inactive recipients are ones that have generated a hard bounce, a spam complaint, or a manual suppression.", + "meta": { + "appErrorCode": 406 + } + }, + "errorAt": "2025-03-21T20:13:07.702Z", + "auto": false, + "sentAt": "2025-03-21T20:13:07.702Z", + "createdAt": "2025-03-21T20:13:07.798Z", + "updatedAt": "2025-03-21T20:13:07.798Z", + "modifiedAt": "2025-03-21T20:13:07.798Z", + "redacted": false, + "createdByTeams": [], + "rev": 1, + "reactions": [], + "intentDetections": [] + }, + "relationships": { + "org": { + "links": { + "self": "/v1/orgs/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "org", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "createdBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "modifiedBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "customer": { + "links": { + "self": "/v1/customers/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "customers", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "conversation": { + "links": { + "self": "/v1/conversations/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "conversation", + "id": "67dd47dfbc632c1804db1bf1" + } + } + }, + "links": { + "self": "/v1/messages/67dd47dfbc632c1804db1bf1" + } + }, + "createdAt": "2025-03-21T20:13:07.814Z", + "persist": true, + "client": "postmark-drafts-sender", + "dataId": "67dd47dfbc632c1804db1bf1", + "meta": { + "customChanged": false + }, + "snsFilters": [ + "assistant" + ] +} \ No newline at end of file diff --git a/components/kustomer/sources/updated-conversation-instant/test-event.mjs b/components/kustomer/sources/updated-conversation-instant/test-event.mjs new file mode 100644 index 0000000000000..16002e915f18a --- /dev/null +++ b/components/kustomer/sources/updated-conversation-instant/test-event.mjs @@ -0,0 +1,250 @@ +export default { + "id": "67dd47dfbc632c1804db1bf1", + "name": "kustomer.conversation.update", + "org": "67dd47dfbc632c1804db1bf1", + "partition": "67dd47dfbc632c1804db1bf1", + "orgName": "par-pipedream-berta", + "data": { + "type": "conversation", + "id": "67dd47dfbc632c1804db1bf1", + "attributes": { + "externalId": "123", + "name": "Updated Name", + "channels": [], + "status": "open", + "open": { + "statusAt": "2025-03-20T18:50:55.976Z" + }, + "messageCount": 0, + "noteCount": 0, + "satisfaction": 0, + "satisfactionLevel": { + "sentByTeams": [], + "answers": [] + }, + "createdAt": "2025-03-20T18:50:55.976Z", + "updatedAt": "2025-03-21T20:20:11.424Z", + "modifiedAt": "2025-03-21T20:20:11.424Z", + "lastActivityAt": "2025-03-20T18:50:55.981Z", + "spam": false, + "ended": false, + "tags": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "suggestedTags": [], + "predictions": [], + "suggestedShortcuts": [], + "firstMessageIn": {}, + "firstMessageOut": { + "createdByTeams": [] + }, + "lastMessageIn": {}, + "lastMessageOut": {}, + "lastMessageUnrespondedTo": {}, + "lastMessageUnrespondedToSinceLastDone": {}, + "assignedUsers": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "assignedTeams": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "firstResponse": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "firstResponseSinceLastDone": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "lastResponse": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "firstDone": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "lastDone": { + "createdByTeams": [], + "assignedTeams": [], + "assignedUsers": [] + }, + "direction": "in", + "outboundMessageCount": 0, + "inboundMessageCount": 0, + "rev": 3, + "priority": 5, + "defaultLang": "en_us", + "locale": "US", + "roleGroupVersions": [], + "accessOverride": [], + "modificationHistory": { + "nameAt": "2025-03-21T20:20:11.426Z", + "priorityAt": "2025-03-20T18:50:55.976Z", + "channelAt": "2025-03-20T18:50:55.976Z", + "assignedTeamsAt": "2025-03-20T18:50:55.976Z", + "assignedUsersAt": "2025-03-20T18:50:55.976Z", + "brandAt": null, + "defaultLangAt": null, + "statusAt": "2025-03-20T18:50:55.976Z", + "tagsAt": "2025-03-20T18:50:55.976Z", + "customAt": null + }, + "billingStatus": "no_messages", + "assistant": { + "fac": { + "reasons": [], + "exclusions": [], + "source": {} + }, + "assistantId": [] + }, + "aiAgent": { + "automationId": [] + }, + "phase": "initial", + "matchedTimeBasedRules": [] + }, + "relationships": { + "messages": { + "links": { + "self": "/v1/conversations/67dd47dfbc632c1804db1bf1/messages" + } + }, + "createdBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "modifiedBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "org": { + "links": { + "self": "/v1/orgs/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "org", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "customer": { + "data": { + "type": "customer", + "id": "67dd47dfbc632c1804db1bf1" + }, + "links": { + "self": "/v1/customers/67dd47dfbc632c1804db1bf1" + } + }, + "queue": { + "data": { + "type": "queue", + "id": "67dd47dfbc632c1804db1bf1" + }, + "links": { + "self": "/v1/routing/queues/67dd47dfbc632c1804db1bf1" + } + }, + "brand": { + "data": { + "type": "brand", + "id": "67dd47dfbc632c1804db1bf1" + }, + "links": { + "self": "/v1/brands/67dd47dfbc632c1804db1bf1" + } + } + }, + "links": { + "self": "/v1/conversations/67dd47dfbc632c1804db1bf1" + } + }, + "createdAt": "2025-03-21T20:20:11.435Z", + "changes": { + "attributes": { + "externalId": { + "op": "replace", + "before": "123", + "after": "321" + }, + "name": { + "op": "replace", + "before": "Name", + "after": "Update Name" + }, + "updatedAt": { + "op": "replace", + "before": "2025-03-21T19:54:02.663Z", + "after": "2025-03-21T20:20:11.424Z" + }, + "modifiedAt": { + "op": "replace", + "before": "2025-03-21T19:54:02.663Z", + "after": "2025-03-21T20:20:11.424Z" + }, + "rev": { + "op": "replace", + "before": 2, + "after": 3 + }, + "modificationHistory": { + "op": "replace", + "before": { + "nameAt": "2025-03-21T19:54:02.664Z", + "priorityAt": "2025-03-20T18:50:55.976Z", + "channelAt": "2025-03-20T18:50:55.976Z", + "assignedTeamsAt": "2025-03-20T18:50:55.976Z", + "assignedUsersAt": "2025-03-20T18:50:55.976Z", + "brandAt": null, + "defaultLangAt": null, + "statusAt": "2025-03-20T18:50:55.976Z", + "tagsAt": "2025-03-20T18:50:55.976Z", + "customAt": null + }, + "after": { + "nameAt": "2025-03-21T20:20:11.426Z", + "priorityAt": "2025-03-20T18:50:55.976Z", + "channelAt": "2025-03-20T18:50:55.976Z", + "assignedTeamsAt": "2025-03-20T18:50:55.976Z", + "assignedUsersAt": "2025-03-20T18:50:55.976Z", + "brandAt": null, + "defaultLangAt": null, + "statusAt": "2025-03-20T18:50:55.976Z", + "tagsAt": "2025-03-20T18:50:55.976Z", + "customAt": null + } + } + }, + "relationships": {} + }, + "persist": true, + "client": "api-gw", + "dataId": "67dd47dfbc632c1804db1bf1", + "meta": { + "customChanged": false + }, + "snsFilters": [ + "assistant", + "csat" + ] +} \ No newline at end of file diff --git a/components/kustomer/sources/updated-conversation-instant/updated-conversation-instant.mjs b/components/kustomer/sources/updated-conversation-instant/updated-conversation-instant.mjs index a3d1836668b05..eea7b13895ee1 100644 --- a/components/kustomer/sources/updated-conversation-instant/updated-conversation-instant.mjs +++ b/components/kustomer/sources/updated-conversation-instant/updated-conversation-instant.mjs @@ -1,113 +1,24 @@ -import { axios } from "@pipedream/platform"; -import kustomer from "../../kustomer.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "kustomer-updated-conversation-instant", - name: "Updated Conversation Instant", - description: "Emit new event when an existing conversation is updated in Kustomer. [See the documentation]().", - version: "0.0.{{ts}}", + name: "Updated Conversation (Instant)", + description: "Emit new event when an existing conversation is updated in Kustomer.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - kustomer, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, + methods: { + ...common.methods, + getEventType() { + return [ + "kustomer.conversation.update", + ]; }, - name: { - type: "string", - label: "Name", - description: "Name of the resource", + getSummary(body) { + return `Conversation Updated: ${body.data.attributes.name}`; }, - url: { - type: "string", - label: "URL", - description: "Webhook URL or similar", - }, - }, - hooks: { - async deploy() { - const lastTimestamp = await this.db.get("lastTimestamp") || 0; - const conversations = await this.kustomer.paginate(this.kustomer.listConversations, { - query: { - updated_at_gt: lastTimestamp, - }, - perpage: 50, - }); - - // Sort conversations by updatedAt in descending order - conversations.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); - - const latestConvos = conversations.slice(0, 50); - let latestTimestamp = lastTimestamp; - - for (const convo of latestConvos) { - const updatedAt = Date.parse(convo.updatedAt) || Date.now(); - const eventData = await this.kustomer.emitConversationUpdateEvent(this.name, this.url); - - this.$emit( - { - name: convo.name, - url: convo.url, - ...eventData, - }, - { - id: convo.id || updatedAt, - summary: `Conversation Updated: ${convo.name}`, - ts: updatedAt, - }, - ); - - if (updatedAt > latestTimestamp) { - latestTimestamp = updatedAt; - } - } - - await this.db.set("lastTimestamp", latestTimestamp); - }, - async activate() { - // No activation steps required for polling source - }, - async deactivate() { - // No deactivation steps required for polling source - }, - }, - async run() { - const lastTimestamp = await this.db.get("lastTimestamp") || 0; - const conversations = await this.kustomer.paginate(this.kustomer.listConversations, { - query: { - updated_at_gt: lastTimestamp, - }, - perpage: 50, - }); - - let latestTimestamp = lastTimestamp; - - for (const convo of conversations) { - const updatedAt = Date.parse(convo.updatedAt) || Date.now(); - const eventData = await this.kustomer.emitConversationUpdateEvent(this.name, this.url); - - this.$emit( - { - name: convo.name, - url: convo.url, - ...eventData, - }, - { - id: convo.id || updatedAt, - summary: `Conversation Updated: ${convo.name}`, - ts: updatedAt, - }, - ); - - if (updatedAt > latestTimestamp) { - latestTimestamp = updatedAt; - } - } - - await this.db.set("lastTimestamp", latestTimestamp); }, + sampleEmit, }; diff --git a/components/kustomer/sources/updated-customer-instant/test-event.mjs b/components/kustomer/sources/updated-customer-instant/test-event.mjs new file mode 100644 index 0000000000000..3aee78beffd11 --- /dev/null +++ b/components/kustomer/sources/updated-customer-instant/test-event.mjs @@ -0,0 +1,222 @@ +export default { + "id": "67dd47dfbc632c1804db1bf1", + "name": "kustomer.customer.update", + "org": "67dd47dfbc632c1804db1bf1", + "partition": "67dd47dfbc632c1804db1bf1", + "data": { + "type": "customer", + "id": "67dd47dfbc632c1804db1bf1", + "attributes": { + "name": "Updated Name", + "displayName": "Updated Name", + "displayColor": "pink", + "displayIcon": "stereo", + "externalId": "123", + "externalIds": [ + { + "externalId": "123", + "verified": true, + "externalVerified": false + } + ], + "sharedExternalIds": [ + { + "externalId": "123", + "verified": false, + "externalVerified": false + } + ], + "avatarUrl": "https://avatarUrl.com", + "username": "username", + "emails": [ + { + "type": "home", + "email": "email@email.com", + "verified": false, + "externalVerified": false + } + ], + "sharedEmails": [ + { + "type": "home", + "email": "email@email.com", + "verified": false, + "externalVerified": false + } + ], + "phones": [ + { + "type": "mobile", + "phone": "+1234567890", + "verified": false + } + ], + "sharedPhones": [ + { + "type": "mobile", + "phone": "+1234567890", + "verified": false + } + ], + "whatsapps": [ + { + "type": "mobile", + "phone": "whatsapp:+1234567890", + "verified": false + } + ], + "sharedWhatsapps": [], + "facebookIds": [], + "instagramIds": [], + "socials": [], + "sharedSocials": [], + "urls": [ + { + "type": "website", + "url": "https://url.com" + } + ], + "locations": [], + "activeUsers": [], + "watchers": [], + "recentLocation": { + "updatedAt": "2025-03-21T19:25:27.886Z" + }, + "locale": null, + "birthdayAt": "2001-12-12T12:12:12.000Z", + "gender": "f", + "createdAt": "2025-03-21T19:25:27.882Z", + "updatedAt": "2025-03-21T20:23:52.827Z", + "modifiedAt": "2025-03-21T20:23:52.827Z", + "lastActivityAt": "2025-03-21T20:23:52.826Z", + "deleted": false, + "lastConversation": { + "channels": [], + "tags": [] + }, + "conversationCounts": { + "done": 0, + "open": 0, + "snoozed": 0, + "all": 0 + }, + "preview": {}, + "tags": [ + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1", + "67dd47dfbc632c1804db1bf1" + ], + "sentiment": { + "polarity": -1, + "confidence": -1 + }, + "progressiveStatus": null, + "verified": true, + "rev": 7, + "recentItems": [], + "defaultLang": "af", + "satisfactionLevel": { + "firstSatisfaction": { + "sentByTeams": [] + }, + "lastSatisfaction": { + "sentByTeams": [] + } + }, + "roleGroupVersions": [], + "accessOverride": [], + "companyName": "company name", + "firstName": "company", + "lastName": "name" + }, + "relationships": { + "messages": { + "links": { + "self": "/v1/customers/67dd47dfbc632c1804db1bf1/messages" + } + }, + "createdBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "modifiedBy": { + "links": { + "self": "/v1/users/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "user", + "id": "67dd47dfbc632c1804db1bf1" + } + }, + "org": { + "data": { + "type": "org", + "id": "67dd47dfbc632c1804db1bf1" + }, + "links": { + "self": "/v1/orgs/67dd47dfbc632c1804db1bf1" + } + }, + "company": { + "links": { + "self": "/v1/companies/67dd47dfbc632c1804db1bf1" + }, + "data": { + "type": "company", + "id": "67dd47dfbc632c1804db1bf1" + } + } + }, + "links": { + "self": "/v1/customers/67dd47dfbc632c1804db1bf1" + } + }, + "createdAt": "2025-03-21T20:23:52.847Z", + "changes": { + "attributes": { + "name": { + "op": "replace", + "before": "Name", + "after": "Updated Name" + }, + "displayName": { + "op": "replace", + "before": "Name", + "after": "Updated Name" + }, + "updatedAt": { + "op": "replace", + "before": "2025-03-21T19:32:42.243Z", + "after": "2025-03-21T20:23:52.827Z" + }, + "modifiedAt": { + "op": "replace", + "before": "2025-03-21T19:32:42.243Z", + "after": "2025-03-21T20:23:52.827Z" + }, + "lastActivityAt": { + "op": "replace", + "before": "2025-03-21T19:32:42.242Z", + "after": "2025-03-21T20:23:52.826Z" + }, + "rev": { + "op": "replace", + "before": 6, + "after": 7 + }, + }, + "relationships": {} + }, + "persist": true, + "client": "api-gw", + "dataId": "67dd47dfbc632c1804db1bf1", + "meta": { + "customChanged": false + }, + "snsFilters": [] +} \ No newline at end of file diff --git a/components/kustomer/sources/updated-customer-instant/updated-customer-instant.mjs b/components/kustomer/sources/updated-customer-instant/updated-customer-instant.mjs index 528e2ad008bb3..bfc23bd58c609 100644 --- a/components/kustomer/sources/updated-customer-instant/updated-customer-instant.mjs +++ b/components/kustomer/sources/updated-customer-instant/updated-customer-instant.mjs @@ -1,111 +1,24 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import kustomer from "../../kustomer.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "kustomer-updated-customer-instant", - name: "Updated Customer", - description: "Emit a new event when an existing customer's details are updated in Kustomer. [See the documentation]()", - version: "0.0.{{ts}}", + name: "Updated Customer (Instant)", + description: "Emit new event when an existing customer's details are updated in Kustomer.", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - kustomer, - db: { - type: "$.service.db", - secret: false, - }, - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - }, - hooks: { - async deploy() { - const customers = await this._getCustomers({ - limit: 50, - sort: "-updatedAt", - }); - - for (const customer of customers) { - this.$emit( - { - name: customer.name, - url: customer.url, - event: "kustomer.customer.update", - }, - { - id: customer.id, - summary: `Updated customer: ${customer.name}`, - ts: new Date(customer.updatedAt).getTime(), - }, - ); - } - - if (customers.length > 0) { - const latestUpdatedAt = Math.max(...customers.map((c) => new Date(c.updatedAt).getTime())); - this.db.set("lastUpdatedAt", latestUpdatedAt); - } - }, - async activate() { - // No activation logic required for polling source - }, - async deactivate() { - // No deactivation logic required for polling source - }, - }, methods: { - async _getCustomers({ - limit = 50, sort = "-updatedAt", since = null, - }) { - const params = { - limit, - sort, - }; - if (since) { - params.filter = { - updatedAt: { - gte: new Date(since).toISOString(), - }, - }; - } - - return await this.kustomer._makeRequest({ - path: "/customers", - params, - }); + ...common.methods, + getEventType() { + return [ + "kustomer.customer.update", + ]; + }, + getSummary(body) { + return `Customer Updated: ${body.data.attributes.name}`; }, }, - async run() { - const lastUpdatedAt = this.db.get("lastUpdatedAt") || 0; - const customers = await this._getCustomers({ - limit: 50, - sort: "updatedAt", - since: lastUpdatedAt, - }); - - for (const customer of customers) { - this.$emit( - { - name: customer.name, - url: customer.url, - event: "kustomer.customer.update", - }, - { - id: customer.id, - summary: `Updated customer: ${customer.name}`, - ts: new Date(customer.updatedAt).getTime(), - }, - ); - - const customerUpdatedAt = new Date(customer.updatedAt).getTime(); - - if (customerUpdatedAt > lastUpdatedAt) { - this.db.set("lastUpdatedAt", customerUpdatedAt); - } - } - }, + sampleEmit, }; From f9c308cb41507d3206c3504042e3594b918ba015 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 21 Mar 2025 17:31:02 -0300 Subject: [PATCH 3/4] pnpm update --- pnpm-lock.yaml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58e08a1481b83..f0a04fd82b6cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4658,9 +4658,6 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/formatting: - specifiers: {} - components/formbricks: dependencies: '@pipedream/platform': @@ -6898,7 +6895,11 @@ importers: components/kucoin_futures: {} - components/kustomer: {} + components/kustomer: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/kvdb: dependencies: @@ -8040,8 +8041,7 @@ importers: specifier: ^3.0.0 version: 3.0.3 - components/miyn: - specifiers: {} + components/miyn: {} components/moaform: dependencies: @@ -9325,8 +9325,7 @@ importers: components/perry_github_test: {} - components/persanaai: - specifiers: {} + components/persanaai: {} components/persistiq: dependencies: From 93975c02f445174e575632a2a889f0032f28f6e6 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 24 Mar 2025 12:21:33 -0300 Subject: [PATCH 4/4] fix error validation --- components/kustomer/common/utils.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/kustomer/common/utils.mjs b/components/kustomer/common/utils.mjs index ff447f0ae1e7b..39432d01960a7 100644 --- a/components/kustomer/common/utils.mjs +++ b/components/kustomer/common/utils.mjs @@ -28,7 +28,10 @@ export const parseObject = (obj) => { export const throwError = (message) => { const parsedMessage = JSON.parse(message); const error = parsedMessage.errors[0]; - const errorParameter = error.meta.subErrors[0].source.pointer || error.source.parameter; - const errorMessage = error.detail || error.meta.subErrors[0].title; + const errorParameter = + error.source.parameter || + error.source.pointer || + error.meta?.subErrors[0]?.source?.pointer; + const errorMessage = error.detail || error.title || error.meta?.subErrors[0]?.title; throw new ConfigurationError(`${errorParameter} - ${errorMessage}`); };