Skip to content

Implement comprehensive Trustpilot webhooks and review management #17613

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

seynadio
Copy link

@seynadio seynadio commented Jul 14, 2025

Summary

  • Enhanced trustpilot.app.ts with full authentication and API methods supporting both API key and OAuth
  • Added comprehensive constants and utilities for proper error handling and validation
  • Implemented webhook sources: review.created, review.revised, review.deleted, reply.created, invitation.sent, invitation.failed
  • Implemented actions: fetch service reviews, fetch service review by ID, fetch product reviews, fetch product review by ID, reply to service review, reply to product review
  • Added proper filtering, pagination, business unit selection, and retry logic
  • Complete webhook infrastructure with base class

Summary by CodeRabbit

  • New Features

    • Introduced Trustpilot integration, enabling users to fetch and reply to product and service reviews, manage conversations, and receive events for new or updated reviews and replies.
    • Added polling-based event sources for new and updated product/service reviews, review replies, and conversations.
    • Enhanced filtering, sorting, and pagination options for review and conversation retrieval.
  • Chores

    • Updated dependencies and configuration for improved reliability and compatibility.

Copy link

vercel bot commented Jul 14, 2025

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

1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
pipedream-docs-redirect-do-not-edit ⬜️ Ignored (Inspect) Jul 14, 2025 3:05pm

Copy link

vercel bot commented Jul 14, 2025

@seynadio is attempting to deploy a commit to the Pipedreamers Team on Vercel.

A member of the Team first needs to authorize it.

@adolfo-pd adolfo-pd added the User submitted Submitted by a user label Jul 14, 2025
Copy link
Contributor

coderabbitai bot commented Jul 14, 2025

Walkthrough

This update introduces a comprehensive Trustpilot integration, including a fully implemented app interface, reusable constants and utilities, and a suite of new actions and polling-based sources for reviews, replies, and conversations. The integration supports fetching, replying, and monitoring both service and product reviews, with robust error handling, pagination, and deduplication logic.

Changes

File(s) Change Summary
components/trustpilot/app/trustpilot.app.ts Full implementation of Trustpilot app: prop definitions, authentication, API methods for reviews, replies, conversations, webhooks, etc.
components/trustpilot/common/constants.mjs
components/trustpilot/common/utils.mjs
New modules: API constants, endpoint templates, enums, utility functions for URL building, parsing, validation, error handling, sleep.
components/trustpilot/package.json Added dependency on @pipedream/platform, fixed JSON structure.
components/trustpilot/.gitignore Stopped ignoring .mjs files; now ignores only .js and dist/.
components/trustpilot/actions/fetch-product-review-by-id/...
components/trustpilot/actions/fetch-product-reviews/...
components/trustpilot/actions/fetch-service-review-by-id/...
components/trustpilot/actions/fetch-service-reviews/...
New action modules: fetch product/service reviews by ID or in bulk, with support for filters and error handling.
components/trustpilot/actions/reply-to-product-review/...
components/trustpilot/actions/reply-to-service-review/...
New action modules: reply to product or service reviews with message validation and error handling.
components/trustpilot/sources/common/polling.mjs New abstract polling module: stateful, deduplicated polling for new/updated items, extensible for sources.
components/trustpilot/sources/new-conversations/...
components/trustpilot/sources/updated-conversations/...
New sources: emit events for new and updated conversations, with summary generation.
components/trustpilot/sources/new-product-reviews/...
components/trustpilot/sources/updated-product-reviews/...
New sources: emit events for new and updated product reviews, with polling and summaries.
components/trustpilot/sources/new-service-reviews/...
components/trustpilot/sources/updated-service-reviews/...
New sources: emit events for new and updated service reviews, polling both public and private endpoints.
components/trustpilot/sources/new-product-review-replies/...
components/trustpilot/sources/new-service-review-replies/...
New sources: emit events for new replies to product and service reviews, with pseudo-reply object creation and summaries.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Action/Source
    participant TrustpilotApp
    participant TrustpilotAPI

    User->>Action/Source: Trigger (manual or scheduled)
    Action/Source->>TrustpilotApp: Call API method (e.g., getServiceReviews)
    TrustpilotApp->>TrustpilotAPI: HTTP request (with auth, params)
    TrustpilotAPI-->>TrustpilotApp: API response (reviews, replies, etc.)
    TrustpilotApp-->>Action/Source: Parsed data or error
    alt Action
        Action-->>User: Return data/result or error
    else Source
        Action/Source->>Action/Source: Deduplicate/filter new/updated items
        Action/Source-->>User: Emit event with summary and metadata
    end
Loading

Suggested labels

ai-assisted

Poem

🐇
A hop and a skip through Trustpilot’s land,
Reviews and replies now close at hand!
Polling for updates, new ratings to see,
Conversations and products, as lively as can be.
With actions and sources, the code’s feeling bright—
This rabbit’s delighted: integration done right!

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs

