Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

Afstkla
Copy link
Contributor

@Afstkla Afstkla commented Aug 22, 2025

WHY

Fixes a bunch of issues with the Trustpilot integration

Summary by CodeRabbit

  • New Features

    • Get Conversation from Product Review action (two-step fetch with clear success/early-exit paths).
  • Enhancements

    • Review actions validate review IDs, return richer metadata (reviewId, conversationId, requestTime, contentLength) and emit stepwise summaries.
    • Reply actions require business/author user IDs and support optional integration IDs.
  • Refactor

    • Unified HTTP client (makeRequest), new ENDPOINTS/utils, standardized fetch APIs with pagination and expanded filters; polling shifted to time-based (15 min).
  • Removed

    • Multiple legacy polling sources and several public review/webhook endpoints.
  • Chores

    • Package version bumped to 0.2.0.

Copy link

vercel bot commented Aug 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
pipedream-docs Ignored Ignored Sep 17, 2025 5:00pm
pipedream-docs-redirect-do-not-edit Ignored Ignored Sep 17, 2025 5:00pm

Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

Walkthrough

Introduce an auth-aware makeRequest client, reorganize endpoints toward private/service flows, replace bespoke HTTP/auth logic with unified requests, refactor parsers, add conversation retrieval and multi-step reply flows, convert polling to a lastReviewTime model, remove several legacy sources, and bump package versions.

Changes

