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

New Components - ortto #14703

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

New Components - ortto #14703

wants to merge 8 commits into from

Conversation

luancazarine
Copy link
Collaborator

@luancazarine luancazarine commented Nov 21, 2024

Resolves #13209.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a module for creating custom activities.
    • Added functionality for creating or updating a person in the Ortto account.
    • Implemented an opt-out feature for SMS communications.
    • New event source for emitting events when a new contact is created.
  • Improvements

    • Added utility functions for object manipulation and JSON parsing.
    • Introduced a polling mechanism for event management.
  • Version Update

    • Updated package version from 0.0.2 to 0.1.0.

@luancazarine luancazarine added the ai-assisted Content generated by AI, with human refinement and modification label Nov 21, 2024
Copy link

vercel bot commented Nov 21, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

3 Skipped Deployments
Name Status Preview Comments Updated (UTC)
docs-v2 ⬜️ Ignored (Inspect) Visit Preview Dec 5, 2024 4:30pm
pipedream-docs ⬜️ Ignored (Inspect) Dec 5, 2024 4:30pm
pipedream-docs-redirect-do-not-edit ⬜️ Ignored (Inspect) Dec 5, 2024 4:30pm

Copy link
Contributor

coderabbitai bot commented Nov 21, 2024

Walkthrough

This pull request introduces several new modules and functionalities for the Ortto platform, including actions for creating custom activities, managing contacts, and opting out of SMS communications. It also adds utility functions for object manipulation and a constant for limits. Additionally, it restructures the application setup and updates the package metadata. The .gitignore file has been removed, and an application component has been created to manage event emissions based on a polling mechanism.

Changes

File Path Change Summary
components/ortto/.gitignore Deleted file that specified ignored patterns for Git.
components/ortto/actions/create-custom-activity/create-custom-activity.mjs New module for creating custom activities with an asynchronous run method.
components/ortto/actions/create-person/create-person.mjs New action for creating or updating a person, with an asynchronous run method.
components/ortto/actions/opt-out-sms/opt-out-sms.mjs New module for opting out of SMS communications, with an asynchronous run method.
components/ortto/app/ortto.app.ts Deleted application file that defined basic app properties and logging functionality.
components/ortto/common/constants.mjs Introduced a new constant LIMIT with a value of 100.
components/ortto/common/utils.mjs Added two utility functions: clearObj and parseObject.
components/ortto/ortto.app.mjs New application component defining structure and functionalities for the Ortto platform.
components/ortto/package.json Updated version to 0.1.0, changed main entry point, removed files field, and added new dependency.
components/ortto/sources/common/base.mjs New component for managing events with a timer-based polling mechanism.
components/ortto/sources/new-contact-created/new-contact-created.mjs New source module for emitting events when a new contact is created.
components/ortto/sources/new-contact-created/test-event.mjs New module representing a sample new contact creation event.

Assessment against linked issues