Oops! Something went wrong! :(

ESLint: 8.57.1

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs
at Object.getPackageJSONURL (node:internal/modules/package_json_reader:255:9)
at packageResolve (node:internal/modules/esm/resolve:767:81)
at moduleResolve (node:internal/modules/esm/resolve:853:18)
at defaultResolve (node:internal/modules/esm/resolve:983:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:801:12)
at #cachedDefaultResolve (node:internal/modules/esm/loader:725:25)
at ModuleLoader.resolve (node:internal/modules/esm/loader:708:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:309:38)
at #link (node:internal/modules/esm/module_job:202:49)

components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs

Oops! Something went wrong! :(

ESLint: 8.57.1

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs
at Object.getPackageJSONURL (node:internal/modules/package_json_reader:255:9)
at packageResolve (node:internal/modules/esm/resolve:767:81)
at moduleResolve (node:internal/modules/esm/resolve:853:18)
at defaultResolve (node:internal/modules/esm/resolve:983:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:801:12)
at #cachedDefaultResolve (node:internal/modules/esm/loader:725:25)
at ModuleLoader.resolve (node:internal/modules/esm/loader:708:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:309:38)
at #link (node:internal/modules/esm/module_job:202:49)

components/trustpilot/app/trustpilot.app.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonc-eslint-parser' imported from /eslint.config.mjs
at Object.getPackageJSONURL (node:internal/modules/package_json_reader:255:9)
at packageResolve (node:internal/modules/esm/resolve:767:81)
at moduleResolve (node:internal/modules/esm/resolve:853:18)
at defaultResolve (node:internal/modules/esm/resolve:983:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:801:12)
at #cachedDefaultResolve (node:internal/modules/esm/loader:725:25)
at ModuleLoader.resolve (node:internal/modules/esm/loader:708:38)
at ModuleLoader.getModuleJobForImport (node:internal/modules/esm/loader:309:38)
at #link (node:internal/modules/esm/module_job:202:49)

  • 15 others
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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.

@pipedream-component-development
Copy link
Collaborator

Thank you so much for submitting this! We've added it to our backlog to review, and our team has been notified.

@pipedream-component-development
Copy link
Collaborator

Thanks for submitting this PR! When we review PRs, we follow the Pipedream component guidelines. If you're not familiar, here's a quick checklist:

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: 7

🧹 Nitpick comments (3)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (1)

30-48: Consider enhancing error context.

The error handling is functional but could provide more context about the specific failure mode.

    } catch (error) {
-      throw new Error(`Failed to fetch service review: ${error.message}`);
+      throw new Error(`Failed to fetch service review ${reviewId} for business unit ${businessUnitId}: ${error.message}`);
    }
components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (1)

49-62: Consider adding message length validation.

While the empty message validation is good, consider adding a maximum length validation to prevent API errors from overly long messages.

    if (!message || message.trim().length === 0) {
      throw new Error("Reply message cannot be empty");
    }
+
+    if (message.trim().length > 1000) { // Adjust limit based on Trustpilot API
+      throw new Error("Reply message exceeds maximum length");
+    }
components/trustpilot/app/trustpilot.app.ts (1)

65-68: Consider improving the label formatting for better readability.

The current label generation produces labels like "created at desc". Consider enhancing the formatting for better user experience.

-        label: key.replace(/_/g, " ").toLowerCase(),
+        label: key.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, l => l.toUpperCase()).replace(/\s(asc|desc)$/i, (match, dir) => ` (${dir.toUpperCase()})`),

This would produce labels like "Created At (DESC)" which is more readable.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e21d334 and 2b663f1.

📒 Files selected for processing (18)
  • components/trustpilot/.gitignore (0 hunks)
  • components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (1 hunks)
  • components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1 hunks)
  • components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (1 hunks)
  • components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1 hunks)
  • components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (1 hunks)
  • components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (1 hunks)
  • components/trustpilot/app/trustpilot.app.ts (1 hunks)
  • components/trustpilot/common/constants.mjs (1 hunks)
  • components/trustpilot/common/utils.mjs (1 hunks)
  • components/trustpilot/package.json (1 hunks)
  • components/trustpilot/sources/common/webhook.mjs (1 hunks)
  • components/trustpilot/sources/invitation-failed/invitation-failed.mjs (1 hunks)
  • components/trustpilot/sources/invitation-sent/invitation-sent.mjs (1 hunks)
  • components/trustpilot/sources/reply-created/reply-created.mjs (1 hunks)
  • components/trustpilot/sources/review-created/review-created.mjs (1 hunks)
  • components/trustpilot/sources/review-deleted/review-deleted.mjs (1 hunks)
  • components/trustpilot/sources/review-revised/review-revised.mjs (1 hunks)
💤 Files with no reviewable changes (1)
  • components/trustpilot/.gitignore