Cohort / File(s) Summary of Changes
Core HTTP & constants
components/trustpilot/common/api-client.mjs, components/trustpilot/common/constants.mjs
Add makeRequest (auth-aware HTTP client); reorganize ENDPOINTS to private/service-focused paths; remove many legacy public constants and webhook/invitation endpoints; change endpoint shapes and names.
Utilities / Parsers
components/trustpilot/common/utils.mjs
Rename/repurpose parsers: parseBusinessUnitparseProductReview, parseWebhookPayloadparseServiceReview; broaden sanitization and returned review shapes; remove sleep/retry helper.
App surface & package metadata
components/trustpilot/trustpilot.app.mjs, components/trustpilot/package.json
Replace in-app request/auth scaffolding with makeRequest+ENDPOINTS; revamp propDefinitions (sku/page/perPage/locale/state/etc.), add fetchServiceReviews/fetchProductReviews, change searchBusinessUnits signature; bump package version to 0.2.0.
Fetch actions (product & service)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs, components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs, components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs, components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs
Switch to makeRequest with built ENDPOINTS; add/rename props (reviewId, page, perPage, sku, locale, state); validate reviewId where applicable; parse responses with new parsers; adjust return shapes (parsed review + metadata or raw result); bump versions.
Conversation & reply actions
components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs, components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs, components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs
Add two-step conversation retrieval; rework reply flows into multi-step sequences (fetch review → create conversation if needed → post comment) or direct private reply endpoint; rename/replace props (content, integrationId, businessUserId, authorBusinessUserId); add validations and structured metadata.
Polling base & source rewrites
components/trustpilot/sources/common/polling.mjs, components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs, components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs
Replace stateful dedupe polling with time-based lastReviewTime persistence; introduce hooks fetchReviews, getFetchParams, filterNewReviews, generateSummary; implement paginated fetch via private endpoints and persist lastReviewTime.
Source deletions
components/trustpilot/sources/new-conversations/..., components/trustpilot/sources/new-product-review-replies/..., components/trustpilot/sources/new-service-review-replies/..., components/trustpilot/sources/updated-conversations/..., components/trustpilot/sources/updated-product-reviews/..., components/trustpilot/sources/updated-service-reviews/...
Remove multiple legacy sources and their imports (conversations, replies, updated reviews).
Action metadata & messages
components/trustpilot/actions/*/*.mjs
Standardize error messages and $summary progress messages, add input validation (e.g., reviewId format), and apply version bumps across updated actions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Action as GetConversationFromProductReview
  participant API as Trustpilot API
  Note right of Action #B3E5FC: validate reviewId
  User->>Action: run({ reviewId })
  Action->>API: GET /private/product-reviews/{reviewId}
  API-->>Action: product review (may include conversationId)
  alt conversationId exists
    Action->>API: GET /private/conversations/{conversationId}
    API-->>Action: conversation
    Action-->>User: { success: true, conversation, metadata }
  else no conversationId
    Action-->>User: { success: false, message, review:{ hasConversation:false }, metadata }
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Action as ReplyToProductReview
  participant API as Trustpilot API
  Note right of Action #FFE0B2: validate inputs (reviewId, content, businessUserId)
  User->>Action: run({ reviewId, content, businessUserId, integrationId? })
  Action->>API: GET /private/product-reviews/{reviewId}
  API-->>Action: review (may include conversationId)
  alt conversation exists
    Action->>API: POST /private/conversations/{conversationId}/comments
    Note right of Action #C8E6C9: header x-business-user-id
    API-->>Action: posted comment
  else create conversation then comment
    Action->>API: POST /private/product-reviews/{reviewId}/create-conversation
    API-->>Action: { conversationId }
    Action->>API: POST /private/conversations/{conversationId}/comments
    Note right of Action #C8E6C9: header x-business-user-id
    API-->>Action: posted comment
  end
  Action-->>User: { comment, metadata:{ wasConversationCreated, ... } }
Loading
sequenceDiagram
  autonumber
  actor User
  participant Action as ReplyToServiceReview
  participant API as Trustpilot API
  Note right of Action #FFCDD2: validate inputs (reviewId, authorBusinessUserId, message)
  User->>Action: run({ reviewId, authorBusinessUserId, message })
  Action->>API: POST /private/reviews/{reviewId}/reply
  Note right of Action #E1BEE7: body { authorBusinessUserId, message }
  API-->>Action: reply result
  Action-->>User: { reply, metadata:{ httpStatus, messageLength, ... } }
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

Poem

I’m a rabbit in the code, I hop through every thread,
I stitch endpoints and parsers, tidy what was spread.
I fetch the review, find convo, then post your thoughtful note,
Timers tick by quarter-hours — new reviews set afloat.
Thump-thump — versions bumped, the integration’s sped! 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The title "trustpilot fixes" is topically related but too generic and non‑descriptive to convey the primary change or impact of this large refactor; it does not summarize the main technical work (API client addition, endpoint/prop renames, source deletions, polling redesign, etc.) and would be unhelpful to reviewers or future readers scanning history. Please update the PR title to a concise, specific summary of the main change (for example, "trustpilot: add makeRequest client and migrate to private review endpoints" or "trustpilot: refactor polling/sources and unify request handling"); include one short phrase describing the primary impact so reviewers can immediately understand intent.
Description Check ❓ Inconclusive The PR includes the repository's WHY section but the content ("Fixes a bunch of issues with the Trustpilot integration") is overly terse and does not meet the template's intent; it lacks a clear summary of what was changed, why each change was necessary, the user/consumer impact, whether there are breaking API changes, and what verification/tests were performed. Given the scope shown in the diff summary (new makeRequest client, endpoint/constants changes, many source deletions, prop/return‑shape changes and version bumps), the current description is too vague for an effective review. Expand the WHY section to briefly list the key changes and their rationale, call out any public API or breaking changes (props, return shapes, endpoint constant removals), link to test results or CI passing, and provide upgrade or verification steps for reviewers; a short summary of affected files/components and the expected runtime impact will make the PR reviewable.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f306eda and e96c626.

📒 Files selected for processing (2)
  • components/etrusted/etrusted.app.mjs (1 hunks)
  • components/financial_data/financial_data.app.mjs (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • components/etrusted/etrusted.app.mjs
  • components/financial_data/financial_data.app.mjs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

@adolfo-pd adolfo-pd added the User submitted Submitted by a user label Aug 22, 2025
@Afstkla Afstkla changed the title trustpilot fixes WIP: trustpilot fixes Aug 22, 2025
@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:

Afstkla and others added 8 commits August 27, 2025 08:34
Major improvements and API updates across all actions:
- Enhanced private API support with proper authentication
- Improved parameter handling and validation
- Better error handling and response structures
- Added new conversation flow for product reviews
- Fixed endpoint URLs to match latest API documentation
- Streamlined request/response processing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@Afstkla Afstkla marked this pull request as ready for review September 3, 2025 14:07
@Afstkla Afstkla changed the title WIP: trustpilot fixes trustpilot fixes Sep 3, 2025
@andrewjschuang andrewjschuang self-requested a review September 3, 2025 14:17
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: 15

🧹 Nitpick comments (37)
components/trustpilot/package.json (1)

3-3: Confirm semver intent for removed sources

Multiple sources were removed across this PR. Under 0.x, minor bumps may include breaking changes, but please confirm no active workflows depend on removed keys and add a CHANGELOG note calling this out.

components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (2)

51-54: Order by newest first (if supported by endpoint)

Prevents missing new items on page 1 when mixing statuses.

Apply if supported:

       const fetchParams = {
         businessUnitId: this.businessUnitId,
         perPage: 100,
-        page: 1,
+        page: 1,
+        orderBy: "createdat.desc",
       };

To verify support, check the app’s product review fetch or docs for orderBy on private product reviews.


56-60: Paginate until reviews are older than last poll to avoid gaps

If > perPage reviews arrive between polls, page 1 isn’t enough.

Apply pattern:

-      const result = await this.trustpilot.fetchProductReviews(fetchParams);
-      const reviews = result.reviews || [];
+      const toMs = (d) => new Date(d).getTime();
+      const lastTs = Number(this._getLastReviewTime()) || 0;
+      let page = 1;
+      let reviews = [];
+      while (true) {
+        const { reviews: batch = [] } = await this.trustpilot.fetchProductReviews({ ...fetchParams, page });
+        if (!batch.length) break;
+        reviews.push(...batch);
+        const oldest = Math.min(...batch.map((r) => toMs(r.createdAt)));
+        if (oldest <= lastTs) break;
+        page += 1;
+      }
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (3)

47-49: Be robust to response shape (review vs root object)

Some endpoints return { review: {...} }. Safely support both.

Apply:

-      const review = parseProductReview(response);
+      const raw = response?.review ?? response;
+      const review = parseProductReview(raw);

59-61: Improve error messaging for 404

Provide a clearer “not found” for missing IDs; keep other errors wrapped.

Apply:

-    } catch (error) {
-      throw new Error(`Failed to fetch product review: ${error.message}`);
-    }
+    } catch (error) {
+      if (error.response?.status === 404) {
+        throw new Error(`Review ${this.reviewId} not found (404)`);
+      }
+      throw new Error(`Failed to fetch product review: ${error.message}`);
+    }

42-46: Optional: set an explicit timeout

Avoid long hangs on degraded endpoints.

Apply:

       const response = await makeRequest(this.trustpilot, {
         endpoint,
+        timeout: 20000,
       });
components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs (2)

50-63: Add a final $summary when no conversation exists.

This improves UX in runs where the action exits early without fetching a conversation.

       if (!conversationId) {
+        $.export("$summary", `No conversation found for product review ${reviewId}`);
         return {
           success: false,
           message: "No conversation found for this product review",

1-8: Return richer API error details to aid troubleshooting.

Surface parsed status/code/message so users can act on 401/403/404/429, etc.

-import {
-  buildUrl,
-  validateReviewId,
-} from "../../common/utils.mjs";
+import {
+  buildUrl,
+  validateReviewId,
+  parseApiError,
+} from "../../common/utils.mjs";
-  } catch (error) {
-      throw new ConfigurationError(`Failed to get conversation from product review: ${error.message}`);
-  }
+  } catch (error) {
+    const apiErr = parseApiError(error);
+    throw new ConfigurationError(
+      `Failed to get conversation from product review: ${apiErr.message} (status: ${apiErr.status}, code: ${apiErr.code})`,
+    );
+  }

Also applies to: 90-92

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

79-97: Handle “conversation already exists” (HTTP 409) to avoid failing valid runs.

Races can occur between Step 1 and Step 2. Gracefully recover by refetching the review (or using the ID from the error body if present). Also, include x-business-user-id on conversation creation if the API requires it.

-      // Step 2: Create conversation if it doesn't exist
-      if (!conversationId) {
-        $.export("$summary", "Creating conversation for review...");
-
-        const createConversationEndpoint = buildUrl(ENDPOINTS.CREATE_CONVERSATION_FOR_REVIEW, {
-          reviewId,
-        });
-
-        const createConversationResponse = await makeRequest(this.trustpilot, {
-          endpoint: createConversationEndpoint,
-          method: "POST",
-        });
-
-        conversationId = createConversationResponse.conversationId;
-
-        if (!conversationId) {
-          throw new Error("Failed to create conversation - no conversationId returned");
-        }
-      }
+      // Step 2: Create conversation if it doesn't exist
+      if (!conversationId) {
+        $.export("$summary", "Creating conversation for review...");
+        const createConversationEndpoint = buildUrl(ENDPOINTS.CREATE_CONVERSATION_FOR_REVIEW, { reviewId });
+        try {
+          const createConversationResponse = await makeRequest(this.trustpilot, {
+            endpoint: createConversationEndpoint,
+            method: "POST",
+            additionalHeaders: {
+              "x-business-user-id": businessUserId, // required by some endpoints
+            },
+          });
+          conversationId = createConversationResponse.conversationId;
+        } catch (err) {
+          // If the conversation already exists, recover by re-fetching it
+          if (err?.response?.status === 409) {
+            $.export("$summary", "Conversation already exists. Fetching conversationId...");
+            const refreshed = await makeRequest(this.trustpilot, { endpoint: getReviewEndpoint });
+            conversationId = refreshed.conversationId || err?.response?.data?.conversationId;
+          } else {
+            throw err;
+          }
+        }
+        if (!conversationId) {
+          throw new Error("Failed to create or locate conversationId");
+        }
+      }

63-64: Sanitize and truncate reply content
Trustpilot’s Conversations API doesn’t document a strict limit; community testing indicates a nominal cap of ~10 000 chars and practical limits closer to ~8 000. To prevent server-side rejections, import and apply a sanitizer/truncator:

-import {
-  buildUrl,
-  validateReviewId,
-} from "../../common/utils.mjs";
+import {
+  buildUrl,
+  validateReviewId,
+  sanitizeInput,
+} from "../../common/utils.mjs";
-    const trimmedContent = content.trim();
+    const trimmedContent = sanitizeInput(content, 8000); // truncate/sanitize to 8 000 chars

Also ensure the x-business-user-id header is sent on Create Conversation calls when using a client_credentials token.

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

107-149: PII exposure: consumer email is passed through. Verify intent and compliance.

parseProductReview returns consumer.email verbatim. Ensure this is expected and compliant with your data handling policy. If not strictly required, consider masking or omitting it by default.


197-203: Normalize boolean default for countsTowardsLocationTrustScore.

For consistency with countsTowardsTrustScore, default this to false when undefined.

-    countsTowardsLocationTrustScore: review.countsTowardsLocationTrustScore,
+    countsTowardsLocationTrustScore: review.countsTowardsLocationTrustScore || false,
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (2)

77-77: Guard summary against unexpected API shapes.

Avoid runtime errors if reviews is missing/not an array.

-      $.export("$summary", `Successfully fetched ${result.reviews.length} product review(s) for business unit ${businessUnitId}`);
+      const count = Array.isArray(result?.reviews) ? result.reviews.length : Array.isArray(result) ? result.length : 0;
+      $.export("$summary", `Successfully fetched ${count} product review(s) for business unit ${businessUnitId}`);

35-40: Language vs. locale: confirm intent and API mapping.

Both language and locale are exposed and forwarded. If the API expects only one (or mutually exclusive values), this can lead to ignored params or ambiguous results. Consider a single prop or validation to ensure they’re not conflicting.

Example constraint:

  async run({ $ }) {
    const {
      businessUnitId,
      page,
      perPage,
      sku,
      language,
      state,
      locale,
    } = this;

+   if (language && locale) {
+     throw new Error("Specify either language or locale, not both.");
+   }

Also applies to: 47-52, 55-63

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

26-34: Review ID validation is too strict for hex case.

validateReviewId only allows lowercase a–f; ObjectIDs/hex IDs are case-insensitive. Allow uppercase to avoid false negatives.

Update the validator in components/trustpilot/common/utils.mjs:

-export function validateReviewId(reviewId) {
-  // Trustpilot Review IDs are 24-character hexadecimal strings (MongoDB ObjectID format)
-  return (
-    typeof reviewId === "string" &&
-    /^[a-f0-9]{24}$/.test(reviewId)
-  );
-}
+export function validateReviewId(reviewId) {
+  // Trustpilot Review IDs are 24-character hexadecimal strings (MongoDB ObjectID format)
+  return typeof reviewId === "string" && /^[A-Fa-f0-9]{24}$/.test(reviewId);
+}

13-14: Double-check documentation link matches the private-by-id endpoint.

The link references “business-units-api#get-private-review-by-id” while the code hits /private/reviews/{reviewId}. Ensure the doc URL matches the exact endpoint to avoid confusing users.

components/trustpilot/common/api-client.mjs (3)

37-44: Prevent accidental override of auth headers via args.headers.

Spreading ...args after headers lets callers overwrite Authorization/apikey. Merge only non-header args, or explicitly merge safe header keys.

-  const config = {
+  const { headers: _ignoredHeaders, ...restArgs } = args;
+  const config = {
     method,
     url,
     headers,
     params: formatQueryParams(params),
     timeout,
-    ...args,
+    ...restArgs,
   };

54-69: Retry policy could be more robust.

Consider retrying on transient errors like 408/429/500/502/503/504 and network timeouts, with jittered exponential backoff.

Example:

-  if (retries > 0 && error.response?.status === HTTP_STATUS.TOO_MANY_REQUESTS) {
+  const status = error.response?.status;
+  const transient = [HTTP_STATUS.TOO_MANY_REQUESTS, 408, 500, 502, 503, 504];
+  if (retries > 0 && transient.includes(status)) {
-    const delay = Math.min(
-      RETRY_CONFIG.INITIAL_DELAY * (RETRY_CONFIG.MAX_RETRIES - retries + 1),
-      RETRY_CONFIG.MAX_DELAY,
-    );
+    const attempt = RETRY_CONFIG.MAX_RETRIES - retries + 1;
+    const base = Math.min(RETRY_CONFIG.INITIAL_DELAY * 2 ** (attempt - 1), RETRY_CONFIG.MAX_DELAY);
+    const jitter = Math.random() * base * 0.2;
+    const delay = Math.min(base + jitter, RETRY_CONFIG.MAX_DELAY);

116-135: Heuristic private URL detection.

Using url.includes("private") works with current constants, but is brittle. Prefer a flag or derive from endpoint groups to avoid misclassification if paths change.

Minimal change:

-export async function makeRequest(trustpilotApp, {
+export async function makeRequest(trustpilotApp, {
   endpoint,
   method = "GET",
   params = {},
   data = null,
   timeout = 30000,
   additionalHeaders = {},
   ...args
 }, retries = RETRY_CONFIG.MAX_RETRIES) {
   const url = `${BASE_URL}${endpoint}`;
-  const headers = {
-    ...getAuthHeaders(trustpilotApp, url),
+  const headers = {
+    ...getAuthHeaders(trustpilotApp, endpoint),
     ...additionalHeaders,
   };

And switch getAuthHeaders to check the endpoint path (startsWith("/private")) instead of the full URL string.

components/trustpilot/actions/search-business-units-test/search-business-units-test.mjs (1)

39-48: Consider null safety for businessUnit properties.

The destructuring assumes id and displayName exist on each business unit. Add defensive checks to prevent runtime errors.

       const options = businessUnits.map((businessUnit) => {
         const {
           id, displayName,
         } = businessUnit;
+        
+        if (!id || !displayName) {
+          console.warn("Business unit missing required fields:", businessUnit);
+          return null;
+        }

         return {
           label: displayName,
           value: id,
         };
-      });
+      }).filter(Boolean); // Remove null entries
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)

6-6: Description is too verbose for a component description.

The description is extremely long and contains implementation details that belong in documentation rather than the component description field.

-  description: "Emit new event when a customer posts a new service review on Trustpilot. This source periodically polls the Trustpilot API to detect new service reviews using the private reviews API for comprehensive coverage. Each event contains the complete review data including star rating, review text, consumer details, business unit info, customer email, and timestamps. Ideal for monitoring overall business reputation, tracking customer satisfaction metrics, and triggering workflows based on review ratings or content.",
+  description: "Emit new event when a customer posts a new service review on Trustpilot using the private reviews API. [See the documentation](https://developers.trustpilot.com/private-reviews-api)",
components/trustpilot/common/constants.mjs (3)

47-54: De-duplicate sort strings across actions

Since SORT_OPTIONS are defined here, import and reuse them in actions (e.g., fetch-service-reviews) to avoid drift.

Apply in the action file:

-        {
-          label: "Created At (Ascending)",
-          value: "createdat.asc",
-        },
+        { label: "Created At (Ascending)", value: SORT_OPTIONS.CREATED_AT_ASC },

(Repeat for the other three options after importing SORT_OPTIONS.)


3-10: Stale webhook events?

WEBHOOK_EVENTS remain, but webhook endpoints were dropped in this PR. If they’re unused, remove them or add a comment noting they’re for inbound-only use to reduce confusion.


91-97: Align SOURCE_TYPES with the current sources

Some source types (e.g., new_conversations) were removed per the PR summary. Either prune them or annotate deprecated values to prevent accidental use.

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

6-8: Description overpromises vs single-page fetch

The action fetches a single page, but the description claims up to 100,000 records. Either implement pagination within the action or adjust the copy.

Apply to fix the copy:

-  description: "Get private reviews for a business unit, limited to 100,000 records. Response includes customer email and order ID. [See the documentation](https://developers.trustpilot.com/business-units-api#get-private-reviews-for-business-unit)",
+  description: "Get a page of private reviews for a business unit. Response includes customer email and order ID. [See the documentation](https://developers.trustpilot.com/business-units-api#get-private-reviews-for-business-unit)",

If you want pagination here, I can propose a minimal loop that respects perPage/max pages and returns aggregated results.


31-36: Validate/sanitize stars input

The API expects 1–5 comma-separated. Consider normalizing (e.g., strip spaces, validate digits) upstream to prevent 400s.

I suggest adding validation in the app’s fetchServiceReviews (see app comment with diff).


52-76: Use constants for orderBy values

Avoid hardcoding the same strings in multiple files.

Apply:

-import trustpilot from "../../trustpilot.app.mjs";
+import trustpilot from "../../trustpilot.app.mjs";
+import { SORT_OPTIONS } from "../../common/constants.mjs";
...
-          value: "createdat.asc",
+          value: SORT_OPTIONS.CREATED_AT_ASC,
...
-          value: "createdat.desc",
+          value: SORT_OPTIONS.CREATED_AT_DESC,
...
-          value: "stars.asc",
+          value: SORT_OPTIONS.STARS_ASC,
...
-          value: "stars.desc",
+          value: SORT_OPTIONS.STARS_DESC,

120-131: Grammar nit and add example format for datetimes

Small copy fix and an example improves UX.

-      description: "Filter reviews by datetime range. If no time is specified than time is implicit 00:00:00. Format: 2013-09-07T13:37:00",
+      description: "Filter reviews by datetime range. If no time is specified then time is implicit 00:00:00. Example format: 2013-09-07T13:37:00",
...
-      description: "Filter reviews by datetime range. If no time is specified than time is implicit 00:00:00. Format: 2013-09-07T13:37:00",
+      description: "Filter reviews by datetime range. If no time is specified then time is implicit 00:00:00. Example format: 2013-09-07T13:37:00",

205-207: Preserve original error as cause

Keeps stack/metadata for debugging while improving the message.

-    } catch (error) {
-      throw new Error(`Failed to fetch service reviews: ${error.message}`);
+    } catch (error) {
+      throw new Error(`Failed to fetch service reviews: ${error.message}`, { cause: error });
     }
components/trustpilot/trustpilot.app.mjs (8)

112-121: Return empty options on error is safe, but log is minimal

If you see repeated failures, consider including response status/body in the debug log to aid triage.


147-154: Business unit ID format validation may be too strict

validateBusinessUnitId enforces lowercase hex. ObjectIDs are case-insensitive; rejecting uppercase can block valid input pasted from external tools.

If you want to relax validation without changing all callers, update utils:

// components/trustpilot/common/utils.mjs
export function validateBusinessUnitId(businessUnitId) {
  return typeof businessUnitId === "string" && /^[a-f0-9]{24}$/i.test(businessUnitId);
}

160-182: Consider normalizing and validating stars before request

Prevents 400s and keeps API surface tidy.

Apply within fetchServiceReviews:

-      if (stars) queryParams.stars = stars;
+      if (stars) {
+        const normalized = String(stars).replace(/\s+/g, "");
+        if (!/^(?:[1-5](?:,[1-5])*)$/.test(normalized)) {
+          throw new Error('Invalid "stars" format. Use comma-separated integers 1–5, e.g. "1,3,5".');
+        }
+        queryParams.stars = normalized;
+      }

189-196: Service reviews pagination shape assumption

You map response.pagination.{total,page,perPage,hasMore}. Please confirm the v1 private reviews endpoint returns this shape (some Trustpilot endpoints use links.next/prev). If not guaranteed, add a fallback like the product flow.


221-226: Add business unit ID validation for product reviews for parity

Service reviews validate ID format; product reviews should do the same to catch obvious errors early.

   if (!businessUnitId) {
     throw new Error("Business Unit ID is required");
   }
+  if (!validateBusinessUnitId(businessUnitId)) {
+    throw new Error("Invalid business unit ID format");
+  }

248-257: Product reviews pagination shape

You rely on links.total and links.next. Please confirm this is stable and documented. If both shapes occur (pagination vs links), consider a defensive merge to standardize the returned pagination object.


210-269: Optional: clamp perPage to MAX_LIMIT at the app layer

Actions set max=100, but sources or external callers could bypass. Clamp to constants.MAX_LIMIT defensively.

-import { ENDPOINTS } from "./common/constants.mjs";
+import { ENDPOINTS, MAX_LIMIT } from "./common/constants.mjs";
...
-      if (perPage) queryParams.perPage = perPage;
+      if (perPage) queryParams.perPage = Math.max(1, Math.min(perPage, MAX_LIMIT));

(Repeat for fetchProductReviews.)


108-121: Add concise JSDoc for public methods

Helps users and aligns with the guidelines to document methods.

/**
 * Search business units by query.
 * @param {{query?: string, page?: number}} params
 * @returns {Promise<Array<{id: string, displayName: string}>>}
 */