Objective Addressed Explanation
Emit new event when a contact is created in your Ortto account. (#13209)
Creates a unique activity for a user. (#13209)
Allows a user to opt out from all SMS communications. (#13209)
Initializes a new record or updates a preexisting record in the Ortto account. (#13209) The functionality for creating or updating organizations is not included.
Emit new event when a contact is updated in your Ortto account. (#13209) The changes do not specify handling for contact updates.

Possibly related PRs

Suggested reviewers

  • michelle0927

Poem

In the garden of Ortto, changes bloom,
New actions sprout, dispelling the gloom.
Contacts and activities, all in a dance,
With a hop and a skip, we take our chance.
Opt-out of SMS, let silence reign,
In this vibrant patch, we grow without pain! 🐇🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Sources
 - New Contact Created

Actions
 - Create Custom Activity
 - Opt Out SMS
lcaresia
lcaresia previously approved these changes Nov 26, 2024
@luancazarine luancazarine marked this pull request as ready for review December 2, 2024 18:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Outside diff range and nitpick comments (7)
components/ortto/common/utils.mjs (1)

18-41: Add input validation to parseObject

The function handles JSON parsing well but could benefit from input validation.

Consider this enhanced implementation:

 export const parseObject = (obj) => {
   if (!obj) return undefined;
+  if (typeof obj !== 'string' && !Array.isArray(obj) && typeof obj !== 'object') {
+    return obj;
+  }
 
   if (Array.isArray(obj)) {
     return obj.map((item) => {
components/ortto/actions/opt-out-sms/opt-out-sms.mjs (1)

36-37: Enhance success message with more details

The success message could be more informative.

Consider this enhancement:

-    $.export("$summary", `Successfully opted out SMS for User ID: ${this.userEmail}`);
+    $.export("$summary", `Successfully opted out SMS communications for user: ${this.userEmail}. Updated at: ${new Date().toISOString()}`);
components/ortto/sources/new-contact-created/new-contact-created.mjs (2)

6-11: Consider incrementing the version number

The version is set to "0.0.1" which suggests this is the first implementation. However, given that this is implementing a core feature from issue #13209, consider starting with version "1.0.0" to indicate a stable, production-ready release.


43-45: Enhance contact summary formatting

The current summary concatenation could result in multiple spaces when fields are empty. Consider using a more robust approach:

-      return `New Contact: ${item.fields["str::first"] || ""} ${item.fields["str::last"] || ""}  ${item.fields["str::email"] || ""} `;
+      const parts = [
+        "New Contact:",
+        item.fields["str::first"],
+        item.fields["str::last"],
+        item.fields["str::email"],
+      ].filter(Boolean);
+      return parts.join(" ");
components/ortto/actions/create-person/create-person.mjs (1)

4-9: Consider starting with version 1.0.0

Since this is a production-ready component that other developers will depend on, consider starting with version 1.0.0 instead of 0.0.1 to indicate stability and API readiness.

components/ortto/ortto.app.mjs (2)

12-29: Optimize user email options loading

The options method could be optimized to:

  1. Cache results to prevent unnecessary API calls
  2. Add error handling for API failures
 async options({ page }) {
+  try {
+    if (!this._cachedContacts) {
+      this._cachedContacts = new Map();
+    }
+    
+    const cacheKey = `page_${page}`;
+    if (this._cachedContacts.has(cacheKey)) {
+      return this._cachedContacts.get(cacheKey);
+    }
+    
     const { contacts } = await this.listPeople({
       data: {
         limit: LIMIT,
         offset: LIMIT * page,
         fields: [
           "str::first",
           "str::last",
           "str::email",
         ],
       },
     });
 
-    return contacts.map(({ fields }) => ({
+    const options = contacts.map(({ fields }) => ({
       label: `${fields["str::first"]} ${fields["str::last"]} (${fields["str::email"]})`,
       value: fields["str::email"],
     }));
+    
+    this._cachedContacts.set(cacheKey, options);
+    return options;
+  } catch (error) {
+    throw new Error(`Failed to load user emails: ${error.message}`);
+  }
 },

93-113: Optimize pagination implementation

The current pagination implementation could be improved:

  1. Add handling for empty responses
  2. Add memory optimization for large datasets
 async *paginate({
   fn, data = {}, fieldName, ...opts
 }) {
   let hasMore = false;
   let page = 0;
+  let totalProcessed = 0;
 
   do {
     data.limit = LIMIT;
     data.offset = LIMIT * page++;
+    
+    try {
       const response = await fn({
         data,
         ...opts,
       });
+      
+      if (!response?.[fieldName]?.length) {
+        break;
+      }
+      
       for (const d of response[fieldName]) {
         yield d;
+        totalProcessed++;
       }
 
       hasMore = response.has_more;
+      
+      // Optional: Add a circuit breaker for safety
+      if (totalProcessed > 1000000) {
+        throw new Error('Pagination limit exceeded');
+      }
+    } catch (error) {
+      throw new Error(`Pagination failed: ${error.message}`);
+    }
   } while (hasMore);
 },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 28a5961 and f7eb161.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • components/ortto/.gitignore (0 hunks)
  • components/ortto/actions/create-custom-activity/create-custom-activity.mjs (1 hunks)
  • components/ortto/actions/create-person/create-person.mjs (1 hunks)
  • components/ortto/actions/opt-out-sms/opt-out-sms.mjs (1 hunks)
  • components/ortto/app/ortto.app.ts (0 hunks)
  • components/ortto/common/constants.mjs (1 hunks)
  • components/ortto/common/utils.mjs (1 hunks)
  • components/ortto/ortto.app.mjs (1 hunks)
  • components/ortto/package.json (1 hunks)
  • components/ortto/sources/common/base.mjs (1 hunks)
  • components/ortto/sources/new-contact-created/new-contact-created.mjs (1 hunks)
  • components/ortto/sources/new-contact-created/test-event.mjs (1 hunks)
💤 Files with no reviewable changes (2)
  • components/ortto/.gitignore
  • components/ortto/app/ortto.app.ts
✅ Files skipped from review due to trivial changes (2)
  • components/ortto/common/constants.mjs
  • components/ortto/sources/new-contact-created/test-event.mjs
🧰 Additional context used
🪛 Biome (1.9.4)
components/ortto/common/utils.mjs

[error] 11-11: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

🔇 Additional comments (5)
components/ortto/package.json (1)

3-16: Verify version bump rationale

The changes look good, but the version bump from 0.0.2 to 0.1.0 suggests significant changes. Please ensure this follows semantic versioning principles given the new Ortto components being added.

✅ Verification successful

Version bump to 0.1.0 is justified

The version bump from 0.0.2 to 0.1.0 is appropriate as this appears to be the initial implementation of the Ortto integration with multiple new components:

  • 3 new actions: create-custom-activity, create-person, and opt-out-sms
  • New source component: new-contact-created
  • Common utilities and constants
  • Base implementation for sources

This represents a significant feature addition that warrants a minor version bump according to semantic versioning principles.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are breaking changes that justify the minor version bump
git diff main -- components/ortto/ | grep -B 2 -A 2 "^[-+]" | grep -v "^[-+]package.json"

Length of output: 118


Script:

#!/bin/bash
# Let's first check what files exist in the components/ortto directory
fd . components/ortto/

# Then check the git history for this directory
git log --oneline -- components/ortto/

# Also check if there are any new files added in this PR
git status

Length of output: 1034


Script:

#!/bin/bash
# Let's check if these are new files by trying to find them in the current branch
git ls-tree -r HEAD:components/ortto --name-only

# Also let's check the content of the README to see if it provides any insights about the changes
cat components/ortto/README.md

Length of output: 1977

components/ortto/sources/new-contact-created/new-contact-created.mjs (1)

20-42: Review and document field identifiers

The field identifiers use shorthand prefixes (str::, phn::, geo::, etc.) which might not be immediately clear to other developers. Consider:

  1. Adding documentation explaining these prefixes
  2. Verifying these match Ortto's API specifications
components/ortto/sources/common/base.mjs (1)

41-46: ⚠️ Potential issue

Fix potential race condition in lastDate update

The current implementation might miss events if multiple instances run simultaneously. Consider using timestamps instead of IDs for tracking:

       if (responseArray.length) {
         if (maxResults && (responseArray.length > maxResults)) {
           responseArray.length = maxResults;
         }
-        this._setLastDate(responseArray[0].id);
+        this._setLastDate(Date.parse(responseArray[0].fields.c));
       }

Likely invalid or redundant comment.

components/ortto/ortto.app.mjs (2)

79-85: 🛠️ Refactor suggestion

Remove duplicate method definition in createPerson

The createPerson method unnecessarily specifies method: "POST" when it's already set in _makeRequest:

 createPerson(opts = {}) {
   return this._makeRequest({
-    method: "POST",
     path: "/person/merge",
     ...opts,
   });
 },

Likely invalid or redundant comment.


48-56: Verify API key security measures

Ensure proper handling of API credentials:

  1. Verify that the API key is properly encrypted in storage
  2. Consider implementing API key rotation mechanism
✅ Verification successful

API key handling follows secure practices

The implementation follows secure practices for API key handling:

  • API key is stored in the auth context (this.$auth) and not hardcoded
  • Key is passed securely in headers using HTTPS
  • No exposure of credentials in logs or error messages
  • Standard header-based authentication using X-Api-Key
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential API key exposure in other files
rg -i "api[-_]key|apikey" --type js --type ts

# Check for proper auth implementation
ast-grep --pattern 'this.$auth'

Length of output: 67035


Script:

#!/bin/bash
# Check for API key storage and handling patterns
rg -i "api[-_]key|apikey" --type js --type ts -A 5 -B 5 components/ortto/

# Check for auth implementation patterns
ast-grep --pattern 'class $_ {
  $$$
  $auth() {
    $$$
  }
  $$$
}'

Length of output: 147


Script:

#!/bin/bash
# Check for API key storage and handling patterns in ortto component
rg -i "api[-_]key|apikey" components/ortto/ortto.app.mjs

# Check for auth implementation and headers
rg -i "headers|auth" components/ortto/ortto.app.mjs -A 5 -B 5

# Look for any sensitive data handling patterns
rg -i "sensitive|secret|credentials" components/ortto/

Length of output: 881

Comment on lines +1 to +16
export const clearObj = (obj) => {
return Object.entries(obj)
.filter(([
,
v,
]) => (v != null && v != "" && JSON.stringify(v) != "{}"))
.reduce((acc, [
k,
v,
]) => ({
...acc,
[k]: (!Array.isArray(v) && v === Object(v))
? clearObj(v)
: v,
}), {});
};
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

Performance and safety improvements needed for clearObj

The current implementation has several concerns:

  1. Using spread operator in reduce causes O(n²) time complexity
  2. Recursive calls without depth limit could cause stack overflow
  3. Missing input validation

Consider this safer implementation:

-export const clearObj = (obj) => {
-  return Object.entries(obj)
-    .filter(([, v]) => (v != null && v != "" && JSON.stringify(v) != "{}"))
-    .reduce((acc, [k, v]) => ({
-      ...acc,
-      [k]: (!Array.isArray(v) && v === Object(v))
-        ? clearObj(v)
-        : v,
-    }), {});
-};
+export const clearObj = (obj, maxDepth = 10) => {
+  if (!obj || typeof obj !== 'object') return obj;
+  if (maxDepth <= 0) return obj;
+
+  const result = {};
+  for (const [k, v] of Object.entries(obj)) {
+    if (v == null || v === "" || JSON.stringify(v) === "{}") continue;
+    result[k] = (!Array.isArray(v) && v === Object(v))
+      ? clearObj(v, maxDepth - 1)
+      : v;
+  }
+  return result;
+};
📝 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
export const clearObj = (obj) => {
return Object.entries(obj)
.filter(([
,
v,
]) => (v != null && v != "" && JSON.stringify(v) != "{}"))
.reduce((acc, [
k,
v,
]) => ({
...acc,
[k]: (!Array.isArray(v) && v === Object(v))
? clearObj(v)
: v,
}), {});
};
export const clearObj = (obj, maxDepth = 10) => {
if (!obj || typeof obj !== 'object') return obj;
if (maxDepth <= 0) return obj;
const result = {};
for (const [k, v] of Object.entries(obj)) {
if (v == null || v === "" || JSON.stringify(v) === "{}") continue;
result[k] = (!Array.isArray(v) && v === Object(v))
? clearObj(v, maxDepth - 1)
: v;
}
return result;
};
🧰 Tools
🪛 Biome (1.9.4)

[error] 11-11: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

Comment on lines +19 to +34
const response = await this.ortto.updatePerson({
data: {
people: [
{
fields: {
"str::email": this.userEmail,
"bol::sp": false,
},
},
],
async: false,
merge_by: [
"str::email",
],
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract field names as constants and add response validation

The implementation has hard-coded field names and lacks response validation.

Consider these improvements:

+const FIELD_NAMES = {
+  EMAIL: "str::email",
+  SMS_PREFERENCE: "bol::sp",
+};
+
 export default {
   // ... existing code ...
   async run({ $ }) {
     const response = await this.ortto.updatePerson({
       data: {
         people: [
           {
             fields: {
-              "str::email": this.userEmail,
-              "bol::sp": false,
+              [FIELD_NAMES.EMAIL]: this.userEmail,
+              [FIELD_NAMES.SMS_PREFERENCE]: false,
             },
           },
         ],
         async: false,
         merge_by: [
-          "str::email",
+          FIELD_NAMES.EMAIL,
         ],
       },
     });
+
+    if (!response?.success) {
+      throw new Error(`Failed to opt out SMS: ${response?.message || 'Unknown error'}`);
+    }
📝 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
const response = await this.ortto.updatePerson({
data: {
people: [
{
fields: {
"str::email": this.userEmail,
"bol::sp": false,
},
},
],
async: false,
merge_by: [
"str::email",
],
},
});
const FIELD_NAMES = {
EMAIL: "str::email",
SMS_PREFERENCE: "bol::sp",
};
const response = await this.ortto.updatePerson({
data: {
people: [
{
fields: {
[FIELD_NAMES.EMAIL]: this.userEmail,
[FIELD_NAMES.SMS_PREFERENCE]: false,
},
},
],
async: false,
merge_by: [
FIELD_NAMES.EMAIL,
],
},
});
if (!response?.success) {
throw new Error(`Failed to opt out SMS: ${response?.message || 'Unknown error'}`);
}

Comment on lines +31 to +47
async run({ $ }) {
const response = await this.ortto.createCustomActivity({
$,
data: {
"activities": [
{
activity_id: this.activityId,
attributes: parseObject(this.attributes),
fields: parseObject(this.fields),
},
],
"merge_by": [
"str::email",
],
},
});

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 and input validation

The run method lacks error handling and input validation. Consider:

  1. Validating required fields before API call
  2. Adding try-catch block for API errors
  3. Verifying response structure
   async run({ $ }) {
+    if (!this.activityId) {
+      throw new Error("Activity ID is required");
+    }
+
+    try {
       const response = await this.ortto.createCustomActivity({
         $,
         data: {
           "activities": [
             {
               activity_id: this.activityId,
               attributes: parseObject(this.attributes),
               fields: parseObject(this.fields),
             },
           ],
           "merge_by": [
             "str::email",
           ],
         },
       });
+      
+      if (!response?.success) {
+        throw new Error("Failed to create activity: " + response?.error || "Unknown error");
+      }
+
+      $.export("$summary", "Successfully created activity");
+      return response;
+    } catch (error) {
+      throw new Error(`Error creating custom activity: ${error.message}`);
+    }
-
-    $.export("$summary", "Successfully created activity");
-    return response;
   },
📝 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 response = await this.ortto.createCustomActivity({
$,
data: {
"activities": [
{
activity_id: this.activityId,
attributes: parseObject(this.attributes),
fields: parseObject(this.fields),
},
],
"merge_by": [
"str::email",
],
},
});
async run({ $ }) {
if (!this.activityId) {
throw new Error("Activity ID is required");
}
try {
const response = await this.ortto.createCustomActivity({
$,
data: {
"activities": [
{
activity_id: this.activityId,
attributes: parseObject(this.attributes),
fields: parseObject(this.fields),
},
],
"merge_by": [
"str::email",
],
},
});
if (!response?.success) {
throw new Error("Failed to create activity: " + response?.error || "Unknown error");
}
$.export("$summary", "Successfully created activity");
return response;
} catch (error) {
throw new Error(`Error creating custom activity: ${error.message}`);
}
},

Comment on lines +22 to +40
async emitEvent(maxResults = false) {
const lastDate = this._getLastDate();

const response = this.ortto.paginate({
fn: this.getFunction(),
fieldName: this.getFieldName(),
data: {
"fields": this.getFields(),
"sort_by_field_id": "c",
"sort_order": "desc",
},
});

let responseArray = [];
for await (const item of response) {
if (Date.parse(item.fields.c) <= lastDate) break;
responseArray.push(item);
}

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

Optimize pagination and memory usage

The current implementation loads all items into memory before filtering. Consider:

  1. Adding a limit to prevent memory issues
  2. Using the API's filtering capabilities if available
  3. Adding error handling for the pagination
   async emitEvent(maxResults = false) {
     const lastDate = this._getLastDate();
+    const SAFE_BATCH_SIZE = 1000;
+    let count = 0;
 
+    try {
       const response = this.ortto.paginate({
         fn: this.getFunction(),
         fieldName: this.getFieldName(),
         data: {
           "fields": this.getFields(),
           "sort_by_field_id": "c",
           "sort_order": "desc",
+          "limit": SAFE_BATCH_SIZE,
         },
       });
 
       let responseArray = [];
       for await (const item of response) {
+        count++;
         if (Date.parse(item.fields.c) <= lastDate) break;
         responseArray.push(item);
+        if (maxResults && responseArray.length >= maxResults) break;
+        if (count >= SAFE_BATCH_SIZE) break;
       }
+    } catch (error) {
+      console.error("Error during pagination:", error);
+      throw new Error(`Failed to fetch items: ${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
async emitEvent(maxResults = false) {
const lastDate = this._getLastDate();
const response = this.ortto.paginate({
fn: this.getFunction(),
fieldName: this.getFieldName(),
data: {
"fields": this.getFields(),
"sort_by_field_id": "c",
"sort_order": "desc",
},
});
let responseArray = [];
for await (const item of response) {
if (Date.parse(item.fields.c) <= lastDate) break;
responseArray.push(item);
}
async emitEvent(maxResults = false) {
const lastDate = this._getLastDate();
const SAFE_BATCH_SIZE = 1000;
let count = 0;
try {
const response = this.ortto.paginate({
fn: this.getFunction(),
fieldName: this.getFieldName(),
data: {
"fields": this.getFields(),
"sort_by_field_id": "c",
"sort_order": "desc",
"limit": SAFE_BATCH_SIZE,
},
});
let responseArray = [];
for await (const item of response) {
count++;
if (Date.parse(item.fields.c) <= lastDate) break;
responseArray.push(item);
if (maxResults && responseArray.length >= maxResults) break;
if (count >= SAFE_BATCH_SIZE) break;
}
} catch (error) {
console.error("Error during pagination:", error);
throw new Error(`Failed to fetch items: ${error.message}`);
}

Comment on lines +48 to +54
for (const item of responseArray.reverse()) {
this.$emit(item, {
id: item.id,
summary: this.getSummary(item),
ts: Date.parse(item.fields.c || new Date()),
});
}
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 event emission

The event emission process lacks error handling and could fail silently:

-      for (const item of responseArray.reverse()) {
-        this.$emit(item, {
-          id: item.id,
-          summary: this.getSummary(item),
-          ts: Date.parse(item.fields.c || new Date()),
-        });
-      }
+      try {
+        for (const item of responseArray.reverse()) {
+          const timestamp = Date.parse(item.fields.c || new Date());
+          if (isNaN(timestamp)) {
+            console.warn(`Invalid timestamp for item ${item.id}`);
+            continue;
+          }
+          await this.$emit(item, {
+            id: item.id,
+            summary: this.getSummary(item),
+            ts: timestamp,
+          });
+        }
+      } catch (error) {
+        console.error("Error emitting events:", error);
+        throw new Error(`Failed to emit events: ${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
for (const item of responseArray.reverse()) {
this.$emit(item, {
id: item.id,
summary: this.getSummary(item),
ts: Date.parse(item.fields.c || new Date()),
});
}
try {
for (const item of responseArray.reverse()) {
const timestamp = Date.parse(item.fields.c || new Date());
if (isNaN(timestamp)) {
console.warn(`Invalid timestamp for item ${item.id}`);
continue;
}
await this.$emit(item, {
id: item.id,
summary: this.getSummary(item),
ts: timestamp,
});
}
} catch (error) {
console.error("Error emitting events:", error);
throw new Error(`Failed to emit events: ${error.message}`);
}

Comment on lines +24 to +28
phone: {
type: "string",
label: "Phone",
description: "The person's phone number.",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for phone and birthday fields

  1. The phone field should include format validation to ensure valid phone numbers.
  2. The birthday field is accepted as string but used as Date - consider:
    • Adding format validation (e.g., ISO 8601)
    • Using a more specific type or adding format description

Consider adding these validations:

 phone: {
   type: "string",
   label: "Phone",
   description: "The person's phone number.",
+  pattern: "^\\+?[1-9]\\d{1,14}$",
+  description: "The person's phone number in E.164 format (e.g., +1234567890).",
 },
 ...
 birthday: {
   type: "string",
   label: "Birthday",
-  description: "The person's birth date.",
+  description: "The person's birth date in ISO 8601 format (YYYY-MM-DD).",
+  pattern: "^\\d{4}-\\d{2}-\\d{2}$",
   optional: true,
 },

Also applies to: 52-57

Comment on lines +65 to +71
const birthday = {};
if (props.birthday) {
const date = new Date(props.birthday);
birthday.day = date.getDate();
birthday.month = date.getMonth() + 1;
birthday.year = date.getFullYear();
}
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 invalid date parsing

The current implementation assumes the date string will always be valid. Add error handling to prevent runtime errors:

 const birthday = {};
 if (props.birthday) {
+  try {
     const date = new Date(props.birthday);
+    if (isNaN(date.getTime())) {
+      throw new Error("Invalid date format");
+    }
     birthday.day = date.getDate();
     birthday.month = date.getMonth() + 1;
     birthday.year = date.getFullYear();
+  } catch (error) {
+    throw new Error(`Invalid birthday format: ${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
const birthday = {};
if (props.birthday) {
const date = new Date(props.birthday);
birthday.day = date.getDate();
birthday.month = date.getMonth() + 1;
birthday.year = date.getFullYear();
}
const birthday = {};
if (props.birthday) {
try {
const date = new Date(props.birthday);
if (isNaN(date.getTime())) {
throw new Error("Invalid date format");
}
birthday.day = date.getDate();
birthday.month = date.getMonth() + 1;
birthday.year = date.getFullYear();
} catch (error) {
throw new Error(`Invalid birthday format: ${error.message}`);
}
}

Comment on lines +73 to +107
const response = await ortto.createPerson({
data: {
people: [
{
fields: clearObj({
"str::first": props.firstName,
"str::last": props.lastName,
"phn::phone": {
"phone": props.phone,
"parse_with_country_code": true,
},
"str::email": props.email,
"geo::city": {
name: props.city,
},
"geo::country": {
name: props.country,
},
"geo::region": {
name: props.region,
},
"dtz::b": birthday,
}),
},
],
async: false,
merge_by: [
"str::email",
],
find_strategy: 0,
},
});
$.export("$summary", "Person successfully initialized or updated!");
return response;
},
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 API response

The current implementation doesn't handle potential API errors. Consider adding try-catch and response validation:

-    const response = await ortto.createPerson({
-      data: {
-        people: [
-          {
-            fields: clearObj({
+    try {
+      const response = await ortto.createPerson({
+        data: {
+          people: [
+            {
+              fields: clearObj({
...
-      },
-    });
-    $.export("$summary", "Person successfully initialized or updated!");
-    return response;
+        },
+      });
+      
+      if (!response || response.error) {
+        throw new Error(response?.error || 'Failed to create/update person');
+      }
+      
+      $.export("$summary", "Person successfully initialized or updated!");
+      return response;
+    } catch (error) {
+      throw new Error(`Failed to create/update person: ${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
const response = await ortto.createPerson({
data: {
people: [
{
fields: clearObj({
"str::first": props.firstName,
"str::last": props.lastName,
"phn::phone": {
"phone": props.phone,
"parse_with_country_code": true,
},
"str::email": props.email,
"geo::city": {
name: props.city,
},
"geo::country": {
name: props.country,
},
"geo::region": {
name: props.region,
},
"dtz::b": birthday,
}),
},
],
async: false,
merge_by: [
"str::email",
],
find_strategy: 0,
},
});
$.export("$summary", "Person successfully initialized or updated!");
return response;
},
try {
const response = await ortto.createPerson({
data: {
people: [
{
fields: clearObj({
"str::first": props.firstName,
"str::last": props.lastName,
"phn::phone": {
"phone": props.phone,
"parse_with_country_code": true,
},
"str::email": props.email,
"geo::city": {
name: props.city,
},
"geo::country": {
name: props.country,
},
"geo::region": {
name: props.region,
},
"dtz::b": birthday,
}),
},
],
async: false,
merge_by: [
"str::email",
],
find_strategy: 0,
},
});
if (!response || response.error) {
throw new Error(response?.error || 'Failed to create/update person');
}
$.export("$summary", "Person successfully initialized or updated!");
return response;
} catch (error) {
throw new Error(`Failed to create/update person: ${error.message}`);
}

Comment on lines +57 to +66
_makeRequest({
$ = this, path, ...opts
}) {
return axios($, {
method: "POST",
url: this._baseUrl() + path,
headers: this._headers(),
...opts,
});
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add request timeout and retry logic

The _makeRequest method should include timeout and retry logic for better reliability:

 _makeRequest({
-  $ = this, path, ...opts
+  $ = this,
+  path,
+  timeout = 30000,
+  retries = 3,
+  ...opts
 }) {
-  return axios($, {
-    method: "POST",
-    url: this._baseUrl() + path,
-    headers: this._headers(),
-    ...opts,
-  });
+  const makeAttempt = async (attempt) => {
+    try {
+      return await axios($, {
+        method: "POST",
+        url: this._baseUrl() + path,
+        headers: this._headers(),
+        timeout,
+        ...opts,
+      });
+    } catch (error) {
+      if (attempt < retries && error.response?.status >= 500) {
+        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
+        return makeAttempt(attempt + 1);
+      }
+      throw error;
+    }
+  };
+  return makeAttempt(1);
 },
📝 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
_makeRequest({
$ = this, path, ...opts
}) {
return axios($, {
method: "POST",
url: this._baseUrl() + path,
headers: this._headers(),
...opts,
});
},
_makeRequest({
$ = this,
path,
timeout = 30000,
retries = 3,
...opts
}) {
const makeAttempt = async (attempt) => {
try {
return await axios($, {
method: "POST",
url: this._baseUrl() + path,
headers: this._headers(),
timeout,
...opts,
});
} catch (error) {
if (attempt < retries && error.response?.status >= 500) {
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
return makeAttempt(attempt + 1);
}
throw error;
}
};
return makeAttempt(1);
},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ai-assisted Content generated by AI, with human refinement and modification
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Components] ortto
2 participants