🧰 Additional context used
🧠 Learnings (13)
components/trustpilot/package.json (1)
Learnt from: jcortes
PR: PipedreamHQ/pipedream#14935
File: components/sailpoint/package.json:15-18
Timestamp: 2024-12-12T19:23:09.039Z
Learning: When developing Pipedream components, do not add built-in Node.js modules like `fs` to `package.json` dependencies, as they are native modules provided by the Node.js runtime.
components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (2)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12731
File: components/hackerone/actions/get-members/get-members.mjs:3-28
Timestamp: 2024-07-04T18:11:59.822Z
Learning: When exporting a summary message in the `run` method of an action, ensure the message is correctly formatted. For example, in the `hackerone-get-members` action, the correct format is `Successfully retrieved ${response.data.length} members`.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12731
File: components/hackerone/actions/get-members/get-members.mjs:3-28
Timestamp: 2024-10-08T15:33:38.240Z
Learning: When exporting a summary message in the `run` method of an action, ensure the message is correctly formatted. For example, in the `hackerone-get-members` action, the correct format is `Successfully retrieved ${response.data.length} members`.
components/trustpilot/sources/review-deleted/review-deleted.mjs (3)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14265
File: components/the_magic_drip/sources/common.mjs:35-43
Timestamp: 2024-10-10T19:18:27.998Z
Learning: In `components/the_magic_drip/sources/common.mjs`, when processing items in `getAndProcessData`, `savedIds` is intentionally updated with IDs of both emitted and non-emitted items to avoid emitting retroactive events upon first deployment and ensure only new events are emitted as they occur.
components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (2)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12731
File: components/hackerone/actions/get-members/get-members.mjs:3-28
Timestamp: 2024-07-04T18:11:59.822Z
Learning: When exporting a summary message in the `run` method of an action, ensure the message is correctly formatted. For example, in the `hackerone-get-members` action, the correct format is `Successfully retrieved ${response.data.length} members`.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12731
File: components/hackerone/actions/get-members/get-members.mjs:3-28
Timestamp: 2024-10-08T15:33:38.240Z
Learning: When exporting a summary message in the `run` method of an action, ensure the message is correctly formatted. For example, in the `hackerone-get-members` action, the correct format is `Successfully retrieved ${response.data.length} members`.
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (2)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12731
File: components/hackerone/actions/get-members/get-members.mjs:3-28
Timestamp: 2024-10-08T15:33:38.240Z
Learning: When exporting a summary message in the `run` method of an action, ensure the message is correctly formatted. For example, in the `hackerone-get-members` action, the correct format is `Successfully retrieved ${response.data.length} members`.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12731
File: components/hackerone/actions/get-members/get-members.mjs:3-28
Timestamp: 2024-07-04T18:11:59.822Z
Learning: When exporting a summary message in the `run` method of an action, ensure the message is correctly formatted. For example, in the `hackerone-get-members` action, the correct format is `Successfully retrieved ${response.data.length} members`.
components/trustpilot/sources/review-created/review-created.mjs (3)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#15376
File: components/monday/sources/name-updated/name-updated.mjs:6-6
Timestamp: 2025-01-23T03:55:15.166Z
Learning: Source names in Monday.com components don't need to start with "New" if they emit events for updated items (e.g., "Name Updated", "Column Value Updated") rather than new items. This follows the component guidelines exception where the "New" prefix is only required when emits are limited to new items.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
components/trustpilot/sources/common/webhook.mjs (4)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14265
File: components/the_magic_drip/sources/common.mjs:35-43
Timestamp: 2024-10-10T19:18:27.998Z
Learning: In `components/the_magic_drip/sources/common.mjs`, when processing items in `getAndProcessData`, `savedIds` is intentionally updated with IDs of both emitted and non-emitted items to avoid emitting retroactive events upon first deployment and ensure only new events are emitted as they occur.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common.mjs:97-98
Timestamp: 2024-07-24T02:05:59.531Z
Learning: The `processTimerEvent` method in the `components/salesforce_rest_api/sources/common.mjs` file is intentionally left unimplemented to enforce that subclasses must implement this method, similar to an abstract class in object-oriented programming.
components/trustpilot/sources/invitation-failed/invitation-failed.mjs (3)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14265
File: components/the_magic_drip/sources/common.mjs:35-43
Timestamp: 2024-10-10T19:18:27.998Z
Learning: In `components/the_magic_drip/sources/common.mjs`, when processing items in `getAndProcessData`, `savedIds` is intentionally updated with IDs of both emitted and non-emitted items to avoid emitting retroactive events upon first deployment and ensure only new events are emitted as they occur.
components/trustpilot/sources/review-revised/review-revised.mjs (4)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14265
File: components/the_magic_drip/sources/common.mjs:35-43
Timestamp: 2024-10-10T19:18:27.998Z
Learning: In `components/the_magic_drip/sources/common.mjs`, when processing items in `getAndProcessData`, `savedIds` is intentionally updated with IDs of both emitted and non-emitted items to avoid emitting retroactive events upon first deployment and ensure only new events are emitted as they occur.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#15376
File: components/monday/sources/name-updated/name-updated.mjs:6-6
Timestamp: 2025-01-23T03:55:15.166Z
Learning: Source names in Monday.com components don't need to start with "New" if they emit events for updated items (e.g., "Name Updated", "Column Value Updated") rather than new items. This follows the component guidelines exception where the "New" prefix is only required when emits are limited to new items.
components/trustpilot/sources/reply-created/reply-created.mjs (2)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
components/trustpilot/sources/invitation-sent/invitation-sent.mjs (3)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14265
File: components/the_magic_drip/sources/common.mjs:35-43
Timestamp: 2024-10-10T19:18:27.998Z
Learning: In `components/the_magic_drip/sources/common.mjs`, when processing items in `getAndProcessData`, `savedIds` is intentionally updated with IDs of both emitted and non-emitted items to avoid emitting retroactive events upon first deployment and ensure only new events are emitted as they occur.
components/trustpilot/common/constants.mjs (2)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
components/trustpilot/app/trustpilot.app.ts (3)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#16954
File: components/salesloft/salesloft.app.mjs:14-23
Timestamp: 2025-06-04T17:52:05.780Z
Learning: In the Salesloft API integration (components/salesloft/salesloft.app.mjs), the _makeRequest method returns response.data which directly contains arrays for list endpoints like listPeople, listCadences, listUsers, and listAccounts. The propDefinitions correctly call .map() directly on these responses without needing to destructure a nested data property.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
🔇 Additional comments (30)
components/trustpilot/package.json (1)

15-15: LGTM! JSON structure fix is correct.

The missing closing brace for the publishConfig object has been properly added.

components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (1)

1-23: LGTM! Action structure follows Pipedream conventions.

The import, property definitions, and overall structure are well-implemented and consistent with Pipedream action patterns.

components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (2)

36-38: Excellent input validation.

The validation for empty or whitespace-only messages is a good practice that prevents API errors and improves user experience.


40-48: Good implementation with proper message trimming.

The message trimming before sending the API request and the summary formatting are well-implemented.

components/trustpilot/sources/review-deleted/review-deleted.mjs (2)

1-16: LGTM! Webhook source follows proper patterns.

The extension of the common webhook base and event specification are correctly implemented.


17-22: Excellent defensive programming with fallback handling.

The fallback to "Anonymous" for missing consumer display names is a good practice that prevents summary generation errors.

components/trustpilot/sources/reply-created/reply-created.mjs (2)

1-16: LGTM! Consistent webhook source implementation.

The extension of the common webhook base and event specification follow the established pattern correctly.


17-24: Good text truncation logic for preview.

The 50-character truncation with ellipsis provides a good balance between preview content and summary length, improving readability in event streams.