(Apply similarly to fetchServiceReviews and fetchProductReviews.)

Also applies to: 123-207, 209-269

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9b23afd and d5f6d9e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (2 hunks)
  • components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (2 hunks)
  • components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (2 hunks)
  • components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (2 hunks)
  • components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs (1 hunks)
  • components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (2 hunks)
  • components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (2 hunks)
  • components/trustpilot/actions/search-business-units-test/search-business-units-test.mjs (1 hunks)
  • components/trustpilot/common/api-client.mjs (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 (0 hunks)
  • components/trustpilot/sources/new-conversations/new-conversations.mjs (0 hunks)
  • components/trustpilot/sources/new-product-review-replies/new-product-review-replies.mjs (0 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 (0 hunks)
  • components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1 hunks)
  • components/trustpilot/sources/updated-conversations/updated-conversations.mjs (0 hunks)
  • components/trustpilot/sources/updated-product-reviews/updated-product-reviews.mjs (0 hunks)
  • components/trustpilot/sources/updated-service-reviews/updated-service-reviews.mjs (0 hunks)
  • components/trustpilot/trustpilot.app.mjs (4 hunks)
💤 Files with no reviewable changes (7)
  • components/trustpilot/sources/common/polling.mjs
  • components/trustpilot/sources/new-product-review-replies/new-product-review-replies.mjs
  • components/trustpilot/sources/updated-product-reviews/updated-product-reviews.mjs
  • components/trustpilot/sources/new-conversations/new-conversations.mjs
  • components/trustpilot/sources/updated-conversations/updated-conversations.mjs
  • components/trustpilot/sources/new-service-review-replies/new-service-review-replies.mjs
  • components/trustpilot/sources/updated-service-reviews/updated-service-reviews.mjs
🧰 Additional context used
🧬 Code graph analysis (13)
components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs (3)
components/trustpilot/common/utils.mjs (3)
  • validateReviewId (231-237)
  • buildUrl (51-65)
  • error (266-268)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (1)
  • makeRequest (22-71)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (3)
components/trustpilot/common/utils.mjs (3)
  • validateReviewId (231-237)
  • buildUrl (51-65)
  • parseServiceReview (157-211)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (2)
  • response (51-51)
  • makeRequest (22-71)
components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (3)
components/trustpilot/common/utils.mjs (3)
  • validateReviewId (231-237)
  • buildUrl (51-65)
  • error (266-268)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (1)
  • makeRequest (22-71)
components/trustpilot/common/api-client.mjs (6)
components/trustpilot/common/constants.mjs (6)
  • RETRY_CONFIG (79-83)
  • RETRY_CONFIG (79-83)
  • BASE_URL (1-1)
  • BASE_URL (1-1)
  • HTTP_STATUS (67-77)
  • HTTP_STATUS (67-77)
components/trustpilot/common/utils.mjs (2)
  • formatQueryParams (244-257)
  • sleep (290-292)
components/trustpilot/trustpilot.app.mjs (7)
  • endpoint (156-158)
  • endpoint (227-229)
  • params (125-145)
  • params (211-219)
  • response (112-118)
  • response (184-187)
  • response (243-246)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (2)
  • endpoint (38-40)
  • response (43-45)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (2)
  • endpoint (38-40)
  • response (43-45)
components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (1)
  • endpoint (60-62)
components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (3)
components/trustpilot/common/utils.mjs (3)
  • validateReviewId (231-237)
  • buildUrl (51-65)
  • error (266-268)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (1)
  • makeRequest (22-71)
components/trustpilot/common/utils.mjs (2)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (1)
  • review (48-48)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (1)
  • review (48-48)
components/trustpilot/actions/search-business-units-test/search-business-units-test.mjs (2)
components/trustpilot/trustpilot.app.mjs (2)
  • businessUnits (28-33)
  • businessUnit (36-38)
components/trustpilot/common/utils.mjs (1)
  • error (266-268)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (3)
components/trustpilot/common/utils.mjs (3)
  • validateReviewId (231-237)
  • buildUrl (51-65)
  • parseProductReview (107-150)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (2)
  • response (51-51)
  • makeRequest (22-71)
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (3)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1)
  • result (180-200)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1)
  • result (57-57)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)
  • result (59-59)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (3)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (8)
  • stars (34-34)
  • consumerName (35-35)
  • businessUnit (36-36)
  • lastReviewTime (44-44)
  • fetchParams (47-51)
  • result (59-59)
  • reviews (61-61)
  • latestReviewTime (69-69)
components/trustpilot/trustpilot.app.mjs (3)
  • businessUnit (36-38)
  • reviews (190-190)
  • reviews (249-249)
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1)
  • result (67-75)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (3)
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1)
  • result (67-75)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1)
  • result (57-57)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)
  • result (59-59)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (3)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (8)
  • stars (34-34)
  • consumerName (35-35)
  • businessUnit (37-37)
  • lastReviewTime (45-45)
  • fetchParams (50-54)
  • result (57-57)
  • reviews (59-59)
  • latestReviewTime (79-79)
components/trustpilot/trustpilot.app.mjs (3)
  • businessUnit (36-38)
  • reviews (190-190)
  • reviews (249-249)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1)
  • result (180-200)
components/trustpilot/trustpilot.app.mjs (3)
components/trustpilot/common/api-client.mjs (2)
  • response (51-51)
  • makeRequest (22-71)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/utils.mjs (4)
  • validateBusinessUnitId (218-224)
  • buildUrl (51-65)
  • parseServiceReview (157-211)
  • parseProductReview (107-150)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base
🔇 Additional comments (19)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1)

1-26: Props/timer/db usage looks good

Key, dedupe, timer default, and state storage align with Pipedream sources.

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

28-35: Good input validation

Presence + format checks for reviewId are clear and fail fast.

components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs (1)

36-89: Flow and error handling look solid.

Validation is tight, retry behavior is delegated to the shared client, and the two-step fetch is correct for private product reviews → conversations.

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

41-139: Overall LGTM — correct multi-step flow with clear validation and summaries.

Good use of shared client, endpoints, and input validation; header use for comment creation is correct.


87-91: Verify header requirements for create-conversation.

Some private endpoints require x-business-user-id; you add it for comments but not for conversation creation. The diff above includes it defensively; please confirm with docs.

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

157-210: Service review parsing looks comprehensive and consistent.

Good escaping, sensible defaults (e.g., numberOfLikes), and structure.

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

7-7: No internal dependencies on raw API response shape Only result.reviews and result.pagination are consumed (e.g. in new-product-reviews); no code references raw fields—safe to proceed.

components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)

78-82: Add deduplication logic to prevent duplicate events.

Since the source uses dedupe: "unique" with the review ID, this should work correctly. However, consider adding explicit deduplication if reviews can be updated after creation.

The event emission correctly uses the review ID for deduplication and includes appropriate metadata.

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

14-16: BUSINESS_UNITS endpoint change looks good

The switch to the search endpoint matches expected usage for query-backed lookups.


19-20: Verify PUBLIC_REVIEW_BY_ID path

You changed the path to "/reviews/{reviewId}". Please confirm Trustpilot’s v1 public API supports fetching a review by ID without the business unit path to avoid 404s.


21-25: Service review endpoints: solid addition

New private service review endpoints align with the refactor and naming is consistent.


27-30: Product review + create-conversation endpoints look consistent

Naming and paths align with the new flows.


34-35: Confirm reply-to-conversation path semantics

Changing to "/private/conversations/{conversationId}/comments" typically implies a comments collection (often POST). Make sure all callers switched HTTP method / payload accordingly.

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

21-22: Reusing app propDefinition for language is correct

Keeps the surface consistent across actions.


23-30: Page prop contract is clear

Min, default, and empty-page behavior are documented. Looks good.


43-51: Per-page bounds are good

min/max set to 1/100 matches API.


178-201: Good: routes through shared app method

Keeps logic centralized and returns the unified shape.

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

18-27: Nice UX: empty query default and useQuery

Defaulting to "a" avoids Trustpilot’s requirement pitfalls and improves searchability.


29-33: Correct page index translation

Clear comment and correct +1 transform.

Afstkla and others added 8 commits September 3, 2025 16:30
…reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (1)

36-62: Preserve rich Axios errors: remove try/catch wrapper.

Catching and rethrowing with new Error(...) discards Pipedream Axios context (status, data). Let errors bubble so users get full diagnostics.

-    try {
-      // Build the endpoint URL for private service review
-      const endpoint = buildUrl(ENDPOINTS.SERVICE_REVIEW_BY_ID, {
-        reviewId,
-      });
-
-      // Make the API request
-      const response = await makeRequest($, this.trustpilot, {
-        endpoint,
-      });
-
-      // Parse the service review with the correct parser
-      const review = parseServiceReview(response);
-
-      $.export("$summary", `Successfully fetched service review ${reviewId}`);
-
-      return {
-        review,
-        metadata: {
-          reviewId,
-          requestTime: new Date().toISOString(),
-        },
-      };
-    } catch (error) {
-      throw new Error(`Failed to fetch service review: ${error.message}`);
-    }
+    // Build the endpoint URL for private service review
+    const endpoint = buildUrl(ENDPOINTS.SERVICE_REVIEW_BY_ID, { reviewId });
+
+    // Make the API request
+    const response = await makeRequest($, this.trustpilot, { endpoint });
+
+    // Parse the service review with the correct parser
+    const review = parseServiceReview(response);
+
+    $.export("$summary", `Successfully fetched service review ${reviewId}`);
+
+    return {
+      review,
+      metadata: {
+        reviewId,
+        requestTime: new Date().toISOString(),
+      },
+    };
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1)

65-83: Preserve Axios error details: drop try/catch.

Let Axios throw to keep status, data, etc., rather than wrapping with new Error(...).

