Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Components]: heyy #14430

Merged
merged 1 commit into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions components/heyy/actions/create-contact/create-contact.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { ConfigurationError } from "@pipedream/platform";
import app from "../../heyy.app.mjs";
import utils from "../../common/utils.mjs";

export default {
key: "heyy-create-contact",
name: "Create Contact",
description: "Creates a new contact for the business. [See the documentation](https://documenter.getpostman.com/view/27408936/2sA2r3a6DW#a1249b8d-10cf-446a-be35-eb8793ffa967).",
version: "0.0.1",
type: "action",
props: {
app,
phoneNumber: {
propDefinition: [
app,
"phoneNumber",
],
},
firstName: {
propDefinition: [
app,
"firstName",
],
},
lastName: {
propDefinition: [
app,
"lastName",
],
},
email: {
propDefinition: [
app,
"email",
],
},
labels: {
propDefinition: [
app,
"labels",
],
},
attributes: {
propDefinition: [
app,
"attributes",
],
},
},
Comment on lines +11 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing address field from PR objectives.

The PR objectives mention 'address' as an optional property, but it's not implemented in the props definition.

Add the address field to the props:

    attributes: {
      propDefinition: [
        app,
        "attributes",
      ],
    },
+   address: {
+     type: "string",
+     label: "Address",
+     description: "Contact's address",
+     optional: true,
+   },

Don't forget to include it in the data object when making the API call:

    data: {
      phoneNumber,
      firstName,
      lastName,
      email,
+     address,
      ...(labels?.length && {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
props: {
app,
phoneNumber: {
propDefinition: [
app,
"phoneNumber",
],
},
firstName: {
propDefinition: [
app,
"firstName",
],
},
lastName: {
propDefinition: [
app,
"lastName",
],
},
email: {
propDefinition: [
app,
"email",
],
},
labels: {
propDefinition: [
app,
"labels",
],
},
attributes: {
propDefinition: [
app,
"attributes",
],
},
},
props: {
app,
phoneNumber: {
propDefinition: [
app,
"phoneNumber",
],
},
firstName: {
propDefinition: [
app,
"firstName",
],
},
lastName: {
propDefinition: [
app,
"lastName",
],
},
email: {
propDefinition: [
app,
"email",
],
},
labels: {
propDefinition: [
app,
"labels",
],
},
attributes: {
propDefinition: [
app,
"attributes",
],
},
address: {
type: "string",
label: "Address",
description: "Contact's address",
optional: true,
},
},

⚠️ Potential issue

Add missing notes field from PR objectives.

The PR objectives mention 'notes' as an optional property, but it's not implemented in the props definition.

Add the notes field to the props:

    attributes: {
      propDefinition: [
        app,
        "attributes",
      ],
    },
+   notes: {
+     type: "string",
+     label: "Notes",
+     description: "Additional notes about the contact",
+     optional: true,
+   },

Don't forget to include it in the data object when making the API call:

    data: {
      phoneNumber,
      firstName,
      lastName,
      email,
+     notes,
      ...(labels?.length && {

Committable suggestion was skipped due to low confidence.

methods: {
createContact(args = {}) {
return this.app.post({
path: "/contacts",
...args,
});
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
},
async run({ $ }) {
const {
createContact,
phoneNumber,
firstName,
lastName,
email,
labels,
attributes,
} = this;

if (!utils.isPhoneNumberValid(phoneNumber)) {
throw new ConfigurationError(`The phone number \`${phoneNumber}\` is invalid. Please provide a valid phone number.`);
}

const response = await createContact({
$,
data: {
phoneNumber,
firstName,
lastName,
email,
...(labels?.length && {
labels: labels.map((name) => ({
name,
})),
}),
attributes:
attributes && Object.entries(attributes)
.reduce((acc, [
externalId,
value,
]) => ([
...acc,
{
externalId,
value,
},
]), []),
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
jcortes marked this conversation as resolved.
Show resolved Hide resolved
});
$.export("$summary", `Successfully created contact with ID \`${response.data.id}\`.`);
return response;
jcortes marked this conversation as resolved.
Show resolved Hide resolved
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import app from "../../heyy.app.mjs";
import constants from "../../common/constants.mjs";
import utils from "../../common/utils.mjs";

export default {
key: "heyy-send-whatsapp-message",
name: "Send WhatsApp Message",
description: "Sends a WhatsApp message to a contact. [See the documentation](https://documenter.getpostman.com/view/27408936/2sa2r3a6dw)",
version: "0.0.1",
type: "action",
props: {
app,
channelId: {
propDefinition: [
app,
"channelId",
],
},
phoneNumber: {
label: "Phone Number",
description: "The phone number of the contact.",
propDefinition: [
app,
"contactId",
() => ({
mapper: ({
firstName, phoneNumber: value,
}) => ({
label: firstName || value,
value,
}),
}),
],
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
msgType: {
type: "string",
label: "Message Type",
description: "The type of message to send.",
options: Object.values(constants.MSG_TYPE),
reloadProps: true,
},
},
additionalProps() {
const { msgType } = this;

const bodyText = {
type: "string",
label: "Body Text",
description: "The text of the message to send.",
};

if (msgType === constants.MSG_TYPE.TEXT) {
return {
bodyText,
};
}

if (msgType === constants.MSG_TYPE.IMAGE) {
return {
bodyText,
fileId: {
type: "string",
label: "File ID",
description: "The ID of the file to attach to the message.",
},
};
}

if (msgType === constants.MSG_TYPE.TEMPLATE) {
return {
messageTemplateId: {
type: "string",
label: "Message Template ID",
description: "The ID of the message template to use.",
optional: true,
options: async ({ page }) => {
const { data: { messageTemplates } } = await this.app.getMessageTemplates({
params: {
page,
sortBy: "updatedAt",
order: "DESC",
},
});
return messageTemplates.map(({
id: value, name: label,
}) => ({
label,
value,
}));
},
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
};
}

if (msgType === constants.MSG_TYPE.INTERACTIVE) {
return {
bodyText,
buttons: {
type: "string[]",
label: "Buttons",
description: "The buttons to include in the message. Each row should have a JSON formated string. Eg. `{ \"id\": \"STRING\", \"title\": \"STRING\" }`.",
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
headerText: {
type: "string",
label: "Header Text",
description: "The header text of the message to send.",
optional: true,
},
footerText: {
type: "string",
label: "Footer Text",
description: "The footer text of the message to send.",
optional: true,
},
};
}
jcortes marked this conversation as resolved.
Show resolved Hide resolved

return {};
},
Comment on lines +43 to +119
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for missing required props based on message type

While the code handles different message types, it doesn't validate that required properties are provided before they're used. For example, IMAGE type requires fileId but there's no validation.

 additionalProps() {
   const { msgType } = this;
+  
+  const requiredError = (field) => `${field} is required for ${msgType} messages`;
 
   const bodyText = {
     type: "string",
     label: "Body Text",
     description: "The text of the message to send.",
+    validate: (value, { msgType }) => {
+      if (msgType === constants.MSG_TYPE.TEXT && !value) {
+        return requiredError("Body text");
+      }
+      return true;
+    },
   };

Committable suggestion was skipped due to low confidence.

methods: {
sendWhatsappMessage({
channelId, ...args
} = {}) {
return this.app.post({
path: `/${channelId}/whatsapp_messages/send`,
...args,
jcortes marked this conversation as resolved.
Show resolved Hide resolved
});
},
},
Comment on lines +120 to +129
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling in sendWhatsappMessage method

The method should handle potential API errors gracefully and provide meaningful error messages.

 sendWhatsappMessage({
   channelId, ...args
 } = {}) {
+  if (!channelId) {
+    throw new Error("Channel ID is required");
+  }
   return this.app.post({
     path: `/${channelId}/whatsapp_messages/send`,
     ...args,
+  }).catch(error => {
+    throw new Error(`Failed to send WhatsApp message: ${error.message}`);
   });
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
methods: {
sendWhatsappMessage({
channelId, ...args
} = {}) {
return this.app.post({
path: `/${channelId}/whatsapp_messages/send`,
...args,
});
},
},
methods: {
sendWhatsappMessage({
channelId, ...args
} = {}) {
if (!channelId) {
throw new Error("Channel ID is required");
}
return this.app.post({
path: `/${channelId}/whatsapp_messages/send`,
...args,
}).catch(error => {
throw new Error(`Failed to send WhatsApp message: ${error.message}`);
});
},
},

async run({ $ }) {
const {
sendWhatsappMessage,
channelId,
phoneNumber,
msgType,
bodyText,
fileId,
messageTemplateId,
headerText,
footerText,
buttons,
} = this;

jcortes marked this conversation as resolved.
Show resolved Hide resolved
const response = await sendWhatsappMessage({
$,
channelId,
data: {
phoneNumber,
type: msgType,
bodyText,
fileId,
messageTemplateId,
headerText,
footerText,
buttons: utils.parseArray(buttons),
jcortes marked this conversation as resolved.
Show resolved Hide resolved
},
});
$.export("$summary", "Succesfully sent WhatsApp message.");
return response;
jcortes marked this conversation as resolved.
Show resolved Hide resolved
},
Comment on lines +130 to +160
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add safe handling of optional properties in the request payload

The current implementation sends all properties to the API, including undefined ones. This should be cleaned up to only send defined values.

-    const response = await sendWhatsappMessage({
-      $,
-      channelId,
-      data: {
-        phoneNumber,
-        type: msgType,
-        bodyText,
-        fileId,
-        messageTemplateId,
-        headerText,
-        footerText,
-        buttons: utils.parseArray(buttons),
-      },
-    });
+    const data = {
+      phoneNumber,
+      type: msgType,
+    };
+
+    if (bodyText) data.bodyText = bodyText;
+    if (fileId) data.fileId = fileId;
+    if (messageTemplateId) data.messageTemplateId = messageTemplateId;
+    if (headerText) data.headerText = headerText;
+    if (footerText) data.footerText = footerText;
+    if (buttons) data.buttons = utils.parseArray(buttons);
+
+    const response = await sendWhatsappMessage({
+      $,
+      channelId,
+      data,
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async run({ $ }) {
const {
sendWhatsappMessage,
channelId,
phoneNumber,
msgType,
bodyText,
fileId,
messageTemplateId,
headerText,
footerText,
buttons,
} = this;
const response = await sendWhatsappMessage({
$,
channelId,
data: {
phoneNumber,
type: msgType,
bodyText,
fileId,
messageTemplateId,
headerText,
footerText,
buttons: utils.parseArray(buttons),
},
});
$.export("$summary", "Succesfully sent WhatsApp message.");
return response;
},
async run({ $ }) {
const {
sendWhatsappMessage,
channelId,
phoneNumber,
msgType,
bodyText,
fileId,
messageTemplateId,
headerText,
footerText,
buttons,
} = this;
const data = {
phoneNumber,
type: msgType,
};
if (bodyText) data.bodyText = bodyText;
if (fileId) data.fileId = fileId;
if (messageTemplateId) data.messageTemplateId = messageTemplateId;
if (headerText) data.headerText = headerText;
if (footerText) data.footerText = footerText;
if (buttons) data.buttons = utils.parseArray(buttons);
const response = await sendWhatsappMessage({
$,
channelId,
data,
});
$.export("$summary", "Succesfully sent WhatsApp message.");
return response;
},

};
115 changes: 115 additions & 0 deletions components/heyy/actions/update-contact/update-contact.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { ConfigurationError } from "@pipedream/platform";
import app from "../../heyy.app.mjs";
import utils from "../../common/utils.mjs";

export default {
key: "heyy-update-contact",
name: "Update Contact",
description: "Updates the details of a contact under your business. [See the documentation](https://documenter.getpostman.com/view/27408936/2sA2r3a6DW#5a5ee22b-c16e-4d46-ae5d-3844b6501a34).",
version: "0.0.1",
type: "action",
props: {
app,
contactId: {
propDefinition: [
app,
"contactId",
],
},
phoneNumber: {
optional: true,
propDefinition: [
app,
"phoneNumber",
],
},
firstName: {
optional: true,
propDefinition: [
app,
"firstName",
],
},
lastName: {
propDefinition: [
app,
"lastName",
],
},
email: {
propDefinition: [
app,
"email",
],
},
labels: {
propDefinition: [
app,
"labels",
],
},
attributes: {
propDefinition: [
app,
"attributes",
],
},
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
methods: {
updateContact({
contactId, ...args
} = {}) {
return this.app.put({
path: `/contacts/${contactId}`,
...args,
jcortes marked this conversation as resolved.
Show resolved Hide resolved
});
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
async run({ $ }) {
const {
updateContact,
contactId,
phoneNumber,
firstName,
lastName,
email,
labels,
attributes,
} = this;

jcortes marked this conversation as resolved.
Show resolved Hide resolved
if (phoneNumber && !utils.isPhoneNumberValid(phoneNumber)) {
throw new ConfigurationError(`The phone number \`${phoneNumber}\` is invalid. Please provide a valid phone number.`);
}

const response = await updateContact({
$,
contactId,
data: {
phoneNumber,
firstName,
lastName,
email,
...(labels?.length && {
labels: labels.map((name) => ({
name,
})),
}),
jcortes marked this conversation as resolved.
Show resolved Hide resolved
attributes:
attributes && Object.entries(attributes)
.reduce((acc, [
externalId,
value,
]) => ([
...acc,
{
externalId,
value,
},
]), []),
jcortes marked this conversation as resolved.
Show resolved Hide resolved
},
});
jcortes marked this conversation as resolved.
Show resolved Hide resolved

$.export("$summary", `Successfully updated contact with ID \`${response.data.id}\`.`);
return response;
},
jcortes marked this conversation as resolved.
Show resolved Hide resolved
};
Loading
Loading