components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (4)

29-31: Good validation logic for empty messages.

The validation correctly checks for both null/undefined and whitespace-only messages, preventing empty replies from being sent.


39-39: Summary format follows best practices.

The summary message format correctly follows the learned pattern: "Successfully [action] [details]" which provides clear feedback to users.


41-49: Comprehensive return object with useful metadata.

The returned object includes success flag, API response, and metadata with review ID, message length, and timestamp - all valuable for debugging and workflow integration.


50-52: Proper error handling with descriptive messages.

The error handling correctly wraps the original error message in a more descriptive context, making it easier to diagnose issues.

components/trustpilot/sources/review-revised/review-revised.mjs (2)

4-5: Proper extension of common webhook base.

The spread operator correctly extends the common webhook functionality, following the established pattern for webhook sources.


17-23: Good summary generation with fallback values.

The summary generation correctly extracts review data and provides appropriate fallback values ("N/A" for stars, "Anonymous" for consumer name) when data is missing, ensuring robust handling of incomplete webhook payloads.

components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (1)

18-38: Well-structured fetch action with proper error handling.

The implementation correctly follows the standard pattern for fetch actions: extract parameters, call API method, export summary, and return data with metadata. The error handling provides descriptive messages and the summary format follows best practices.

components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (3)

53-60: Good validation constraints on custom offset prop.

The offset prop correctly includes validation constraints (min: 0) and a sensible default value (0), preventing invalid pagination parameters.


86-88: Proper response destructuring and summary format.

The response is correctly destructured into reviews and pagination, and the summary message follows the learned pattern by including the count of fetched reviews.


90-104: Comprehensive return object with filters metadata.

The returned object includes reviews, pagination, and detailed metadata with applied filters, providing excellent transparency for debugging and workflow integration.

components/trustpilot/sources/review-created/review-created.mjs (2)

4-8: Proper webhook source structure and naming.

The source correctly extends the common webhook base and uses an appropriate name "Review Created" for events about new items, following component guidelines.


17-23: Descriptive summary generation with robust fallback handling.

The summary generation creates a clear, informative message that includes star rating, consumer name, and business unit ID, with appropriate fallback values for missing data.

components/trustpilot/sources/invitation-sent/invitation-sent.mjs (3)

1-11: LGTM!

The module setup and metadata are properly configured for the invitation sent webhook source.


14-16: LGTM!

Correctly specifies the webhook event type this source handles.


17-23: LGTM!

Good use of optional chaining and fallback values for robust summary generation.

components/trustpilot/sources/invitation-failed/invitation-failed.mjs (1)

1-39: LGTM!

Well-structured webhook source with proper error message handling in the summary generation.

components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1)

62-108: LGTM!

Well-implemented action with proper error handling, structured response, and informative summary export.

components/trustpilot/common/utils.mjs (1)

25-51: LGTM!

The parsing functions are well-structured with good use of optional chaining and sensible defaults.

Also applies to: 58-74, 81-93

components/trustpilot/common/constants.mjs (1)

1-78: LGTM! Well-organized constants module.

This constants file provides a clean, centralized location for all Trustpilot integration constants. The structure is logical with proper categorization of endpoints, events, enums, and configuration values.

components/trustpilot/app/trustpilot.app.ts (3)

103-157: Excellent implementation of authentication and retry logic.

The authentication supports both API key and OAuth methods, and the retry mechanism with exponential backoff for rate-limited requests is well-implemented. The error handling provides clear, actionable error messages.


160-259: Well-structured API methods with comprehensive validation.

The business unit and service review methods are well-implemented with:

  • Proper input validation using utility functions
  • Comprehensive parameter support with sensible defaults
  • Correct pagination handling
  • Consistent error messages

262-392: Excellent comprehensive Trustpilot integration implementation.

The product review methods mirror the service review methods appropriately, maintaining consistency. The overall implementation demonstrates:

  • Consistent patterns across all method types
  • Proper error handling and validation throughout
  • Good use of shared utilities and constants
  • Clean, maintainable code structure

This is production-ready code (pending the webhook signature validation implementation).