-    try {
-      // Use the shared method from the app
-      const result = await this.trustpilot.fetchProductReviews($, {
-        businessUnitId,
-        page,
-        perPage,
-        sku,
-        language,
-        state,
-        locale,
-      });
-
-      $.export("$summary", `Successfully fetched ${result.reviews.length} product review(s) for business unit ${businessUnitId}`);
-
-      return result;
-    } catch (error) {
-      throw new Error(`Failed to fetch product reviews: ${error.message}`);
-    }
+    // Use the shared method from the app
+    const result = await this.trustpilot.fetchProductReviews($, {
+      businessUnitId,
+      page,
+      perPage,
+      sku,
+      language,
+      state,
+      locale,
+    });
+
+    $.export("$summary", `Successfully fetched ${result.reviews?.length ?? 0} product review(s) for business unit ${businessUnitId}`);
+
+    return result;
♻️ Duplicate comments (1)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1)

23-30: Good: page prop UX and guardrails

Clear description, min=1, sensible default. Matches prior review guidance.

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

28-35: Use ConfigurationError for input/flow errors (align with other actions).

Consistent error types improve UX and surface configuration issues clearly.

+import { ConfigurationError } from "@pipedream/platform";
 import { makeRequest } from "../../common/api-client.mjs";
 import { ENDPOINTS } from "../../common/constants.mjs";
 import {
   buildUrl,
   parseProductReview,
   validateReviewId,
 } from "../../common/utils.mjs";

 // Validate required parameters
 if (!reviewId) {
-  throw new Error("Review ID is required");
+  throw new ConfigurationError("Review ID is required");
 }
 if (!validateReviewId(reviewId)) {
-  throw new Error("Invalid review ID format");
+  throw new ConfigurationError("Invalid review ID format");
 }

 ...
 } catch (error) {
-  throw new Error(`Failed to fetch product review: ${error.message}`);
+  throw new ConfigurationError(`Failed to fetch product review: ${error.message}`);
 }

Also applies to: 59-61, 2-4

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

63-64: Sanitize reply content without imposing a length cap.

Avoid control chars while respecting prior decision to not enforce arbitrary limits.

 import { makeRequest } from "../../common/api-client.mjs";
 import { ENDPOINTS } from "../../common/constants.mjs";
 import {
   buildUrl,
   validateReviewId,
+  sanitizeInput,
 } from "../../common/utils.mjs";

-const trimmedContent = content.trim();
+const sanitizedContent = sanitizeInput(content, Infinity);

 // Prepare request data
 const requestData = {
-  content: trimmedContent,
+  content: sanitizedContent,
 };

 // Add integrationId if provided
 if (integrationId) {
   requestData.integrationId = integrationId;
 }

 ...
 return {
   success: true,
   comment: replyResponse,
   metadata: {
     reviewId,
     conversationId,
     businessUserId,
-    contentLength: trimmedContent.length,
+    contentLength: sanitizedContent.length,
     integrationId: integrationId || null,
     wasConversationCreated: !review.conversationId,
     requestTime: new Date().toISOString(),
   },
 };

Also applies to: 106-115, 127-139, 5-8


67-68: Intermediate $summary calls are overwritten.

Keep a single final summary or switch these to logs if you need progress notes.

-$.export("$summary", "Fetching product review details...");
 ...
-$.export("$summary", "Creating conversation for review...");
 ...
-$.export("$summary", "Posting reply comment...");

Also applies to: 81-82, 100-101

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

218-224: Accept uppercase hex and stray whitespace for IDs.

ObjectIDs can be uppercase; trimming avoids false negatives.

 export function validateBusinessUnitId(businessUnitId) {
   // Trustpilot Business Unit IDs are 24-character hexadecimal strings (MongoDB ObjectID format)
   return (
-    typeof businessUnitId === "string" &&
-    /^[a-f0-9]{24}$/.test(businessUnitId)
+    typeof businessUnitId === "string" &&
+    /^[a-f0-9]{24}$/i.test(businessUnitId.trim())
   );
 }

231-236: Same normalization for review IDs.

 export function validateReviewId(reviewId) {
   // Trustpilot Review IDs are 24-character hexadecimal strings (MongoDB ObjectID format)
   return (
-    typeof reviewId === "string" &&
-    /^[a-f0-9]{24}$/.test(reviewId)
+    typeof reviewId === "string" &&
+    /^[a-f0-9]{24}$/i.test(reviewId.trim())
   );
 }

33-36: Comment/code mismatch on sanitizeInput.

The code uses explicit ranges, not Unicode property escapes. Update the comment for clarity.

-// Using Unicode property escapes for safer regex
-// eslint-disable-next-line no-control-regex
+// Using explicit control-char ranges for broad runtime support
+// eslint-disable-next-line no-control-regex
   sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (2)

13-14: Add helpful docs link to description.

-description: "Reply to a service review on Trustpilot.",
+description: "Reply to a service review on Trustpilot. [See the documentation](https://developers.trustpilot.com/service-reviews-api#reply-to-a-review)",

56-57: Sanitize message without adding a size limit.

Removes control chars but keeps your “no arbitrary cutoff” stance.

 import { makeRequest } from "../../common/api-client.mjs";
 import { ENDPOINTS } from "../../common/constants.mjs";
 import {
   buildUrl,
   validateReviewId,
+  sanitizeInput,
 } from "../../common/utils.mjs";

-const trimmedMessage = message.trim();
+const sanitizedMessage = sanitizeInput(message, Infinity);

 // Prepare request data according to API specification
 const requestData = {
   authorBusinessUserId,
-  message: trimmedMessage,
+  message: sanitizedMessage,
 };

 ...
 return {
   success: true,
   reply: {
-    message: trimmedMessage,
+    message: sanitizedMessage,
     authorBusinessUserId,
     reviewId,
     status: "created",
     statusCode: 201,
     postedAt: new Date().toISOString(),
   },
   metadata: {
     reviewId,
     authorBusinessUserId,
-    messageLength: trimmedMessage.length,
+    messageLength: sanitizedMessage.length,
     requestTime: new Date().toISOString(),
     httpStatus: "201 Created",
   },
 };

Also applies to: 65-69, 83-95, 5-8

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

26-34: Optional: be lenient on reviewId input (trim/case).

End users may paste uppercase/space-padded IDs. Normalize before validation to reduce false negatives.

-    const { reviewId } = this;
+    const { reviewId } = this;
+    const normalizedReviewId = String(reviewId).trim();
 
     // Validate required parameters
-    if (!reviewId) {
+    if (!normalizedReviewId) {
       throw new Error("Review ID is required");
     }
-    if (!validateReviewId(reviewId)) {
+    if (!validateReviewId(normalizedReviewId)) {
       throw new Error("Invalid review ID format");
     }

Outside this file (utils.mjs), consider accepting uppercase hex as well:

-  /^[a-f0-9]{24}$/.test(reviewId)
+  /^[a-f0-9]{24}$/i.test(reviewId)
components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs (1)

49-61: Optional: add a summary on the no-conversation path.

Improves UX in the run log when exiting early.

     if (!conversationId) {
+      $.export("$summary", `No conversation found for product review ${reviewId}`);
       return {
         success: false,
         message: "No conversation found for this product review",
components/trustpilot/common/api-client.mjs (3)

5-16: JSDoc parameter order is outdated.

Document the $ parameter and reflect the actual signature makeRequest($, trustpilotApp, options).

- * @param {object} trustpilotApp - The Trustpilot app instance with auth credentials
- * @param {object} options - Request options
+ * @param {object} $ - Pipedream context (for axios/error handling)
+ * @param {object} trustpilotApp - The Trustpilot app instance with auth credentials
+ * @param {object} options - Request options

92-111: Optional: include Accept header.

Some APIs vary payloads by Accept. Adding Accept: application/json is harmless and can prevent content-negotiation surprises.

   const headers = {
     "Content-Type": "application/json",
+    "Accept": "application/json",
     "User-Agent": "Pipedream/1.0",
   };

54-56: Optional: make private detection more explicit.

url.includes("private") is workable but brittle. Consider checking the endpoint prefix (e.g., endpoint.startsWith("/private/")) or allowing an explicit authMode option.

components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)

53-56: Optional: ensure startDateTime is ISO string.

If lastReviewTime was stored as a number, coerce to ISO before using it in query params.

-      if (lastReviewTime) {
-        fetchParams.startDateTime = lastReviewTime;
-      }
+      if (lastReviewTime) {
+        fetchParams.startDateTime = new Date(lastReviewTime).toISOString();
+      }
components/trustpilot/common/constants.mjs (1)

29-30: Rename CREATE_CONVERSATION_FOR_REVIEW to CREATE_CONVERSATION_FOR_PRODUCT_REVIEW

In components/trustpilot/common/constants.mjs, update:

-  CREATE_CONVERSATION_FOR_REVIEW: "/private/product-reviews/{reviewId}/create-conversation",
+  CREATE_CONVERSATION_FOR_PRODUCT_REVIEW: "/private/product-reviews/{reviewId}/create-conversation",

Then update its usage in components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (change ENDPOINTS.CREATE_CONVERSATION_FOR_REVIEW to ENDPOINTS.CREATE_CONVERSATION_FOR_PRODUCT_REVIEW) to match the naming of other product-review constants.

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

31-36: Constrain or validate star rating input

Free-form "1-5, separated by commas" is error-prone. Either present selectable options or validate with a regex to avoid invalid input (e.g., "6", "a").

-    stars: {
-      type: "string",
+    stars: {
+      type: "string",
       label: "Star Rating",
-      description: "Filter by reviews with a specific star rating. 1-5, separated by commas.",
+      description: "Filter by reviews with a specific star rating. Accepts comma-separated values from 1–5 (e.g., 5 or 4,5).",
+      pattern: "^(?:[1-5])(?:,(?:[1-5]))*$",
       optional: true,
     },

120-131: Date-time props: great detail; consider timezone note

Descriptions are solid (ISO format, implicit 00:00:00). Add a brief note on timezone expectation (UTC vs local) if the API assumes UTC.

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

18-33: Harden async options for businessUnitId (page default + fallback labels)

Guard against undefined page and missing displayName to improve UX.

-      async options({
-        page, query,
-      }) {
+      async options({
+        page = 0, query,
+      } = {}) {
         try {
           if (query === "") {
             // Trustpilot requires a query to be passed in, default to "a" if empty
             query = "a";
           }
 
           const businessUnits = await this.searchBusinessUnits({
             // Trustpilot requires the page to be 1-indexed
             // whereas pipedream is 0-indexed
             page: page + 1,
             query,
           });
 
-          return businessUnits.map((businessUnit) => {
-            const {
-              id, displayName,
-            } = businessUnit;
-
-            return {
-              label: displayName,
-              value: id,
-            };
-          });
+          return businessUnits.map(({ id, displayName, identifyingName }) => ({
+            label: displayName || identifyingName || id,
+            value: id,
+          }));

175-191: Add consistent BU ID validation for product reviews

Service flow validates businessUnitId format; apply the same check here for parity.

     async fetchProductReviews($, params = {}) {
       const {
         businessUnitId,
         page,
         perPage,
         sku,
         language,
         state,
         locale,
       } = params;
 
       // Validate required parameters
       if (!businessUnitId) {
         throw new Error("Business Unit ID is required");
       }
+      if (!validateBusinessUnitId(businessUnitId)) {
+        throw new Error("Invalid business unit ID format");
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d5f6d9e and c2d5ffa.

📒 Files selected for processing (13)
  • components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (2 hunks)
  • components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (2 hunks)
  • components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (2 hunks)
  • components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (2 hunks)
  • components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs (1 hunks)
  • components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (2 hunks)
  • components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (2 hunks)
  • components/trustpilot/common/api-client.mjs (1 hunks)
  • components/trustpilot/common/constants.mjs (1 hunks)
  • components/trustpilot/common/utils.mjs (1 hunks)
  • components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1 hunks)
  • components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1 hunks)
  • components/trustpilot/trustpilot.app.mjs (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-03T14:43:27.512Z
Learnt from: Afstkla
PR: PipedreamHQ/pipedream#18152
File: components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs:42-49
Timestamp: 2025-09-03T14:43:27.512Z
Learning: The Trustpilot SERVICE_REVIEW_BY_ID endpoint returns the review data directly as the response body, not wrapped in a container object like `{ review: {...} }`.

Applied to files:

  • components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs
🧬 Code graph analysis (11)
components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs (3)
components/trustpilot/common/utils.mjs (2)
  • validateReviewId (231-237)
  • buildUrl (51-65)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (1)
  • makeRequest (17-47)
components/trustpilot/actions/reply-to-product-review/reply-to-product-review.mjs (3)
components/trustpilot/common/utils.mjs (2)
  • validateReviewId (231-237)
  • buildUrl (51-65)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (1)
  • makeRequest (17-47)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (3)
components/trustpilot/common/utils.mjs (3)
  • validateReviewId (231-237)
  • buildUrl (51-65)
  • parseServiceReview (157-211)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (2)
  • response (45-45)
  • makeRequest (17-47)
components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (3)
components/trustpilot/common/utils.mjs (2)
  • validateReviewId (231-237)
  • buildUrl (51-65)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (1)
  • makeRequest (17-47)
components/trustpilot/common/api-client.mjs (6)
components/trustpilot/common/utils.mjs (1)
  • formatQueryParams (244-257)
components/trustpilot/common/constants.mjs (2)
  • BASE_URL (1-1)
  • BASE_URL (1-1)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (2)
  • endpoint (38-40)
  • response (43-45)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (2)
  • endpoint (38-40)
  • response (43-45)
components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs (1)
  • endpoint (60-62)
components/trustpilot/trustpilot.app.mjs (7)
  • endpoint (136-138)
  • endpoint (193-195)
  • params (125-125)
  • params (177-185)
  • response (112-118)
  • response (150-153)
  • response (209-212)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (3)
components/trustpilot/common/utils.mjs (3)
  • validateReviewId (231-237)
  • buildUrl (51-65)
  • parseProductReview (107-150)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/api-client.mjs (2)
  • response (45-45)
  • makeRequest (17-47)
components/trustpilot/common/utils.mjs (2)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (1)
  • review (48-48)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (1)
  • review (48-48)
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (3)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1)
  • result (180-200)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1)
  • result (57-57)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)
  • result (59-59)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (3)
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1)
  • result (67-75)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1)
  • result (57-57)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)
  • result (59-59)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (3)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (8)
  • stars (34-34)
  • consumerName (35-35)
  • businessUnit (37-37)
  • lastReviewTime (45-45)
  • fetchParams (50-54)
  • result (57-57)
  • reviews (59-59)
  • latestReviewTime (80-80)
components/trustpilot/trustpilot.app.mjs (3)
  • businessUnit (36-38)
  • reviews (156-156)
  • reviews (215-215)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1)
  • result (180-200)
components/trustpilot/trustpilot.app.mjs (3)
components/trustpilot/common/api-client.mjs (2)
  • response (45-45)
  • makeRequest (17-47)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (12-35)
  • ENDPOINTS (12-35)
components/trustpilot/common/utils.mjs (4)
  • validateBusinessUnitId (218-224)
  • buildUrl (51-65)
  • parseServiceReview (157-211)
  • parseProductReview (107-150)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base
🔇 Additional comments (16)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (1)

37-49: Endpoint construction + parsing look solid.

Good use of ENDPOINTS + buildUrl + parseProductReview with makeRequest.

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

87-123: Conversation creation flow and x-business-user-id header are correct.

Nice multi-step orchestration with clear error on missing conversationId.

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

102-150: Richer, escaped review models look good.

Good field coverage and systematic escapeHtml usage across nested objects.

Also applies to: 152-211

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

42-76: Validation and endpoint usage are correct.

Input checks, URL build, and payload shape match the private reply endpoint.

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

37-49: Endpoint build + parsing look correct.

Building SERVICE_REVIEW_BY_ID with buildUrl and feeding the raw payload to parseServiceReview matches the endpoint that returns the review body directly. LGTM.

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

7-7: Major version bump for prop renames is correct.

Prop surface changed; bumping to 1.0.0 is appropriate for breaking changes.

components/trustpilot/actions/get-conversation-from-product-review/get-conversation-from-product-review.mjs (2)

28-35: Good validation UX with ConfigurationError.

Clear, user-facing errors for missing/invalid reviewId. Nicely done.


36-89: Solid two-step retrieval flow.

Fetching the review first, early-returning when no conversationId, then fetching conversation details is clean and predictable. Using $summary for progress/success is helpful.

components/trustpilot/common/api-client.mjs (1)

17-47: Good: $ is first param and passed to axios with fallback.

This aligns with platform best practices and preserves richer error context.

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

21-25: Ensure REPLY_TO_SERVICE_REVIEW uses POST
Confirm that in components/trustpilot/actions/reply-to-service-review/reply-to-service-review.mjs the HTTP method is explicitly set to POST and that the request body is sent as expected.


14-16: No hard-coded /business-units calls remain in Trustpilot
Only ENDPOINTS.BUSINESS_UNITS is referenced in components/trustpilot/trustpilot.app.mjs; no raw /business-units paths found.


19-20: PUBLIC_REVIEW_BY_ID callers updated — no businessUnitId references found
Our searches didn’t locate any buildUrl calls passing businessUnitId into PUBLIC_REVIEW_BY_ID. Please manually verify all callers to ensure no businessUnitId path-param remains.


34-35: Conversation reply configuration OK
Method POST and request body schema (content, optional integrationId) align with Trustpilot’s API for creating comments.

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

156-176: Destructured defaults are fine, but currently overridden downstream

These defaults are sensible; however, the app method overwrites page/perPage/orderBy/ignoreTagValueCase. After fixing the app, these will apply correctly.


202-205: Summary and return shape look good

Returning the full result and summarizing by reviews.length is consistent with the app method contract.

components/trustpilot/trustpilot.app.mjs (1)

110-121: Search call through makeRequest looks good

Query and page pass-through is clear; returning [] on failure keeps the UI resilient.

@Afstkla Afstkla requested a review from GTFalcao September 5, 2025 09:18
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: 0

♻️ Duplicate comments (2)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)