Comment on lines +16 to 18
"dependencies": {
"@pipedream/platform": "^1.0.0"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the @pipedream/platform dependency version.

The ^1.0.0 version constraint for @pipedream/platform should be verified to ensure it's the correct and latest stable version for the Trustpilot integration features.


🌐 Web query:

What is the latest stable version of @pipedream/platform npm package?

💡 Result:

The latest stable version of the @pipedream/platform npm package is 3.1.0, which was published approximately one month ago as of June 2025[1][7].

Citations:


Update @pipedream/platform to latest stable (3.1.0)

The current ^1.0.0 constraint is outdated—@pipedream/platform v3.1.0 was released June 2025. Please bump the dependency to ensure you’re using the latest stable API.

Affected file:

  • components/trustpilot/package.json (lines 16–18)

Suggested change:

   "dependencies": {
-    "@pipedream/platform": "^1.0.0"
+    "@pipedream/platform": "^3.1.0"
   }
📝 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
"dependencies": {
"@pipedream/platform": "^1.0.0"
}
"dependencies": {
"@pipedream/platform": "^3.1.0"
}
🤖 Prompt for AI Agents
In components/trustpilot/package.json around lines 16 to 18, the dependency
version for @pipedream/platform is outdated at ^1.0.0. Update the version
constraint to "^3.1.0" to use the latest stable release. This involves changing
the version string in the dependencies object to reflect the new version.

this._setWebhookId(webhook.id);

// Generate and store a secret for signature validation
const secret = `tp_${Math.random().toString(36).substring(2, 15)}`;
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

⚠️ Potential issue

Use cryptographically secure random generation for the secret.

Math.random() is not cryptographically secure and should not be used for generating secrets.

Use Node.js crypto module for secure random generation:

-const secret = `tp_${Math.random().toString(36).substring(2, 15)}`;
+const crypto = await import('crypto');
+const secret = `tp_${crypto.randomBytes(16).toString('hex')}`;

Add the import at the top of the file:

import { randomBytes } from "crypto";
🤖 Prompt for AI Agents
In components/trustpilot/sources/common/webhook.mjs at line 91, replace the use
of Math.random() for generating the secret with a cryptographically secure
method. Import randomBytes from the Node.js crypto module at the top of the
file, then generate the secret using randomBytes to create a secure random
string instead of Math.random().

Comment on lines 63 to 66
validateSignature(body, signature) {
// TODO: Implement signature validation when Trustpilot provides it
return true;
},
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

Implement proper signature validation.

The TODO indicates this critical security feature is not implemented. Webhook signature validation is essential to ensure requests are authentic.

Would you like me to help implement the signature validation logic or open an issue to track this security requirement?

🤖 Prompt for AI Agents
In components/trustpilot/sources/common/webhook.mjs at lines 63 to 66, the
validateSignature method currently returns true without performing any actual
validation, which is a security risk. Implement proper signature validation by
using the signature provided by Trustpilot to verify the authenticity of the
webhook payload, typically by computing a hash or HMAC of the body with a shared
secret and comparing it to the signature. If the exact validation method is
unknown, open an issue to track this security requirement and ensure it is
addressed once Trustpilot provides the necessary details.

Comment on lines +9 to +18
export function buildUrl(endpoint, params = {}) {
let url = endpoint;

// Replace path parameters
Object.entries(params).forEach(([key, value]) => {
url = url.replace(`{${key}}`, value);
});

return url;
}
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

Ensure URL parameter replacement handles edge cases.

The current implementation might fail if parameter values contain regex special characters or the {key} pattern.

Consider using a more robust replacement approach:

-export function buildUrl(endpoint, params = {}) {
-  let url = endpoint;
-  
-  // Replace path parameters
-  Object.entries(params).forEach(([key, value]) => {
-    url = url.replace(`{${key}}`, value);
-  });
-  
-  return url;
-}
+export function buildUrl(endpoint, params = {}) {
+  let url = endpoint;
+  
+  // Replace path parameters
+  Object.entries(params).forEach(([key, value]) => {
+    const placeholder = `{${key}}`;
+    // Use split/join to avoid regex issues
+    url = url.split(placeholder).join(encodeURIComponent(String(value)));
+  });
+  
+  return url;
+}
📝 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 function buildUrl(endpoint, params = {}) {
let url = endpoint;
// Replace path parameters
Object.entries(params).forEach(([key, value]) => {
url = url.replace(`{${key}}`, value);
});
return url;
}
export function buildUrl(endpoint, params = {}) {
let url = endpoint;
// Replace path parameters
Object.entries(params).forEach(([key, value]) => {
const placeholder = `{${key}}`;
// Use split/join to avoid regex issues and ensure all occurrences are replaced
url = url.split(placeholder).join(encodeURIComponent(String(value)));
});
return url;
}
🤖 Prompt for AI Agents
In components/trustpilot/common/utils.mjs around lines 9 to 18, the buildUrl
function replaces URL parameters using string replace, which can fail if
parameter values contain regex special characters or if the {key} pattern
appears multiple times. To fix this, update the replacement logic to safely
escape special characters in the keys and values, and use a method that replaces
all occurrences of the parameter placeholders without interpreting them as regex
patterns, ensuring robust and accurate URL construction.

Comment on lines +100 to +111
export function validateBusinessUnitId(businessUnitId) {
return businessUnitId && typeof businessUnitId === 'string' && businessUnitId.length > 0;
}

/**
* Validate review ID format
* @param {string} reviewId - Review ID to validate
* @returns {boolean} - Whether the ID is valid
*/
export function validateReviewId(reviewId) {
return reviewId && typeof reviewId === 'string' && reviewId.length > 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider more specific validation.

The current validation only checks for non-empty strings. Trustpilot IDs might have specific format requirements.

Are there specific format requirements for Trustpilot business unit and review IDs? If so, consider adding format validation:


🌐 Web query:

What is the format of Trustpilot business unit IDs and review IDs?

💡 Result:

Trustpilot business unit IDs and review IDs are both unique identifiers used in Trustpilot's APIs. Their format is as follows:

Business Unit ID (BUID):

  • The Business Unit ID is a unique identifier for each Trustpilot business profile (usually mapped to a company domain).
  • The format is a 24-character hexadecimal string (letters a–f and numbers 0–9).
  • Example: 507f191e810c19729de86aaa[2][4].
  • This format is consistent across API documentation and examples:

    "id": "507f191e810c19729de86aaa"[2].

  • Business Unit IDs are required in endpoints that fetch business-specific data, such as getting reviews or profile information[1][2][3][4][6].

Review ID:

  • The Review ID is a unique identifier for each review on the Trustpilot platform.
  • The format of a review ID is also a 24-character hexadecimal string, matching the MongoDB ObjectID format.
  • Example: 507f191e810c19729de860ea[5].
  • This format appears repeatedly in API endpoint examples:

    .../v1/reviews/507f191e810c19729de860ea[5][10].

Summary Table

Identifier Format Example Notes
Business Unit ID 24-character hex string 507f191e810c19729de86aaa Sometimes called BUID
Review ID 24-character hex string 507f191e810c19729de860ea Follows MongoDB ObjectID pattern

Both IDs use the same string structure: lowercase hexadecimal, 24 characters long. This structure allows for efficient database operations and is consistent with widely used formats for unique identifiers in many systems[2][5].

Citations:


Enforce 24-character hexadecimal format for Trustpilot IDs

Both the Business Unit ID and Review ID must be 24-character lowercase hex strings (MongoDB ObjectID format). Update the validators accordingly:

• File: components/trustpilot/common/utils.mjs (around lines 100–111)

Suggested diff:

-export function validateBusinessUnitId(businessUnitId) {
-  return businessUnitId && typeof businessUnitId === 'string' && businessUnitId.length > 0;
-}
+export function validateBusinessUnitId(businessUnitId) {
+  return (
+    typeof businessUnitId === 'string' &&
+    /^[a-f0-9]{24}$/.test(businessUnitId)
+  );
+}

-export function validateReviewId(reviewId) {
-  return reviewId && typeof reviewId === 'string' && reviewId.length > 0;
-}
+export function validateReviewId(reviewId) {
+  return (
+    typeof reviewId === 'string' &&
+    /^[a-f0-9]{24}$/.test(reviewId)
+  );
+}
📝 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 function validateBusinessUnitId(businessUnitId) {
return businessUnitId && typeof businessUnitId === 'string' && businessUnitId.length > 0;
}
/**
* Validate review ID format
* @param {string} reviewId - Review ID to validate
* @returns {boolean} - Whether the ID is valid
*/
export function validateReviewId(reviewId) {
return reviewId && typeof reviewId === 'string' && reviewId.length > 0;
}
export function validateBusinessUnitId(businessUnitId) {
return (
typeof businessUnitId === 'string' &&
/^[a-f0-9]{24}$/.test(businessUnitId)
);
}
/**
* Validate review ID format
* @param {string} reviewId - Review ID to validate
* @returns {boolean} - Whether the ID is valid
*/
export function validateReviewId(reviewId) {
return (
typeof reviewId === 'string' &&
/^[a-f0-9]{24}$/.test(reviewId)
);
}
🤖 Prompt for AI Agents
In components/trustpilot/common/utils.mjs around lines 100 to 111, the current
validation functions for Business Unit ID and Review ID only check for non-empty
strings. Update both validators to enforce that the IDs are exactly 24
characters long and contain only lowercase hexadecimal characters (0-9 and a-f)
to match the MongoDB ObjectID format. Use a regular expression to perform this
check and return true only if the ID matches this pattern.

Comment on lines +383 to +502
validateWebhookSignature(payload, signature, secret) {
// TODO: Implement webhook signature validation when Trustpilot provides it
return true;
},
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

TODO: Implement webhook signature validation for security.

The validateWebhookSignature method currently always returns true, which could pose a security risk if webhook verification is required. This should be implemented before using webhooks in production.

Would you like me to help implement the webhook signature validation or create an issue to track this security-critical task?

🤖 Prompt for AI Agents
In components/trustpilot/app/trustpilot.app.ts around lines 383 to 386, the
validateWebhookSignature method currently returns true unconditionally, which is
insecure. Implement the actual webhook signature validation logic using the
payload, signature, and secret parameters according to Trustpilot's webhook
security guidelines. If the signature matches the expected value computed from
the payload and secret, return true; otherwise, return false. This is critical
to ensure webhook authenticity before using it in production.

## Overview
Complete Trustpilot integration using polling approach (webhooks not supported by Trustpilot API).

## Features Implemented

### Authentication & Core App
- Enhanced trustpilot.app.ts with OAuth and API key authentication
- Added comprehensive API methods for reviews, products, and conversations
- Support for both public and private endpoints
- Proper error handling, validation, and retry logic with rate limiting

### Actions (6 total)
- **fetch-service-reviews** - Get service reviews with filtering and pagination
- **fetch-service-review-by-id** - Get specific service review
- **fetch-product-reviews** - Get product reviews with filtering and pagination
- **fetch-product-review-by-id** - Get specific product review
- **reply-to-service-review** - Reply to service reviews
- **reply-to-product-review** - Reply to product reviews

### Polling Sources (8 total)
- **new-service-reviews** - New service reviews (public + private endpoints)
- **updated-service-reviews** - Updated/revised service reviews
- **new-product-reviews** - New product reviews
- **updated-product-reviews** - Updated/revised product reviews
- **new-service-review-replies** - New replies to service reviews
- **new-product-review-replies** - New replies to product reviews
- **new-conversations** - New conversations started
- **updated-conversations** - Updated conversations (new messages)

### Technical Implementation
- 15-minute polling intervals following Google Drive pattern
- Smart deduplication by reviewId + timestamp
- Business unit filtering (optional)
- 24-hour lookback on first run
- Comprehensive constants and utilities
- Proper pagination and error handling

## API Endpoints Used
- `/business-units/{businessUnitId}/reviews` (public)
- `/private/business-units/{businessUnitId}/reviews` (private service)
- `/private/product-reviews/business-units/{businessUnitId}/reviews` (products)
- `/private/conversations` (conversations)
- All reply endpoints for posting responses

Addresses all requirements from https://developers.trustpilot.com/introduction/
@seynadio seynadio force-pushed the trustpilot-webhooks-and-reviews branch from e7cee0f to 47cee75 Compare July 14, 2025 15:05
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: 2

♻️ Duplicate comments (1)
components/trustpilot/app/trustpilot.app.ts (1)

499-502: TODO: Implement webhook signature validation for security.

The validateWebhookSignature method currently always returns true, which could pose a security risk if webhook verification is required. This should be implemented before using webhooks in production.

Would you like me to help implement the webhook signature validation or create an issue to track this security-critical task?

🧹 Nitpick comments (4)
components/trustpilot/app/trustpilot.app.ts (4)

34-46: Consider logging errors in options function for better debugging.

The error handling in the businessUnitId options function returns an empty array on error, which provides a good user experience but may mask underlying issues that developers need to be aware of.

Consider adding more detailed logging:

} catch (error) {
- console.error("Error fetching business units:", error);
+ console.error("Error fetching business units:", error.message || error);
+ console.error("Full error details:", JSON.stringify(error, null, 2));
  return [];
}

140-143: Enhance error message with additional context.

The error handling could provide more debugging information by including the endpoint and method in the error message.

Add more context to error messages:

} catch (error) {
  const parsedError = parseApiError(error);
- throw new Error(`Trustpilot API Error: ${parsedError.message} (${parsedError.code})`);
+ throw new Error(`Trustpilot API Error [${method} ${endpoint}]: ${parsedError.message} (${parsedError.code})`);
}