34-46: Fix pagination: condition checks total accumulated length, so you fetch at most one extra page.

Use the size of the last fetched page to continue, and avoid mutating the input params.

-      // Handle pagination for service reviews
-      while (result.reviews && result.reviews.length === 100) {
-        params.page = (params.page || 1) + 1;
-        const nextResult = await this.trustpilot.fetchServiceReviews($, params);
-        result.reviews = result.reviews.concat(nextResult.reviews || []);
-      }
+      // Handle pagination for service reviews
+      const perPage = params.perPage ?? 100;
+      let page = params.page ?? 1;
+      let all = result.reviews || [];
+      let lastChunkSize = all.length;
+      while (lastChunkSize === perPage) {
+        page += 1;
+        const next = await this.trustpilot.fetchServiceReviews($, { ...params, page });
+        const chunk = next.reviews || [];
+        all = all.concat(chunk);
+        lastChunkSize = chunk.length;
+      }
+      result = { ...result, reviews: all };
components/trustpilot/sources/common/polling.mjs (1)

76-127: Let Axios errors propagate; remove the try/catch wrapper.

Catching and rethrowing loses response metadata (status, data). Allow the framework to surface the original error.

-  async run({ $ }) {
-    try {
+  async run({ $ }) {
       // Get the last review time for filtering new reviews
       const lastReviewTime = this._getLastReviewTime();
@@
-      // Update the last review time for next poll
-      if (latestReviewTime && latestReviewTime !== lastReviewTime) {
-        this._setLastReviewTime(latestReviewTime);
-      }
-
-    } catch (error) {
-      throw new Error(`Failed to fetch reviews: ${error.message}`);
-    }
+      // Update the last review time for next poll
+      if (latestReviewTime && latestReviewTime !== lastReviewTime) {
+        this._setLastReviewTime(latestReviewTime);
+      }
   },
🧹 Nitpick comments (4)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (2)

7-8: Tighten description and verify data fields claimed.

The description reads like marketing copy and asserts inclusion of “customer email” and use of the “private reviews API.” Please confirm both are true for all events; if unsure, tone down the claim.

-  description: "Emit new event when a customer posts a new service review on Trustpilot. This source periodically polls the Trustpilot API to detect new service reviews using the private reviews API for comprehensive coverage. Each event contains the complete review data including star rating, review text, consumer details, business unit info, customer email, and timestamps. Ideal for monitoring overall business reputation, tracking customer satisfaction metrics, and triggering workflows based on review ratings or content.",
+  description: "Emits an event when a new Trustpilot service review is posted. Polls the Trustpilot API and includes key review fields (stars, text, consumer, business unit, timestamps).",

13-19: Align consumerName fallback to displayName then name
Use consumer?.displayName ?? consumer?.name ?? "Anonymous" to match new-product-reviews and avoid dropping an existing name.

- const consumerName = review.consumer?.displayName || "Anonymous";
+ const consumerName = review.consumer?.displayName ?? review.consumer?.name ?? "Anonymous";
components/trustpilot/sources/common/polling.mjs (2)

57-63: Set a default page to complement perPage.

Minor: establish page: 1 in the default params to standardize child behavior unless overridden.

     getFetchParams(_lastReviewTime) {
       return {
         businessUnitId: this.businessUnitId,
-        perPage: 100,
+        perPage: 100,
+        page: 1,
       };
     },

101-116: Compare numeric timestamps to avoid redundant Date/ISO conversions.

Reduces parsing overhead and avoids edge cases with string comparisons.

-      // Track the latest review time
-      let latestReviewTime = lastReviewTime;
+      // Track the latest review time (numeric)
+      const lastTs = lastReviewTime ? Date.parse(lastReviewTime) : 0;
+      let latestTs = lastTs;
@@
-        // Track the latest review time
-        const reviewTime = new Date(review.createdAt).toISOString();
-        if (!latestReviewTime || new Date(reviewTime) > new Date(latestReviewTime)) {
-          latestReviewTime = reviewTime;
-        }
+        // Track the latest review time
+        const reviewTs = Date.parse(review.createdAt);
+        if (Number.isFinite(reviewTs) && reviewTs > latestTs) {
+          latestTs = reviewTs;
+        }
@@
-        this.$emit(review, {
+        this.$emit(review, {
           id: review.id,
           summary: this.generateSummary(review),
-          ts: new Date(review.createdAt).getTime(),
+          ts: Number.isFinite(reviewTs) ? reviewTs : Date.now(),
         });
       }
@@
-      if (latestReviewTime && latestReviewTime !== lastReviewTime) {
-        this._setLastReviewTime(latestReviewTime);
-      }
+      if (latestTs > lastTs) {
+        this._setLastReviewTime(new Date(latestTs).toISOString());
+      }

Also applies to: 119-122

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c2d5ffa and 62924cc.

📒 Files selected for processing (3)
  • components/trustpilot/sources/common/polling.mjs (2 hunks)
  • components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1 hunks)
  • components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs
🧰 Additional context used
🧬 Code graph analysis (2)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (4)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (3)
  • stars (14-14)
  • consumerName (15-15)
  • businessUnit (17-17)
components/trustpilot/trustpilot.app.mjs (3)
  • businessUnit (36-38)
  • params (125-125)
  • params (177-185)
components/trustpilot/sources/common/polling.mjs (2)
  • lastReviewTime (79-79)
  • result (85-85)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1)
  • result (180-200)
components/trustpilot/sources/common/polling.mjs (6)
components/trustpilot/trustpilot.app.mjs (2)
  • reviews (156-156)
  • reviews (215-215)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)
  • result (36-36)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1)
  • result (180-200)
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1)
  • result (67-75)
components/trustpilot/actions/fetch-service-review-by-id/fetch-service-review-by-id.mjs (1)
  • review (48-48)
components/trustpilot/actions/fetch-product-review-by-id/fetch-product-review-by-id.mjs (1)
  • review (48-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: pnpm publish
  • GitHub Check: Lint Code Base
🔇 Additional comments (4)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)

20-33: Drop page addition; defaults already applied
Downstream in trustpilot.app.mjs the spread of params is immediately followed by page: 1, perPage: 20 and orderBy: "createdat.desc", so adding page here is redundant and the orderBy format is already correct.

Likely an incorrect or invalid review comment.

components/trustpilot/sources/common/polling.mjs (3)

27-32: State handling LGTM.

Persisting a single ISO timestamp keeps the poller simple and robust.


33-41: Clear contract for child summary.

The explicit error helps enforce implementation. Good.


42-51: Fetch hook contract LGTM.

The enforced override and clear return shape are appropriate.

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

♻️ Duplicate comments (5)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)

42-54: Pagination logic fix looks good vs prior “total-length” bug.

You now gate continuation on the last page size instead of the accumulated length. This resolves the earlier issue where only one extra page could be fetched.

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

23-30: Page docs text looks good

The page description matches current Trustpilot docs behavior (empty array when exceeding available pages). (developers.trustpilot.com)


178-200: Good: orderBy/pagination are now respected

Passing page/perPage/orderBy through (without being overridden in the app) addresses the earlier issue.

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

125-133: LGTM: preserve user pagination/sort and exclude path param from query

Destructuring with defaults and spreading filters (excluding businessUnitId) fixes the prior override/leak.


221-230: Fix product-reviews pagination: use total and detect next via links array

The private product reviews list returns productReviews, total, and a links array. Reading links.total and links.next is incorrect; compute hasMore by checking for a link with rel === "next".

-      const pagination = {
-        total: response.links?.total || 0,
-        page: queryParams.page || 1,
-        perPage: queryParams.perPage || 20,
-        hasMore: response.links?.next
-          ? true
-          : false,
-      };
+      const pagination = {
+        total: typeof response.total === "number" ? response.total : null,
+        page: queryParams.page || 1,
+        perPage: queryParams.perPage || 20,
+        hasMore: Array.isArray(response.links)
+          ? response.links.some((l) => l?.rel === "next")
+          : false,
+      };

(documentation-apidocumentation.trustpilot.com)

🧹 Nitpick comments (8)
components/trustpilot/common/constants.mjs (1)

7-11: Normalize endpoint key naming for consistency.

Mixing SERVICE_* with PRIVATE_PRODUCT_* invites mistakes. Consider aligning both families (either drop PRIVATE_ or add it for service).

Example:

-  SERVICE_REVIEWS: "/private/business-units/{businessUnitId}/reviews",
-  SERVICE_REVIEW_BY_ID: "/private/reviews/{reviewId}",
-  REPLY_TO_SERVICE_REVIEW: "/private/reviews/{reviewId}/reply",
-  PRIVATE_PRODUCT_REVIEWS: "/private/product-reviews/business-units/{businessUnitId}/reviews",
-  PRIVATE_PRODUCT_REVIEW_BY_ID: "/private/product-reviews/{reviewId}",
+  PRODUCT_REVIEWS: "/private/product-reviews/business-units/{businessUnitId}/reviews",
+  PRODUCT_REVIEW_BY_ID: "/private/product-reviews/{reviewId}",

Apply corresponding symbol renames in the client.

Also applies to: 13-16

components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)

17-23: Tweak summary grammar for singular “star”.

-      return `New ${stars}-star service review by ${consumerName} for ${businessUnit}`;
+      const starLabel = Number(stars) === 1 ? "star" : "stars";
+      return `New ${stars}-${starLabel} service review by ${consumerName} for ${businessUnit}`;
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (3)

14-21: Tweak summary grammar for singular “star”.

-      return `New ${stars}-star product review by ${consumerName} for "${productName}" (${businessUnit})`;
+      const starLabel = Number(stars) === 1 ? "star" : "stars";
+      return `New ${stars}-${starLabel} product review by ${consumerName} for "${productName}" (${businessUnit})`;

49-55: Harden lastReviewTime persistence against NaN.

Guard against invalid inputs before writing to DB.

-      const timeMs = typeof time === "string"
-        ? new Date(time).getTime()
-        : time;
-      this.db.set("lastReviewTime", timeMs);
+      const timeMs = typeof time === "string" ? new Date(time).getTime() : Number(time);
+      if (Number.isFinite(timeMs) && timeMs > 0) {
+        this.db.set("lastReviewTime", timeMs);
+      }

35-44: Skip reviews with invalid createdAt to avoid NaN compare pitfalls.

-      const lastTs = Number(lastReviewTime) || 0;
-      const toMs = (d) => new Date(d).getTime();
-      return lastTs
-        ? reviews.filter((r) => toMs(r.createdAt) > lastTs)
-        : reviews;
+      const lastTs = Number(lastReviewTime) || 0;
+      const toMs = (d) => {
+        const t = new Date(d).getTime();
+        return Number.isFinite(t) ? t : -Infinity;
+      };
+      return lastTs ? reviews.filter((r) => toMs(r.createdAt) > lastTs) : reviews;
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (2)

6-7: Clarify description to reflect private fields and pagination

Note that private Business Unit reviews include consumer email, referenceId, and referralEmail; docs also emphasize pagination via page/perPage. Consider expanding the description for accuracy and user expectation. (developers.trustpilot.com)

-  description: "Get private reviews for a business unit. Response includes customer email and order ID. [See the documentation](https://developers.trustpilot.com/business-units-api#get-private-reviews-for-business-unit)",
+  description: "Get private reviews for a business unit (private endpoint). Response includes consumer email, referenceId, and referralEmail. Supports pagination via page/perPage. [Docs](https://developers.trustpilot.com/business-units-api#get-private-reviews-for-business-unit)",

31-36: Normalize/validate stars to avoid bad inputs

Users may input spaces or invalid values; normalize to a comma-separated list of 1–5 before sending.

   async run({ $ }) {
     const {
       businessUnitId,
-      stars,
+      stars,
       language,
       page = 1,
       internalLocationId,
       perPage = 20,
       orderBy = "createdat.desc",
       tagGroup,
@@
-      // Use the shared method from the app
+      // Normalize stars: keep only digits 1–5, comma-separated
+      const starsNormalized = typeof stars === "string"
+        ? stars
+          .split(",")
+          .map((s) => s.trim())
+          .filter((v) => /^[1-5]$/.test(v))
+          .join(",")
+        : stars;
+
+      // Use the shared method from the app
       const result = await this.trustpilot.fetchServiceReviews($, {
         businessUnitId,
-        stars,
+        stars: starsNormalized,
         language,
         page,
         internalLocationId,
         perPage,
         orderBy,
components/trustpilot/trustpilot.app.mjs (1)

195-197: Optional: validate Business Unit ID here too

Mirror the validation used in fetchServiceReviews to fail fast on malformed IDs.

       if (!businessUnitId) {
         throw new Error("Business Unit ID is required");
       }
+      if (!validateBusinessUnitId(businessUnitId)) {
+        throw new Error("Invalid business unit ID format");
+      }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 62924cc and d84333b.

📒 Files selected for processing (5)
  • components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (2 hunks)
  • components/trustpilot/common/constants.mjs (1 hunks)
  • components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1 hunks)
  • components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1 hunks)
  • components/trustpilot/trustpilot.app.mjs (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/trustpilot/trustpilot.app.mjs (2)
components/trustpilot/common/api-client.mjs (2)
  • response (45-45)
  • makeRequest (17-47)
components/trustpilot/common/utils.mjs (4)
  • validateBusinessUnitId (218-224)
  • buildUrl (51-65)
  • parseServiceReview (157-211)
  • parseProductReview (107-150)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Lint Code Base
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
🔇 Additional comments (2)
components/trustpilot/common/constants.mjs (1)

5-5: No lingering endpoint references
Confirmed no callers reference dropped constants (BUSINESS_UNIT_BY_ID, public review endpoints, EMAIL_INVITATIONS, WEBHOOKS, CONVERSATIONS). The only usage of ENDPOINTS.BUSINESS_UNITS is in components/trustpilot/trustpilot.app.mjs with the expected query and page params for /business-units/search.

components/trustpilot/trustpilot.app.mjs (1)

184-192: Include productUrl or remove the prop (private product reviews)

You define a productUrl prop but don’t pass it to fetchProductReviews. Also, Trustpilot’s private product reviews docs list sku, state, language, locale, page, perPage — productUrl isn’t documented there (it is for some public endpoints). Confirm support; if supported, include it; if not, remove the prop to avoid confusion.

-      const {
+      const {
         businessUnitId,
-        page,
-        perPage,
-        sku,
+        page,
+        perPage,
+        sku,
+        productUrl,
         language,
         state,
         locale,
       } = params;

If supported, add to query:

-      const queryParams = {
-        sku,
+      const queryParams = {
+        sku,
+        productUrl,
         state,
         locale,
         perPage,
         page,
-        includeReportedReviews: false,
         language,
       };

Docs references: private product reviews parameters (no productUrl listed), and private BU reviews response shape for context. (documentation-apidocumentation.trustpilot.com)

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

♻️ Duplicate comments (2)
components/trustpilot/trustpilot.app.mjs (2)

162-169: Service-reviews pagination assumes a non-existent pagination block

Use links-based paging; total may be absent. Current logic can stall paging.

-      const pagination = {
-        total: response.pagination?.total || 0,
-        page: response.pagination?.page || queryParams.page,
-        perPage: response.pagination?.perPage || queryParams.perPage,
-        hasMore: response.pagination?.hasMore || false,
-      };
+      const pagination = {
+        total: typeof response.total === "number" ? response.total : null,
+        page: queryParams.page,
+        perPage: queryParams.perPage,
+        hasMore: Array.isArray(response.links)
+          ? response.links.some((l) => l?.rel === "next")
+          : false,
+      };

205-213: Remove unsupported includeReportedReviews from product reviews

Not listed for the private product reviews list; can trigger 400s.

       const queryParams = {
         sku,
         state,
         locale,
         perPage,
         page,
-        includeReportedReviews: false,
         language,
       };
🧹 Nitpick comments (6)
components/trustpilot/trustpilot.app.mjs (4)

19-33: Harden options() defaults: trim/seed query and guard page to avoid NaN

Prevents empty/whitespace queries and undefined page from producing invalid requests.

-      async options({
-        page, query,
-      }) {
+      async options({ page = 0, query } = {}) {
         try {
-          if (query === "") {
-            // Trustpilot requires a query to be passed in, default to "a" if empty
-            query = "a";
-          }
+          // Trustpilot requires a non-empty query; seed with "a" if blank
+          query = (typeof query === "string" ? query.trim() : "");
+          if (!query) query = "a";
 
           const businessUnits = await this.searchBusinessUnits({
-            // Trustpilot requires the page to be 1-indexed
-            // whereas pipedream is 0-indexed
-            page: page + 1,
+            // Trustpilot is 1-indexed; PD is 0-indexed
+            page: Number.isInteger(page) && page >= 0 ? page + 1 : 1,
             query,
           });

91-100: State says default is Published but no default is set

Set the default explicitly for consistency with the description.

     state: {
       type: "string",
       label: "State",
       description: "Which reviews to retrieve according to their review state. Default is Published.",
       options: [
         "published",
         "unpublished",
       ],
+      default: "published",
       optional: true,
     },

183-193: Default page/perPage in fetchProductReviews to robust values

Ensures stable behavior when callers don’t pass these props.

-      const {
-        businessUnitId,
-        page,
-        perPage,
+      const {
+        businessUnitId,
+        page = 1,
+        perPage = 20,
         sku,
         language,
         state,
         locale,
       } = params;

221-228: Avoid misleading 0 total; prefer null when total is absent

Matches docs and prevents consumers from misinterpreting “0” as authoritative.

-      const pagination = {
-        total: response.total || 0,
+      const pagination = {
+        total: typeof response.total === "number" ? response.total : null,
         page: queryParams.page || 1,
         perPage: queryParams.perPage || 20,
         hasMore: response.links?.some((l) => l.rel === "next") || false,
       };
components/trustpilot/sources/common/polling.mjs (2)

6-7: Doc says “deduplication” but base doesn’t dedupe by itself. Clarify to time-based semantics.

- * Provides common functionality for polling Trustpilot API endpoints
- * and emitting new events with deduplication.
+ * Provides common functionality for polling Trustpilot API endpoints
+ * and emitting new events using time-based deduplication via `lastReviewTime`.
+ * Child classes must either:
+ *  - pass a time filter in `getFetchParams(lastReviewTime)`, or
+ *  - implement `filterNewReviews(reviews, lastReviewTime)`.

87-90: Use Pipedream logger instead of console for consistency.

-  console.log("No reviews found");
+  ($.logger?.info ?? console.log)("No reviews found");
...
-  console.log("No new reviews since last poll");
+  ($.logger?.info ?? console.log)("No new reviews since last poll");

Also applies to: 95-98

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d84333b and 4ed892e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • components/trustpilot/sources/common/polling.mjs (2 hunks)
  • components/trustpilot/trustpilot.app.mjs (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/trustpilot/trustpilot.app.mjs (4)
components/trustpilot/common/api-client.mjs (2)
  • response (45-45)
  • makeRequest (17-47)
components/trustpilot/common/constants.mjs (2)
  • ENDPOINTS (3-20)
  • ENDPOINTS (3-20)
components/trustpilot/common/utils.mjs (4)
  • validateBusinessUnitId (218-224)
  • buildUrl (51-65)
  • parseServiceReview (157-211)
  • parseProductReview (107-150)
components/trustpilot/sources/common/polling.mjs (1)
  • reviews (85-85)
components/trustpilot/sources/common/polling.mjs (4)
components/trustpilot/trustpilot.app.mjs (2)
  • reviews (163-163)
  • reviews (222-222)
components/trustpilot/actions/fetch-service-reviews/fetch-service-reviews.mjs (1)
  • result (180-200)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)
  • result (40-40)
components/trustpilot/actions/fetch-product-reviews/fetch-product-reviews.mjs (1)
  • result (67-75)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base
🔇 Additional comments (2)
components/trustpilot/trustpilot.app.mjs (1)

123-154: Nice: user pagination/sort preserved and businessUnitId omitted from query

This fixes prior overwriting/leakage and aligns with best practices.

components/trustpilot/sources/common/polling.mjs (1)

118-121: LGTM: state update after emission ensures at-least-once delivery.

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

♻️ Duplicate comments (6)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1)

38-57: Refactor pagination: don’t mutate params, base loop on last page size, and cap by MAX_LIMIT.

Current loop mutates params and uses while(true). Align with the robust pattern used in product reviews.

-      // Use the shared method from the app directly with pagination support
-      let result = await this.trustpilot.fetchServiceReviews($, params);
-
-      // Handle pagination for service reviews
-      if (result.reviews && result.reviews.length === DEFAULT_LIMIT) {
-        while (true) {
-          params.page = (params.page || 1) + 1;
-          const nextResult = await this.trustpilot.fetchServiceReviews($, params);
-          result.reviews = result.reviews.concat(nextResult.reviews || []);
-
-          if (!nextResult.reviews ||
-            nextResult.reviews.length < DEFAULT_LIMIT ||
-            result.reviews.length >= MAX_LIMIT) {
-            break;
-          }
-        }
-      }
-
-      return result;
+      const perPage = params.perPage ?? DEFAULT_LIMIT;
+      let page = params.page ?? 1;
+      let result = await this.trustpilot.fetchServiceReviews($, { ...params, page });
+      let all = Array.isArray(result.reviews) ? result.reviews : [];
+      let lastPageSize = all.length;
+      while (lastPageSize === perPage && all.length < MAX_LIMIT) {
+        page += 1;
+        const next = await this.trustpilot.fetchServiceReviews($, { ...params, page });
+        const chunk = Array.isArray(next.reviews) ? next.reviews : [];
+        if (chunk.length === 0) break;
+        all = all.concat(chunk);
+        lastPageSize = chunk.length;
+        result = next; // preserve latest metadata if needed
+      }
+      result.reviews = all.slice(0, MAX_LIMIT);
+      return result;
components/trustpilot/sources/common/polling.mjs (1)

128-133: Add missing createdAt in parseServiceReview return
parseServiceReview (components/trustpilot/common/utils.mjs) currently returns only { links, id }, omitting createdAt and causing createdMs in polling.mjs to be undefined. Add createdAt: review.createdAt to its returned object.

components/trustpilot/trustpilot.app.mjs (4)

119-127: Good: preserves caller pagination/sort and omits businessUnitId from query

Destructuring with defaults and filtering out businessUnitId addresses earlier bugs. Looks correct.

Also applies to: 141-148


194-200: Good: validates businessUnitId format for product reviews

Fast-fail guard mirrors service reviews. Nice.


223-230: Product reviews pagination logic looks correct

Uses top-level total and checks links for rel === "next". This aligns with the documented response.


156-169: Fix hasMore detection: rel should be "next", not "next-page"

Trustpilot links use rel: "next". Using "next-page" will stall pagination.

-        hasMore: Array.isArray(response.links)
-          ? response.links.some((l) => l?.rel === "next-page")
-          : false,
+        hasMore: Array.isArray(response.links)
+          ? response.links.some((l) => l?.rel === "next")
+          : false,
🧹 Nitpick comments (7)
components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (2)

17-23: Harden summary fallbacks (support both displayName and name).

Service and product parsers may emit either displayName or name. Use both before defaulting.

-      const consumerName = review.consumer?.displayName || "Anonymous";
+      const consumerName = review.consumer?.displayName
+        || review.consumer?.name
+        || "Anonymous";

24-36: Initialize page in params for deterministic paging.

Explicitly set page: 1 to avoid relying on server defaults.

       const params = {
         businessUnitId: this.businessUnitId,
         perPage: DEFAULT_LIMIT,
         orderBy: "createdat.desc",
+        page: 1,
       };
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1)

17-24: Stars fallback: support rating if stars absent.

Some parsers/flows emit rating. Add a nullish-coalescing fallback.

-      const stars = review.stars || "N/A";
+      const stars = review.stars ?? review.rating ?? "N/A";
components/trustpilot/sources/common/polling.mjs (2)

56-66: Doc: accept string | number for time.

Children (e.g., product reviews) use numeric ms. Reflect this in JSDoc.

-     * @param {string} _lastReviewTime - ISO timestamp of last review
+     * @param {string|number} _lastReviewTime - Last review time (ISO string or ms)

97-99: Use $.logger for consistency.

See diff in the previous comment replacing console.log with $.logger?.info.

Also applies to: 105-107

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

19-33: Harden async options: default page and avoid mutating query

Guard against undefined/negative page and sanitize query locally. Also pass $ through so downstream requests can use it.

Apply:

-      async options({
-        page, query,
-      }) {
+      async options({ page = 0, query, $ }) {
         try {
-          if (query === "") {
-            // Trustpilot requires a query to be passed in, default to "a" if empty
-            query = "a";
-          }
+          const q = (query || "").trim() || "a"; // Trustpilot requires non-empty query
           const businessUnits = await this.searchBusinessUnits(
-            {
-            // Trustpilot requires the page to be 1-indexed
-            // whereas pipedream is 0-indexed
-            page: page + 1,
-            query,
-          });
+            $, {
+              // Trustpilot pages are 1-indexed; Pipedream is 0-indexed
+              page: Math.max(1, Number(page) + 1),
+              query: q,
+            },
+          );

Also applies to: 35-44


85-94: Set a default to match the description (“Default is Published.”)

The prop says default is Published but no default is set.

     state: {
       type: "string",
       label: "State",
       description: "Which reviews to retrieve according to their review state. Default is Published.",
       options: [
         "published",
         "unpublished",
       ],
+      default: "published",
       optional: true,
     },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed892e and f306eda.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • components/trustpilot/sources/common/polling.mjs (2 hunks)
  • components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (1 hunks)
  • components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs (1 hunks)
  • components/trustpilot/trustpilot.app.mjs (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Lint Code Base
  • GitHub Check: pnpm publish
🔇 Additional comments (5)
components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs (3)

25-33: LGTM on fetch params.

Clear defaults with explicit paging. Matches the contract.


34-68: LGTM on robust pagination.

Good use of perPage, last-page-size loop, and MAX_LIMIT cap.


69-78: Client-side filter looks good.

Numeric comparison avoids string/timezone pitfalls.

components/trustpilot/sources/common/polling.mjs (2)

28-33: LGTM: BU-namespaced state.

Prevents cross-configuration data mixups.


69-83: Default filter is sensible.

Numeric parse with strict > avoids duplicate emissions.

Comment on lines +86 to +101
async run({ $ }) {
// Get the last review time for filtering new reviews
const lastReviewTime = this._getLastReviewTime();

// Get fetch parameters from child class
const fetchParams = this.getFetchParams(lastReviewTime);

// Fetch reviews using child class method
const result = await this.fetchReviews($, fetchParams);
const reviews = result.reviews || [];

if (!reviews.length) {
console.log("No reviews found");
return;
}

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

Critical: mixed string/number handling can stall lastReviewTime updates.

Date.parse(latestReviewTime) breaks when latestReviewTime is a number (product flow), preventing advancement. Normalize to numbers for comparison, then persist ISO.

   async run({ $ }) {
     // Get the last review time for filtering new reviews
-    const lastReviewTime = this._getLastReviewTime();
+    const lastReviewTime = this._getLastReviewTime();
+    const lastMsRaw = typeof lastReviewTime === "number"
+      ? lastReviewTime
+      : Date.parse(lastReviewTime);
+    const lastMs = Number.isFinite(lastMsRaw) ? lastMsRaw : 0;

@@
-    if (!reviews.length) {
-      console.log("No reviews found");
+    if (!reviews.length) {
+      ($.logger?.info ?? console.log)("No reviews found");
       return;
     }
@@
-    if (!newReviews.length) {
-      console.log("No new reviews since last poll");
+    if (!newReviews.length) {
+      ($.logger?.info ?? console.log)("No new reviews since last poll");
       return;
     }
 
     // Track the latest review time
-    let latestReviewTime = lastReviewTime;
+    let latestMs = lastMs;
 
     for (const review of newReviews) {
       // Track the latest review time
       const createdMs = Date.parse(review?.createdAt);
       if (!Number.isFinite(createdMs)) {
         ($.logger?.warn ?? console.warn)("Skipping review with invalid createdAt", {
           id: review?.id,
           createdAt: review?.createdAt,
         });
         continue;
       }
-      const reviewTime = new Date(createdMs).toISOString();
-      if (!latestReviewTime || createdMs > Date.parse(latestReviewTime)) {
-        latestReviewTime = reviewTime;
-      }
+      if (createdMs > latestMs) latestMs = createdMs;
 
       // Emit the review with unique ID and summary
       this.$emit(review, {
         id: review.id,
         summary: this.generateSummary(review),
         ts: createdMs,
       });
     }
 
     // Update the last review time for next poll
-    if (latestReviewTime && latestReviewTime !== lastReviewTime) {
-      this._setLastReviewTime(latestReviewTime);
+    if (latestMs && latestMs !== lastMs) {
+      this._setLastReviewTime(new Date(latestMs).toISOString());
     }
   },

Also applies to: 105-109, 110-139

Comment on lines +79 to +88
_getLastReviewTime() {
// Product reviews store timestamp as number (ms), others store as ISO string
return this.db.get("lastReviewTime");
},
_setLastReviewTime(time) {
// Store as number for product reviews to match existing behavior
const timeMs = typeof time === "string"
? new Date(time).getTime()
: time;
this.db.set("lastReviewTime", timeMs);
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

Bug: lastReviewTime not namespaced by Business Unit (cross-BU bleed).

Overriding the base methods drops the BU namespace, causing data loss when users switch BUs.

-    _getLastReviewTime() {
-      // Product reviews store timestamp as number (ms), others store as ISO string
-      return this.db.get("lastReviewTime");
-    },
-    _setLastReviewTime(time) {
-      // Store as number for product reviews to match existing behavior
-      const timeMs = typeof time === "string"
-        ? new Date(time).getTime()
-        : time;
-      this.db.set("lastReviewTime", timeMs);
-    },
+    _getLastReviewTime() {
+      // Product reviews: store as number (ms), namespaced by BU
+      return this.db.get(`lastReviewTime:${this.businessUnitId}`);
+    },
+    _setLastReviewTime(time) {
+      const timeMs = typeof time === "string" ? new Date(time).getTime() : time;
+      this.db.set(`lastReviewTime:${this.businessUnitId}`, timeMs);
+    },
📝 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
_getLastReviewTime() {
// Product reviews store timestamp as number (ms), others store as ISO string
return this.db.get("lastReviewTime");
},
_setLastReviewTime(time) {
// Store as number for product reviews to match existing behavior
const timeMs = typeof time === "string"
? new Date(time).getTime()
: time;
this.db.set("lastReviewTime", timeMs);
_getLastReviewTime() {
// Product reviews: store as number (ms), namespaced by BU
return this.db.get(`lastReviewTime:${this.businessUnitId}`);
},
_setLastReviewTime(time) {
const timeMs = typeof time === "string" ? new Date(time).getTime() : time;
this.db.set(`lastReviewTime:${this.businessUnitId}`, timeMs);
},
🤖 Prompt for AI Agents
In components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs
around lines 79–88, the overridden _getLastReviewTime/_setLastReviewTime drop
the Business Unit (BU) namespace causing cross-BU data bleed; change both
methods to read/write the lastReviewTime under a BU-scoped key (e.g. prepend or
otherwise include this.businessUnit or this.businessUnitId in the key used with
this.db.get/this.db.set) and keep the existing timestamp-to-ms conversion logic
in _setLastReviewTime.

Comment on lines 103 to 115
async searchBusinessUnits({
query = "", limit = DEFAULT_LIMIT, offset = 0,
query = "a", page = 1,
} = {}) {
const response = await this._makeRequest({
const response = await makeRequest(this, this, {
endpoint: ENDPOINTS.BUSINESS_UNITS,
params: {
query,
limit,
offset,
page,
},
});

return response.businessUnits?.map(parseBusinessUnit) || [];
return response.businessUnits || [];
},
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

Fix makeRequest call: passing this as $ is likely wrong

Other methods use makeRequest($, this, …). Here it’s makeRequest(this, this, …), which can break HTTP calls (missing $.http/axios). Accept $ and pass it through.

-    async searchBusinessUnits({
-      query = "a", page = 1,
-    } = {}) {
-      const response = await makeRequest(this, this, {
+    async searchBusinessUnits($, { query = "a", page = 1 } = {}) {
+      const response = await makeRequest($, this, {
         endpoint: ENDPOINTS.BUSINESS_UNITS,
         params: {
           query,
           page,
         },
       });

Run to find any remaining inconsistent invocations:


🏁 Script executed:

#!/bin/bash
rg -nP --type=js -C2 '\bmakeRequest\s*\(' components/trustpilot

Length of output: 65


🏁 Script executed:

#!/bin/bash
# List all async method signatures in trustpilot.app.mjs
rg -nP -n -C3 'async\s+\w+' -g 'components/trustpilot/*.mjs'

# List all makeRequest calls in trustpilot.app.mjs
rg -nP -n -C3 'makeRequest' -g 'components/trustpilot/*.mjs'

Length of output: 3895


Fix makeRequest invocation in searchBusinessUnits
In components/trustpilot/trustpilot.app.mjs, update the searchBusinessUnits method to accept $ and call makeRequest($, this, …) so the correct HTTP client is used.

- async searchBusinessUnits({ query = "a", page = 1 } = {}) {
-   const response = await makeRequest(this, this, {
+ async searchBusinessUnits($, { query = "a", page = 1 } = {}) {
+   const response = await makeRequest($, this, {
📝 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 searchBusinessUnits({
query = "", limit = DEFAULT_LIMIT, offset = 0,
query = "a", page = 1,
} = {}) {
const response = await this._makeRequest({
const response = await makeRequest(this, this, {
endpoint: ENDPOINTS.BUSINESS_UNITS,
params: {
query,
limit,
offset,
page,
},
});
return response.businessUnits?.map(parseBusinessUnit) || [];
return response.businessUnits || [];
},
async searchBusinessUnits($, { query = "a", page = 1 } = {}) {
const response = await makeRequest($, this, {
endpoint: ENDPOINTS.BUSINESS_UNITS,
params: {
query,
page,
},
});
return response.businessUnits || [];
},
🤖 Prompt for AI Agents
In components/trustpilot/trustpilot.app.mjs around lines 103 to 115, update the
searchBusinessUnits method signature to accept the HTTP client parameter $ (e.g.
async searchBusinessUnits($, { query = "a", page = 1 } = {})), and change the
makeRequest call to pass $ as the first argument (makeRequest($, this, {
endpoint: ENDPOINTS.BUSINESS_UNITS, params: { query, page } })). This ensures
the correct HTTP client is used while keeping the default params behavior.

Copy link
Collaborator

@GTFalcao GTFalcao left a comment

Choose a reason for hiding this comment

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

Moving the issue forward to QA. I left one comment that may not need to be acted upon, just checked.

requestTime: new Date().toISOString(),
},
};
} catch (error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's still a few try/catch blocks being added that may not be desirable (it's usually preferable to let platform axios handle the error and display the appropriate info in the UI).

If they are intentional, just reply to this confirming so, no problem

@GTFalcao GTFalcao moved this from In Review to Ready for QA in Component (Source and Action) Backlog Sep 8, 2025
@vunguyenhung vunguyenhung moved this from Ready for QA to Ready for Release in Component (Source and Action) Backlog Sep 9, 2025
@vunguyenhung
Copy link
Collaborator

Hi everyone, all test cases are passed! Ready for release!

Test report
https://vunguyenhung.notion.site/trustpilot-fixes-268bf548bb5e81b4b4d7ea37c7337cf3

@GTFalcao GTFalcao merged commit 69eb39d into PipedreamHQ:master Sep 17, 2025
10 checks passed
@github-project-automation github-project-automation bot moved this from Ready for Release to Done in Component (Source and Action) Backlog Sep 17, 2025
sergio-eliot-rodriguez pushed a commit to sergio-eliot-rodriguez/sergio_wong_does_pipedream that referenced this pull request Sep 21, 2025
* trustpilot fixes

* more fixes

* update versions

* more version updates

* fixes

* Bump all Trustpilot actions to version 0.1.0

Major improvements and API updates across all actions:
- Enhanced private API support with proper authentication
- Improved parameter handling and validation
- Better error handling and response structures
- Added new conversation flow for product reviews
- Fixed endpoint URLs to match latest API documentation
- Streamlined request/response processing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>

* up version and clean up sources

* merge

* fix business ID

* delete temp action

* Update components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* comments

* Pagination

* fixes

* comments

* missed some `$`'s

* unduplicated

* more fixes

* final comments

* more comments

* .

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
vunguyenhung added a commit that referenced this pull request Sep 24, 2025
* Leonardo AI components

* added unzoom image action

* fixing link errors

* more lint fixes

* Merging pull request #18359

* fix: pagination prop and params struct

* fix: no need for paginate here

* chore: update version

* chore: cleanup

* chore: update package

* feat: allow raw response

* chore: bump package

* fix: buffer response instead

* Update components/google_drive/actions/download-file/download-file.mjs

Co-authored-by: Jorge Cortes <[email protected]>

* versions

* pnpm-lock.yaml

* pnpm-lock.yaml

* pnpm-lock.yaml

* feat: add content selector

* chore: bump package

* fix: comments

* chore: bump versions

* chore: fix versions

* fixes: QA fixes

* feat: add cursor to req

* package.json

---------

Co-authored-by: joao <[email protected]>
Co-authored-by: joaocoform <[email protected]>
Co-authored-by: Jorge Cortes <[email protected]>
Co-authored-by: Michelle Bergeron <[email protected]>
Co-authored-by: Luan Cazarine <[email protected]>

* Merging pull request #18361

* update siteId prop

* pnpm-lock.yaml

* package.json version

* Google Sheets - update row refresh fields  (#18369)

* change prop order and refresh fields

* bump package.json

* Pipedrive - fix app name (#18370)

* use pipedriveApp instead of app

* bump package.json

* Pipedrive - pipelineId integer (#18372)

* pipelineId - integer

* bump versions

* Adding app scaffolding for lightspeed_ecom_c_series

* Adding app scaffolding for financial_data

* Adding app scaffolding for microsoft_authenticator

* Merging pull request #18345

* updates

* versions

* versions

* Merging pull request #18368

* updates

* remove console.log

* versions

* Coinbase Developer Platform - New Wallet Event (#18342)

* new component

* pnpm-lock.yaml

* updates

* updates

* Hubspot - update search-crm (#18360)

* update search-crm

* limit results to one page

* update version

* package.json version

* Merging pull request #18347

* widget props

* fix version

* Adding app scaffolding for rundeck

* Merging pull request #18378

* Update Taiga component with new actions and sources

- Bump version to 0.1.0 in package.json and add dependency on @pipedream/platform.
- Introduce new actions for creating, updating, and deleting issues, tasks, and user stories.
- Add sources for tracking changes and deletions of issues and tasks.
- Implement utility functions for parsing and cleaning objects in common/utils.mjs.
- Enhance prop definitions for better integration with Taiga API.

* pnpm update

* Refactor Taiga actions to utilize parseObject utility

- Added parseObject utility for tags, watchers, and points in update-issue, update-task, and update-userstory actions.
- Removed the update-project action as it is no longer needed.
- Enhanced base source to include secret key validation for webhook security.

* Merging pull request #18382

* add testSources prop

* pnpm-lock.yaml

* fix

* Merging pull request #18323

* Added actions

* Added actions

* Added actions

* Merging pull request #18377

* Added actions

* Update components/weaviate/actions/create-class/create-class.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: Luan Cazarine <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Merging pull request #18376

* Adding app scaffolding for etrusted

* Adding app scaffolding for intelliflo_office

* Adding app scaffolding for thoughtspot

* Adding app scaffolding for kordiam

* Adding app scaffolding for ticketsauce

* trustpilot fixes (#18152)

* trustpilot fixes

* more fixes

* update versions

* more version updates

* fixes

* Bump all Trustpilot actions to version 0.1.0

Major improvements and API updates across all actions:
- Enhanced private API support with proper authentication
- Improved parameter handling and validation
- Better error handling and response structures
- Added new conversation flow for product reviews
- Fixed endpoint URLs to match latest API documentation
- Streamlined request/response processing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>

* up version and clean up sources

* merge

* fix business ID

* delete temp action

* Update components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/trustpilot/sources/new-product-reviews/new-product-reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update components/trustpilot/sources/new-service-reviews/new-service-reviews.mjs

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* comments

* Pagination

* fixes

* comments

* missed some `$`'s

* unduplicated

* more fixes

* final comments

* more comments

* .

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Adding app scaffolding for peekalink

* 18314 twilio (#18350)

* Update Twilio component versions and dependencies

- Update Twilio Send Message action adding detailed description for 'from' prop and refactoring phone number validation logic.
- Incremented action versions for several Twilio actions.

* pnpm update

* Updating LinkedIn API version (#18399)

* Merging pull request #18394

* Databricks API - Jobs action components (#18371)

* Notion property building improvements (#18381)

* validate property types

* versions

* Google Business - add debug log (#18407)

* add debug log

* bump versions

* Notion API Key - update @pipedream/notion version (#18409)

* update @pipedream/notion dependency version

* pnpm-lock.yaml

* Adding app scaffolding for reduct_video

* Adding app scaffolding for shopware

* Adding app scaffolding for instamojo

* Hubspot - bug fix to sources w/ property changes (#18379)

* updates

* versions

* Google sheets type fix (#18411)

* Fixing worksheetId prop type from string to integer

* Version bumps

---------

Co-authored-by: Leo Vu <[email protected]>

* Merging pull request #18393

* new components

* remove console.log

* versions

* update

* Merging pull request #18408

* 403 error message

* versions

* update

* Merging pull request #18419

* Changes per PR Review

* Removes leonardo_ai_actions.mdc not indented for merging

* synced lockfile after install

* fully lock form-data for leonardo_ai

* conflict solving

* lint fixes

* Chipped down Readme, implemented async options in gen motion

---------

Co-authored-by: jocarino <[email protected]>
Co-authored-by: joao <[email protected]>
Co-authored-by: joaocoform <[email protected]>
Co-authored-by: Jorge Cortes <[email protected]>
Co-authored-by: Michelle Bergeron <[email protected]>
Co-authored-by: Luan Cazarine <[email protected]>
Co-authored-by: michelle0927 <[email protected]>
Co-authored-by: Andrew Chuang <[email protected]>
Co-authored-by: danhsiung <[email protected]>
Co-authored-by: Lucas Caresia <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Job <[email protected]>
Co-authored-by: Claude <[email protected]>
Co-authored-by: Guilherme Falcão <[email protected]>
Co-authored-by: Leo Vu <[email protected]>
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
Development

Successfully merging this pull request may close these issues.

5 participants