146-157: Add exponential backoff jitter to prevent thundering herd.

The retry logic uses a predictable delay pattern which could cause multiple clients to retry simultaneously. Adding jitter would distribute the load more evenly.

Add jitter to the retry delay:

if (retries > 0 && error.response?.status === HTTP_STATUS.TOO_MANY_REQUESTS) {
- const delay = Math.min(RETRY_CONFIG.INITIAL_DELAY * (RETRY_CONFIG.MAX_RETRIES - retries + 1), RETRY_CONFIG.MAX_DELAY);
+ const baseDelay = Math.min(RETRY_CONFIG.INITIAL_DELAY * (RETRY_CONFIG.MAX_RETRIES - retries + 1), RETRY_CONFIG.MAX_DELAY);
+ const jitter = Math.random() * 0.5 * baseDelay; // Add up to 50% jitter
+ const delay = baseDelay + jitter;
  await sleep(delay);
  return this._makeRequestWithRetry(config, retries - 1);
}

505-508: Consider removing or improving the debugging method.

The authKeys method appears to be for debugging purposes. Consider either removing it from production code or improving its implementation with better error handling.

Either remove the method if it's not needed, or improve it:

- authKeys() {
-   console.log("Auth keys:", Object.keys(this.$auth || {}));
-   return Object.keys(this.$auth || {});
- },
+ // For debugging authentication configuration
+ debugAuthKeys() {
+   const keys = Object.keys(this.$auth || {});
+   console.log("Available auth keys:", keys);
+   console.log("Auth configuration status:", {
+     hasApiKey: !!this.$auth?.api_key,
+     hasOAuthToken: !!this.$auth?.oauth_access_token,
+   });
+   return keys;
+ },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7cee0f and 47cee75.

📒 Files selected for processing (20)
  • components/trustpilot/.gitignore (0 hunks)
  • components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (1 hunks)
  • components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1 hunks)
  • components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (1 hunks)
  • components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1 hunks)
  • components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (1 hunks)
  • components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (1 hunks)
  • components/trustpilot/app/trustpilot.app.ts (1 hunks)
  • components/trustpilot/common/constants.mjs (1 hunks)
  • components/trustpilot/common/utils.mjs (1 hunks)
  • components/trustpilot/package.json (1 hunks)
  • components/trustpilot/sources/common/polling.mjs (1 hunks)
  • components/trustpilot/sources/new-conversations/new-conversations.mjs (1 hunks)
  • components/trustpilot/sources/new-product-review-replies/new-product-review-replies.mjs (1 hunks)
  • components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1 hunks)
  • components/trustpilot/sources/new-service-review-replies/new-service-review-replies.mjs (1 hunks)
  • components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1 hunks)
  • components/trustpilot/sources/updated-conversations/updated-conversations.mjs (1 hunks)
  • components/trustpilot/sources/updated-product-reviews/updated-product-reviews.mjs (1 hunks)
  • components/trustpilot/sources/updated-service-reviews/updated-service-reviews.mjs (1 hunks)
💤 Files with no reviewable changes (1)
  • components/trustpilot/.gitignore
✅ Files skipped from review due to trivial changes (1)
  • components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs
🚧 Files skipped from review as they are similar to previous changes (17)
  • components/trustpilot/package.json
  • components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs
  • components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs
  • components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs
  • components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs
  • components/trustpilot/sources/updated-service-reviews/updated-service-reviews.mjs
  • components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs
  • components/trustpilot/sources/updated-conversations/updated-conversations.mjs
  • components/trustpilot/sources/new-product-review-replies/new-product-review-replies.mjs
  • components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs
  • components/trustpilot/sources/new-conversations/new-conversations.mjs
  • components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs
  • components/trustpilot/common/utils.mjs
  • components/trustpilot/sources/new-service-review-replies/new-service-review-replies.mjs
  • components/trustpilot/sources/updated-product-reviews/updated-product-reviews.mjs
  • components/trustpilot/common/constants.mjs
  • components/trustpilot/sources/common/polling.mjs
🧰 Additional context used
🧠 Learnings (1)
components/trustpilot/app/trustpilot.app.ts (3)
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#16954
File: components/salesloft/salesloft.app.mjs:14-23
Timestamp: 2025-06-04T17:52:05.780Z
Learning: In the Salesloft API integration (components/salesloft/salesloft.app.mjs), the _makeRequest method returns response.data which directly contains arrays for list endpoints like listPeople, listCadences, listUsers, and listAccounts. The propDefinitions correctly call .map() directly on these responses without needing to destructure a nested data property.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-07-24T02:06:47.016Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#12697
File: components/salesforce_rest_api/sources/common-webhook-methods.mjs:1-71
Timestamp: 2024-10-08T15:33:38.240Z
Learning: The `common-webhook-methods.mjs` object is designed to be extended, similar to an abstract class, and intentionally does not implement certain methods like `generateWebhookMeta` and `getEventType` to enforce implementation in subclasses.
🔇 Additional comments (1)
components/trustpilot/app/trustpilot.app.ts (1)

2-12: Missing RATING_SCALE import from constants.

The RATING_SCALE constant is used on line 58 but is not imported from the constants file. This will cause a reference error.

Add RATING_SCALE to the imports:

import { 
  BASE_URL, 
  ENDPOINTS, 
  DEFAULT_LIMIT, 
  MAX_LIMIT, 
  SORT_OPTIONS,
+ RATING_SCALE,
  RETRY_CONFIG,
  HTTP_STATUS,
} from "../common/constants.mjs";
⛔ Skipped due to learnings
Learnt from: jcortes
PR: PipedreamHQ/pipedream#14935
File: components/sailpoint/package.json:15-18
Timestamp: 2024-12-12T19:23:09.039Z
Learning: When developing Pipedream components, do not add built-in Node.js modules like `fs` to `package.json` dependencies, as they are native modules provided by the Node.js runtime.

Comment on lines +109 to +117
if (this.$auth?.api_key) {
headers["apikey"] = this.$auth.api_key;
}

if (this.$auth?.oauth_access_token) {
headers["Authorization"] = `Bearer ${this.$auth.oauth_access_token}`;
}

return headers;
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 validation for auth configuration.

The authentication header method should validate that at least one authentication method is configured to provide clearer error messages.

Add validation to ensure proper authentication:

_getAuthHeaders() {
  const headers = {
    "Content-Type": "application/json",
    "User-Agent": "Pipedream/1.0",
  };

+ if (!this.$auth?.api_key && !this.$auth?.oauth_access_token) {
+   throw new Error("Authentication required: Configure either API key or OAuth token");
+ }

  if (this.$auth?.api_key) {
    headers["apikey"] = this.$auth.api_key;
  }

  if (this.$auth?.oauth_access_token) {
    headers["Authorization"] = `Bearer ${this.$auth.oauth_access_token}`;
  }

  return headers;
},
📝 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
if (this.$auth?.api_key) {
headers["apikey"] = this.$auth.api_key;
}
if (this.$auth?.oauth_access_token) {
headers["Authorization"] = `Bearer ${this.$auth.oauth_access_token}`;
}
return headers;
_getAuthHeaders() {
const headers = {
"Content-Type": "application/json",
"User-Agent": "Pipedream/1.0",
};
if (!this.$auth?.api_key && !this.$auth?.oauth_access_token) {
throw new Error("Authentication required: Configure either API key or OAuth token");
}
if (this.$auth?.api_key) {
headers["apikey"] = this.$auth.api_key;
}
if (this.$auth?.oauth_access_token) {
headers["Authorization"] = `Bearer ${this.$auth.oauth_access_token}`;
}
return headers;
},
🤖 Prompt for AI Agents
In components/trustpilot/app/trustpilot.app.ts around lines 109 to 117, the code
sets authentication headers without validating if any authentication method is
configured. Add a validation step before setting headers to check if either
api_key or oauth_access_token is present. If neither is configured, throw an
error or return a clear message indicating that authentication configuration is
missing. This ensures clearer error handling and prevents silent failures.

Comment on lines +240 to +282
async getServiceReviews({
businessUnitId,
stars = null,
sortBy = SORT_OPTIONS.CREATED_AT_DESC,
limit = DEFAULT_LIMIT,
offset = 0,
includeReportedReviews = false,
tags = [],
language = null,
} = {}) {
if (!validateBusinessUnitId(businessUnitId)) {
throw new Error("Invalid business unit ID");
}

const endpoint = buildUrl(ENDPOINTS.PRIVATE_SERVICE_REVIEWS, { businessUnitId });
const params = {
stars,
orderBy: sortBy,
perPage: limit,
page: Math.floor(offset / limit) + 1,
includeReportedReviews,
language,
};

if (tags.length > 0) {
params.tags = tags.join(",");
}

const response = await this._makeRequestWithRetry({
endpoint,
params,
});

return {
reviews: response.reviews?.map(parseReview) || [],
pagination: {
total: response.pagination?.total || 0,
page: response.pagination?.page || 1,
perPage: response.pagination?.perPage || limit,
hasMore: response.pagination?.hasMore || false,
},
};
},
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

Consider extracting common review fetching logic.

The getServiceReviews and getProductReviews methods (lines 240-282 and 318-360) share nearly identical logic with only the endpoint differing. This violates the DRY principle.

Extract common logic into a private method:

+ async _getReviews({
+   endpoint,
+   businessUnitId,
+   stars = null,
+   sortBy = SORT_OPTIONS.CREATED_AT_DESC,
+   limit = DEFAULT_LIMIT,
+   offset = 0,
+   includeReportedReviews = false,
+   tags = [],
+   language = null,
+ } = {}) {
+   if (businessUnitId && !validateBusinessUnitId(businessUnitId)) {
+     throw new Error("Invalid business unit ID");
+   }
+
+   const params = {
+     stars,
+     orderBy: sortBy,
+     perPage: limit,
+     page: Math.floor(offset / limit) + 1,
+     includeReportedReviews,
+     language,
+   };
+
+   if (tags.length > 0) {
+     params.tags = tags.join(",");
+   }
+
+   const response = await this._makeRequestWithRetry({
+     endpoint,
+     params,
+   });
+
+   return {
+     reviews: response.reviews?.map(parseReview) || [],
+     pagination: {
+       total: response.pagination?.total || 0,
+       page: response.pagination?.page || 1,
+       perPage: response.pagination?.perPage || limit,
+       hasMore: response.pagination?.hasMore || false,
+     },
+   };
+ },

async getServiceReviews(options = {}) {
+ const endpoint = buildUrl(ENDPOINTS.PRIVATE_SERVICE_REVIEWS, { businessUnitId: options.businessUnitId });
+ return this._getReviews({ endpoint, ...options });
},

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In components/trustpilot/app/trustpilot.app.ts between lines 240 and 282, the
getServiceReviews method shares almost identical logic with getProductReviews
(lines 318-360) except for the endpoint used. To adhere to the DRY principle,
extract the shared review fetching logic into a private method that accepts the
endpoint and parameters, then refactor both getServiceReviews and
getProductReviews to call this new method with their respective endpoints.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
User submitted Submitted by a user
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants