Skip to content

DataForSEO new components #18095

New issue

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

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

Already on GitHub? Sign in to your account

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

DataForSEO new components #18095

wants to merge 9 commits into from

Conversation

michelle0927
Copy link
Collaborator

@michelle0927 michelle0927 commented Aug 18, 2025

Resolves #18048

The issue requests adding new components for each endpoint in the DataForSEO API. This would amount to over 350 potential new components. This PR adds 40 new components aimed at covering most use cases.

Summary by CodeRabbit

  • New Features

    • Dozens of new DataForSEO actions: expanded SERP (Google/Bing/Yahoo, Images, News, top/historical), Google Ads (status, locations, languages, search volume, keywords, ad traffic, completed tasks), domain analytics, content analysis, business listings & reviews (GMB, Trustpilot, TripAdvisor, Trustpilot, TripAdvisor enhancements), app store searches/reviews, and bulk traffic analytics.
  • Refactor

    • DataForSEO app surface expanded; input types adjusted (limit → integer, location selector enhancements including TripAdvisor locations).
  • Chores

    • Multiple action version bumps and package version updated.

Copy link

vercel bot commented Aug 18, 2025

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

3 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
docs-v2 Ignored Ignored Preview Aug 21, 2025 3:26pm
pipedream-docs Ignored Ignored Aug 21, 2025 3:26pm
pipedream-docs-redirect-do-not-edit Ignored Ignored Aug 21, 2025 3:26pm

Copy link
Contributor

coderabbitai bot commented Aug 18, 2025

Walkthrough

Added many new DataForSEO action modules and expanded the DataForSEO app client with numerous new methods; adjusted prop types (limit → integer, locationCode → integer), removed async from several internal methods, and applied multiple version bumps across actions and package.

Changes

Cohort / File(s) Summary
App client expansion & refactor
components/dataforseo/dataforseo.app.mjs
Added many new public methods (keyword data, DataForSEOLabs, Google Ads, SERP, domain analytics, content, apps, reviews, listings, bulk analytics); added getTripadvisorLocations and tripAdvisorLocationCode prop; changed limit and locationCode types; removed async from several internal methods including _makeRequest.
New actions — apps, stores & reviews
components/dataforseo/actions/get-app-*/*, .../get-app-store-search/*, .../get-google-play-search/*, .../get-app-reviews-summary/*, .../get-tripadvisor-reviews/*, .../get-trustpilot-reviews/*, .../get-google-reviews/*
Added app-store, app-intersection, app-reviews-summary, and multiple reviews/listings actions; support postback/wait patterns where applicable; standard two-tier response validation and summaries.
New actions — SERP, search & historical
components/dataforseo/actions/get-google-*-results/*, .../get-bing-organic-results/*, .../get-yahoo-organic-results/*, .../get-top-serp-results/*, .../get-historical-serp-data/*, .../get-google-images-results/*, .../get-google-news-results/*
Added multi-engine SERP/search actions with keyword/location/language props and optional depth/maxCrawlPages/date ranges; call client methods and validate responses.
New actions — Google Ads & completed tasks
components/dataforseo/actions/get-google-ads-*/**
Added Google Ads endpoints: status, locations, languages, search volume, keywords for keywords/site (and completed tasks), ad traffic (and completed tasks); follow standard request/validation/summary pattern.
New actions — keyword-data & utilities
components/dataforseo/actions/get-keyword-data-id-list/*, .../get-keyword-data-errors/*, .../get-keyword-ideas-live/*, .../get-keyword-suggestions/*, .../get-keyword-difficulty/*
Added keyword ID list, errors (with date validation), live ideas, suggestions, and difficulty actions; include input validation (date validators where applicable), payload building, and response checks.
New actions — domain analytics & intersections
components/dataforseo/actions/get-domain-*/**
Added domain-related actions: rank overview, competitor domains, domain keywords, domain intersection, whois overview, technologies, etc.; use propDefinitions and two-tier error handling.
New actions — content analysis & citations
components/dataforseo/actions/get-content-*/*
Added content citations, content summary, sentiment analysis actions; standard payload mapping and response checks.
New actions — bulk & traffic analytics
components/dataforseo/actions/get-bulk-traffic-analytics/*
Added bulk traffic analytics action; builds targets payload, validates response, exports count-based summary.
Version bumps only (no logic change)
components/dataforseo/actions/* (multiple)
Multiple actions had only version increments (e.g., get-backlinks*, get-bulk-*, get-domain-pages-summary, parse-page-content, search-business-listings, get-ranked-keywords, get-keyword-difficulty, etc.).
Utilities
components/dataforseo/common/utils.mjs
Added parseArray(value) utility: returns [] for falsy, parses JSON for string input, otherwise returns value as-is.
Package metadata
components/dataforseo/package.json
Version bumped 0.2.00.3.0.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Action as Pipedream Action
  participant App as DataForSEO App Client
  participant API as DataForSEO API

  User->>Action: Provide props (keyword/target/appIds/etc.)
  Action->>App: this.dataforseo.getXxx({ $, data: [...] })
  App->>API: HTTP request via _makeRequest()
  API-->>App: Response { status_code, tasks: [...] }
  App-->>Action: Response
  Action->>Action: Validate response.status_code and response.tasks[0].status_code
  alt error
    Action-->>User: throw ConfigurationError(status_message)
  else success
    Action-->>User: $.export("$summary") + return full response
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Add Keyword data endpoints (#18048)
Add DataForSEOLabs endpoints (#18048)
Add Backlinks endpoints (#18048) Several backlinks-related files show only version bumps; no new backlinks client methods or new backlinks actions were clearly added in this diff.
Add OnPage endpoints (#18048) No OnPage-specific endpoints or actions (OnPage category) were added in this diff.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Removed async from _makeRequest and several methods (components/dataforseo/dataforseo.app.mjs) Linked issue requested adding endpoints; altering method async signatures changes internal implementation surface and was not part of the stated objective.
Changed limit prop type from string to integer (components/dataforseo/dataforseo.app.mjs) The issue asked to add endpoints; changing public prop types affects UI/validation and is outside simply adding endpoints.

I thump my paws on fresh new code,
Endpoints sprout where I once strode.
From SERP to Apps I hop and cheer,
Summaries clear and carrots near.
Hooray — more data, hop by hop! 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-18048

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Status, Documentation and Community

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

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

🔭 Outside diff range comments (4)
components/dataforseo/actions/parse-page-content/parse-page-content.mjs (1)

37-38: Fix broken Markdown link in user-facing description

The link text/URL are reversed. This will render as a broken link.

Apply this diff to correct the Markdown:

-      description: "Set to `true` if you want to get the HTML of the page using the [https://docs.dataforseo.com/v3/on_page/raw_html/](OnPage Raw HTML endpoint)",
+      description: "Set to `true` if you want to get the HTML of the page using the [OnPage Raw HTML endpoint](https://docs.dataforseo.com/v3/on_page/raw_html/)",
components/dataforseo/dataforseo.app.mjs (3)

14-21: Null-safe options for Location Code and clearer naming

Avoid exceptions when the API returns an empty tasks array and use a neutral variable name.

-        const response = await this.getLocations();
-        const languageCodes = response.tasks[0].result;
-        return languageCodes.map(({
+        const response = await this.getLocations();
+        const results = response?.tasks?.[0]?.result || [];
+        return results.map(({
           location_name, location_code,
         }) => ({
           value: location_code,
           label: location_name,
         }));

79-87: Null-safe options for Language Code

Mirror the defensive approach used for Location Code to prevent runtime errors.

-        const response = await this.getLanguageCode();
-        const languageCodes = response.tasks[0].result;
-        return languageCodes.map(({
+        const response = await this.getLanguageCode();
+        const languageCodes = response?.tasks?.[0]?.result || [];
+        return languageCodes.map(({
           language_name, language_code,
         }) => ({
           value: language_code,
           label: language_name,
         }));

167-168: Close the parenthesis in the targets description

Minor documentation typo in a user-facing field.

-      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`",
+      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",
🧹 Nitpick comments (77)
components/dataforseo/actions/parse-page-content/parse-page-content.mjs (1)

41-44: Capitalize “JavaScript” in label and description

Minor UX polish in user-facing text.

-      label: "Enable Javascript",
-      description: "Set to `true` if you want to load the scripts available on a page",
+      label: "Enable JavaScript",
+      description: "Set to `true` if you want to load the scripts available on a page",
components/dataforseo/actions/get-business-listings/get-business-listings.mjs (1)

62-62: Harden $summary construction to avoid runtime errors when tasks is missing/empty

If the API returns an unexpected shape (e.g., no tasks), accessing response.tasks[0] will throw.

Apply this diff:

-    $.export("$summary", `Successfully sent the request. Status: ${response.tasks[0].status_message}`);
+    const statusMsg = response?.tasks?.[0]?.status_message ?? response?.status_message ?? "Request sent";
+    $.export("$summary", `Successfully sent the request. Status: ${statusMsg}`);
components/dataforseo/actions/get-ranked-keywords/get-ranked-keywords.mjs (1)

41-42: Defensive summary construction to avoid potential TypeError

If the API returns an unexpected payload (e.g., empty tasks array), indexing tasks[0] will throw. Recommend a safe fallback.

Apply this diff:

-    $.export("$summary", `Successfully sent the request. Status: ${response.tasks[0].status_message}`);
+    const status = response?.tasks?.[0]?.status_message ?? "Request sent";
+    $.export("$summary", `Successfully sent the request. Status: ${status}`);
components/dataforseo/actions/get-bulk-ranks/get-bulk-ranks.mjs (1)

11-18: Minor naming nit: method name could be simplified

Method name getBacklinksBulkRanks is accurate but slightly verbose within this action whose key is already “get-bulk-ranks”. Consider getBulkRanks for clarity.

Apply this diff if you agree:

-    getBacklinksBulkRanks(args = {}) {
+    getBulkRanks(args = {}) {
       return this.dataforseo._makeRequest({
         path: "/backlinks/bulk_ranks/live",
         method: "post",
         ...args,
       });
     },

And update the caller:

-    const response = await this.getBacklinksBulkRanks({
+    const response = await this.getBulkRanks({
components/dataforseo/actions/search-business-listings/search-business-listings.mjs (1)

62-78: Consider validating minimum input to prevent empty searches

Per typical DataForSEO patterns, at least one of the query-defining fields should be provided (e.g., categories, description, title, or a location constraint). Add a guard to catch empty criteria early.

Apply this diff:

   async run({ $ }) {
-    const response = await this.searchBusinessListings({
+    if (
+      (!this.categories || this.categories.length === 0)
+      && !this.description
+      && !this.title
+      && !this.locationCoordinate
+    ) {
+      throw new Error("Provide at least one of: categories, description, title, or locationCoordinate.");
+    }
+    const response = await this.searchBusinessListings({
       $,
       data: [
         {
           categories: this.categories,
           description: this.description,
           title: this.title,
           location_coordinate: this.locationCoordinate,
           tag: this.tag,
           ...parseObjectEntries(this.additionalOptions),
         },
       ],
     });
components/dataforseo/actions/get-categories-aggregation/get-categories-aggregation.mjs (1)

62-78: Optional: validate inputs to avoid overly broad aggregations

To reduce unexpected API calls, validate that at least one scoping field is present (e.g., categories/description/title or a location).

Apply this diff:

   async run({ $ }) {
-    const response = await this.getCategoriesAggregation({
+    if (
+      (!this.categories || this.categories.length === 0)
+      && !this.description
+      && !this.title
+      && !this.locationCoordinate
+    ) {
+      throw new Error("Provide at least one of: categories, description, title, or locationCoordinate.");
+    }
+    const response = await this.getCategoriesAggregation({
       $,
       data: [
         {
           categories: this.categories,
           description: this.description,
           title: this.title,
           location_coordinate: this.locationCoordinate,
           tag: this.tag,
           ...parseObjectEntries(this.additionalOptions),
         },
       ],
     });
components/dataforseo/actions/get-bulk-referring-domains/get-bulk-referring-domains.mjs (1)

34-46: Optional: validate batch size and inputs for targets before sending the request.
DataForSEO bulk endpoints typically accept a bounded list of targets. Pre-validating helps fail fast with actionable messages and prevents API rejections.

Apply this minimal guard:

   async run({ $ }) {
-    const response = await this.getBulkReferringDomains({
+    if (!Array.isArray(this.targets) || this.targets.length === 0) {
+      throw new Error("Please provide at least one target.");
+    }
+    // Optional: enforce an upper bound if DataForSEO applies one (e.g., 1000)
+    if (this.targets.length > 1000) {
+      throw new Error(`Too many targets: ${this.targets.length}. Maximum supported is 1000.`);
+    }
+    const response = await this.getBulkReferringDomains({
       $,
       data: [
         {
           targets: this.targets,
           tag: this.tag,
         },
       ],
     });
components/dataforseo/actions/get-backlinks-history/get-backlinks-history.mjs (1)

53-68: Optional: validate date inputs and ordering before calling the API.
The endpoint expects YYYY-MM-DD format, and date_from should not exceed date_to. Guarding here prevents avoidable API errors.

Add lightweight validation:

   async run({ $ }) {
-    const response = await this.getBacklinksHistory({
+    const fmt = /^\d{4}-\d{2}-\d{2}$/;
+    if (this.dateFrom && !fmt.test(this.dateFrom)) {
+      throw new Error("dateFrom must be in YYYY-MM-DD format.");
+    }
+    if (this.dateTo && !fmt.test(this.dateTo)) {
+      throw new Error("dateTo must be in YYYY-MM-DD format.");
+    }
+    if (this.dateFrom && this.dateTo && this.dateFrom > this.dateTo) {
+      throw new Error("dateFrom must be less than or equal to dateTo.");
+    }
+    const response = await this.getBacklinksHistory({
       $,
       data: [
         {
           target: this.target,
           date_from: this.dateFrom,
           date_to: this.dateTo,
           rank_scale: this.rankScale,
           tag: this.tag,
         },
       ],
     });
components/dataforseo/actions/get-domain-pages-summary/get-domain-pages-summary.mjs (2)

70-77: Double-check additionalOptions parsing behavior.
You’re spreading parseObjectEntries(this.additionalOptions). Ensure the utility:

  • Returns {} for falsy input,
  • Parses JSON-like values where needed,
  • Does not include keys with empty string values unless intended.

If any of the above isn’t guaranteed, a defensive fallback is trivial.

Optionally, add a local fallback:

   additionalOptions: {
     propDefinition: [
       dataforseo,
       "additionalOptions",
     ],
     description: "Additional parameters to send in the request. [See the documentation](https://docs.dataforseo.com/v3/backlinks/domain_pages_summary/live/) for all available parameters. Values will be parsed as JSON where applicable.",
   },
 },
 async run({ $ }) {
-    const response = await this.getDomainPagesSummary({
+    const extra = parseObjectEntries(this.additionalOptions) || {};
+    const response = await this.getDomainPagesSummary({
       $,
       data: [
         {
           target: this.target,
           include_subdomains: this.includeSubdomains,
           include_indirect_links: this.includeIndirectLinks,
           exclude_internal_backlinks: this.excludeInternalBacklinks,
           backlinks_status_type: this.backlinksStatusType,
           backlinks_filters: this.backlinksFilters,
           rank_scale: this.rankScale,
           tag: this.tag,
-          ...parseObjectEntries(this.additionalOptions),
+          ...extra,
         },
       ],
     });

52-63: Consider light validation for backlinksFilters shape.
DataForSEO often expects an array of filter triplets. If the app propDefinition already enforces schema, ignore. Otherwise, a quick check avoids API errors.

Example minimal guard:

-          backlinks_filters: this.backlinksFilters,
+          backlinks_filters: Array.isArray(this.backlinksFilters) ? this.backlinksFilters : undefined,
components/dataforseo/actions/get-bulk-spam-score/get-bulk-spam-score.mjs (1)

34-46: Optional: add basic input checks for targets.
Same rationale as other bulk endpoints: ensure non-empty list and cap to a reasonable max to prevent API rejects.

Suggested guard:

   async run({ $ }) {
-    const response = await this.getBulkSpamScore({
+    if (!Array.isArray(this.targets) || this.targets.length === 0) {
+      throw new Error("Please provide at least one target.");
+    }
+    if (this.targets.length > 1000) {
+      throw new Error(`Too many targets: ${this.targets.length}. Maximum supported is 1000.`);
+    }
+    const response = await this.getBulkSpamScore({
       $,
       data: [
         {
           targets: this.targets,
           tag: this.tag,
         },
       ],
     });
components/dataforseo/actions/get-bulk-backlinks/get-bulk-backlinks.mjs (1)

34-46: Optional: enforce simple targets validation for a better UX.
Same guidance as the other bulk actions to avoid avoidable API errors.

Apply:

   async run({ $ }) {
-    const response = await this.getBulkBacklinks({
+    if (!Array.isArray(this.targets) || this.targets.length === 0) {
+      throw new Error("Please provide at least one target.");
+    }
+    if (this.targets.length > 1000) {
+      throw new Error(`Too many targets: ${this.targets.length}. Maximum supported is 1000.`);
+    }
+    const response = await this.getBulkBacklinks({
       $,
       data: [
         {
           targets: this.targets,
           tag: this.tag,
         },
       ],
     });
components/dataforseo/actions/get-business-listings-categories/get-business-listings-categories.mjs (1)

18-24: Guard against missing tasks[0] to avoid runtime TypeError.
If DataForSEO returns no tasks, accessing response.tasks[0] will throw. Add a length check before dereferencing.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks || response.tasks.length === 0) {
+      throw new ConfigurationError("Error: no tasks returned in response");
+    }
+    if (response.tasks[0].status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
+    }
components/dataforseo/actions/get-google-ads-keywords-for-site-completed-tasks/get-google-ads-keywords-for-site-completed-tasks.mjs (1)

22-24: Prevent potential crash when tasks is empty.
Add a guard before accessing response.tasks[0].

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks || response.tasks.length === 0) {
+      throw new ConfigurationError("Error: no tasks returned in response");
+    }
+    if (response.tasks[0].status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
+    }
components/dataforseo/actions/get-sentiment-analysis/get-sentiment-analysis.mjs (1)

28-34: Add a guard for empty tasks to avoid runtime errors.
This matches the pattern recommended across the new actions and prevents TypeErrors when tasks is empty.

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks || response.tasks.length === 0) {
+      throw new ConfigurationError("Error: no tasks returned in response");
+    }
+    if (response.tasks[0].status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
+    }
components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs (1)

39-45: Guard tasks access to avoid a potential TypeError.
Add a defensive check before using response.tasks[0].

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks || response.tasks.length === 0) {
+      throw new ConfigurationError("Error: no tasks returned in response");
+    }
+    if (response.tasks[0].status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
+    }
components/dataforseo/actions/get-google-ads-ad-traffic-by-keyword/get-google-ads-ad-traffic-by-keyword.mjs (2)

5-7: Prefer pluralized key/name to reflect multiple keywords

This action accepts an array of keywords and calls a pluralized API method. Align the key/name to reduce ambiguity and keep consistency with other actions (e.g., “Keywords For Keywords”).

-  key: "dataforseo-get-google-ads-ad-traffic-by-keyword",
-  name: "Get Google Ads Ad Traffic By Keyword",
+  key: "dataforseo-get-google-ads-ad-traffic-by-keywords",
+  name: "Get Google Ads Ad Traffic By Keywords",

70-71: Make the success summary more informative

Show how many keywords were processed and handle pluralization.

-    $.export("$summary", "Successfully retrieved Google Ads ad traffic by keyword.");
+    const count = this.keywords?.length ?? 0;
+    $.export("$summary", `Successfully retrieved Google Ads ad traffic for ${count} keyword${count === 1 ? "" : "s"}.`);
components/dataforseo/dataforseo.app.mjs (2)

69-73: Enforce numeric bounds for the limit prop

The description notes a maximum; reflect that in the schema.

     limit: {
       type: "integer",
       label: "Limit",
-      description: "The maximum number of results to return. Maximum: 1000",
+      description: "The maximum number of results to return. Maximum: 1000",
+      min: 1,
+      max: 1000,
       optional: true,
     },

210-221: Location codes are consistent, but use the Keywords Data endpoint for Keywords‐Data UIs

Location codes (e.g. United States = 2840) are shared between the SERP and Keywords Data APIs, but to surface the correct list (and any API-specific caveats) you should:

  • In components/dataforseo/dataforseo.app.mjs, leave
    getLocations() pointed at /serp/google/ads_search/locations for SERP/Ads-Search use
  • Add (or update) a dedicated method for Keywords Data, e.g.:
    getKeywordsLocations(args = {}) {
      return this._makeRequest({
        path: "/keywords_data/google_ads/locations",
        ...args,
      });
    }
  • If you really need a single shared UI, either
    • fetch from the matching endpoint at runtime, or
    • fetch both /serp/.../locations and /keywords_data/.../locations once, merge/normalize, and cache

This ensures you get any Keywords-API–specific differences (e.g., “Okrug” or postal-code support).

components/dataforseo/actions/get-bulk-traffic-analytics/get-bulk-traffic-analytics.mjs (1)

51-52: Polish the success summary with pluralization

-    $.export("$summary", `Successfully retrieved bulk traffic analytics for ${this.targets.length} domains.`);
+    const count = this.targets?.length ?? 0;
+    $.export("$summary", `Successfully retrieved bulk traffic analytics for ${count} domain${count === 1 ? "" : "s"}.`);
components/dataforseo/actions/get-bing-organic-results/get-bing-organic-results.mjs (1)

29-35: Set default for depth to match the description

The description states a default of 100; reflect that in the schema.

     depth: {
       type: "integer",
       label: "Depth",
       description: "The parsing depth. Default: 100",
       max: 700,
+      default: 100,
       optional: true,
     },
components/dataforseo/actions/get-google-ads-traffic-by-keywords-completed-tasks/get-google-ads-traffic-by-keywords-completed-tasks.mjs (1)

14-16: Optionally validate all tasks, not just the first

If multiple tasks are returned, consider failing fast on any task with a non-20000 status to surface partial failures.

-    const response = await this.dataforseo.getGoogleAdsAdTrafficByKeywordsCompletedTasks({
+    const response = await this.dataforseo.getGoogleAdsAdTrafficByKeywordsCompletedTasks({
       $,
     });
@@
-    const firstTask = response.tasks && response.tasks[0];
-    if (!firstTask) {
-      throw new ConfigurationError("No tasks returned by DataForSEO.");
-    }
-    if (firstTask.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${firstTask.status_message}`);
-    }
+    const tasks = response.tasks || [];
+    if (!tasks.length) {
+      throw new ConfigurationError("No tasks returned by DataForSEO.");
+    }
+    const failed = tasks.find(t => t.status_code !== 20000);
+    if (failed) {
+      throw new ConfigurationError(`Error: ${failed.status_message}`);
+    }

Also applies to: 18-24

components/dataforseo/actions/get-google-ads-locations/get-google-ads-locations.mjs (2)

12-16: Make Country Code optional (API supports broad fetch without filter)

Locations can often be fetched without a filter; forcing a value reduces usability. Mark as optional so users can fetch all locations or filter by country when needed.

     countryCode: {
       type: "string",
       label: "Country Code",
-      description: "The country code to get locations for. Ex: `US`",
+      description: "Optional ISO 3166-1 alpha-2 country code to filter locations. Ex: `US`",
+      optional: true,
     },

32-33: Tiny consistency nit: add trailing period to summary

Match the punctuation style used in sibling actions.

-    $.export("$summary", "Successfully retrieved Google Ads locations");
+    $.export("$summary", "Successfully retrieved Google Ads locations.");
components/dataforseo/actions/get-google-ads-search-volume/get-google-ads-search-volume.mjs (2)

19-31: Confirm requiredness of locationCode and languageCode for this endpoint

For DataForSEO Search Volume, location_code and language_code are typically required. Marking them optional may cause avoidable API errors. If docs confirm they are required, remove optional: true for both props to align with other Google Ads actions in this PR.

     locationCode: {
       propDefinition: [
         dataforseo,
         "locationCode",
       ],
-      optional: true,
     },
     languageCode: {
       propDefinition: [
         dataforseo,
         "languageCode",
       ],
-      optional: true,
     },

If the docs indicate they are indeed optional, no change needed—this is just to maintain consistency and reduce user confusion.


33-43: Consider chunking large keyword lists to respect API limits

If users pass more than the API’s allowed number of keywords per request, pre-chunking and auto-pagination can improve UX and reliability. This can be added later; not blocking.

Happy to propose a chunking utility if you want to support batching transparently.

components/dataforseo/actions/get-google-play-search/get-google-play-search.mjs (2)

46-48: Defensive check for missing tasks to avoid runtime error

If the API responds with an empty tasks array, response.tasks[0] will throw. Guard before dereferencing.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: Empty tasks array in API response");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

42-48: Deduplicate two-tier error handling via a shared helper

This exact status_code + first task status_code pattern is repeated across actions. Consider centralizing in dataforseo.app.mjs (e.g., await app.requestAndValidate(method, payload, $)), returning { task, response }. This keeps actions thin and consistent.

components/dataforseo/actions/get-app-intersection/get-app-intersection.mjs (3)

36-41: Validate minimum number of apps for intersection

API requires comparing at least two apps. Fail fast with a clear message.

Apply this diff:

   async run({ $ }) {
-    const appIds = {};
+    if (!Array.isArray(this.appIds) || this.appIds.length < 2) {
+      throw new ConfigurationError("Please provide at least 2 app IDs for intersection.");
+    }
+    const appIds = {};
     for (let i = 0; i < this.appIds.length; i++) {
       appIds[`${i + 1}`] = this.appIds[i];
     }

44-44: Remove debug flag or make it a prop

debug: true likely shouldn’t ship by default. Either remove or expose as an optional prop.

Apply this diff:

-      debug: true,

59-61: Guard against empty tasks array to prevent crash

Same defensive pattern as other actions: check for tasks?.[0].

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: Empty tasks array in API response");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }
components/dataforseo/actions/get-google-ads-search-volume-completed-tasks/get-google-ads-search-volume-completed-tasks.mjs (1)

22-24: Handle empty tasks when none are ready

Completed-tasks endpoints can return no tasks; guard to avoid tasks[0] access errors.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      $.export("$summary", "No completed tasks ready.");
+      return response;
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }
components/dataforseo/actions/get-top-serp-results/get-top-serp-results.mjs (2)

37-38: Validate non-empty keywords array before API call

Avoid sending an empty keywords list which will return an API error.

Apply this diff:

   async run({ $ }) {
+    if (!Array.isArray(this.keywords) || this.keywords.length === 0) {
+      throw new ConfigurationError("Please provide at least one keyword.");
+    }
     const response = await this.dataforseo.getTopSerpResults({

54-56: Defensive check for missing tasks

Prevent crash on empty tasks and reuse the task variable.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: Empty tasks array in API response");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (2)

57-59: Guard tasks access to avoid runtime error on empty response

Add a check before reading tasks[0].

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: Empty tasks array in API response");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

12-16: Consider basic input validation for keyword

Reject blank/whitespace-only keywords to avoid avoidable API calls.

Example insertion just before the API call:

if (!this.keyword?.trim()) {
  throw new ConfigurationError("Keyword is required.");
}
components/dataforseo/actions/get-domain-rank-overview/get-domain-rank-overview.mjs (2)

43-49: Guard against missing tasks to avoid runtime errors

Accessing response.tasks[0] without checking presence can throw if the array is empty or missing. Add a defensive check.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: no tasks returned by DataForSEO.");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

31-53: Reduce duplication of status checks across actions

Many actions in this PR duplicate the same two-tier status check. Consider extracting a small helper to validate responses, improving maintainability and consistency.

You could add a utility in the app (or a nearby module) like:

export function ensureDataForSeoSuccess(response) {
  if (response?.status_code !== 20000) {
    const msg = response?.status_message || "Unknown API error";
    throw new ConfigurationError(`Error: ${msg}`);
  }
  const task = response?.tasks?.[0];
  if (!task) {
    throw new ConfigurationError("Error: no tasks returned by DataForSEO.");
  }
  if (task.status_code !== 20000) {
    const msg = task?.status_message || "Unknown task error";
    throw new ConfigurationError(`Error: ${msg}`);
  }
  return task;
}

Then call it inside actions and optionally return task.result if that’s the primary payload consumers need.

components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (2)

42-48: Harden task error handling to avoid undefined access

Add a guard around response.tasks[0] to prevent runtime errors when tasks is empty or missing.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: no tasks returned by DataForSEO.");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

30-40: Confirm supported parameters and recommend place_id/cid for precision

  • File: components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (lines 30–40)
  • The “Google Reviews (live)” endpoint does accept
    {
      keyword: this.keyword,
      location_coordinate: this.locationCoordinate,
      language_code: this.languageCode,
    }
    so the current implementation is valid.
  • For unambiguous review retrieval, DataForSEO recommends supplying a place_id or cid in the keyword field (e.g. keyword: "place_id:GhIJQWDl0CIeQUARxks3icF8U8A").
  • If you can obtain a Place ID or CID upstream, consider extending this action to:
    • accept a placeId (or cid) prop
    • set
    - keyword: this.keyword,
    + keyword: `place_id:${this.placeId}`,
    (you may still include location_coordinate for verification/narrowing, but it isn’t required when using place_id/cid).
  • Otherwise, no changes are required—the existing keyword+coordinates approach is supported.
components/dataforseo/actions/get-historical-serp-data/get-historical-serp-data.mjs (3)

29-40: Optional: Validate date inputs early

Consider validating dateFrom/dateTo format and order to fail fast with clearer messages, instead of relying on API-side validation.

Add a lightweight check in run() to ensure YYYY-MM-DD format and that dateFrom <= dateTo when both are provided.


42-54: Build params conditionally and validate date range

Avoid sending undefined fields and provide a clearer validation error if the date range is invalid.

Apply this diff:

-  async run({ $ }) {
-    const response = await this.dataforseo.getHistoricalSerpData({
-      $,
-      data: [
-        {
-          keyword: this.keyword,
-          location_code: this.locationCode,
-          language_code: this.languageCode,
-          date_from: this.dateFrom,
-          date_to: this.dateTo,
-        },
-      ],
-    });
+  async run({ $ }) {
+    if (this.dateFrom && this.dateTo && new Date(this.dateFrom) > new Date(this.dateTo)) {
+      throw new ConfigurationError('"Date From" must be before or equal to "Date To".');
+    }
+    const params = {
+      keyword: this.keyword,
+      location_code: this.locationCode,
+      language_code: this.languageCode,
+    };
+    if (this.dateFrom) params.date_from = this.dateFrom;
+    if (this.dateTo) params.date_to = this.dateTo;
+    const response = await this.dataforseo.getHistoricalSerpData({
+      $,
+      data: [ params ],
+    });

60-62: Guard against missing tasks before dereferencing

Same pattern as in other actions: avoid accessing tasks[0] blindly.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: no tasks returned by DataForSEO.");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }
components/dataforseo/actions/get-domain-whois-overview/get-domain-whois-overview.mjs (1)

33-35: Add guard for potentially missing tasks

Protect against tasks being absent to avoid runtime errors.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: no tasks returned by DataForSEO.");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }
components/dataforseo/actions/get-content-summary/get-content-summary.mjs (1)

32-34: Defensive check for tasks

Same small hardening suggestion for tasks[0].

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: no tasks returned by DataForSEO.");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }
components/dataforseo/actions/get-domain-keywords/get-domain-keywords.mjs (2)

50-56: DRY up repeated response validation across actions

This two-tier response validation repeats in many new actions. Consider centralizing in the app to reduce duplication and keep behavior consistent.

Example in components/dataforseo/dataforseo.app.mjs:

// inside the exported app object
methods: {
  assertOk(response) {
    if (response?.status_code !== 20000) {
      throw new this.ConfigurationError(`Error: ${response?.status_message || 'Unknown error'}`);
    }
    const task = response.tasks?.[0];
    if (!task) {
      throw new this.ConfigurationError("Unexpected API response: no tasks returned");
    }
    if (task.status_code !== 20000) {
      throw new this.ConfigurationError(`Error: ${task.status_message || 'Unknown task error'}`);
    }
    return task;
  },
}

Then in actions:

const response = await this.dataforseo.getDomainKeywords({ $, data: [ /*...*/ ] });
this.dataforseo.assertOk(response);

58-59: Optional: Make the summary more informative by including item count

If available, include the number of items returned to help users quickly assess results.

-    $.export("$summary", `Successfully retrieved domain keywords for "${this.target}".`);
+    const task = response.tasks?.[0];
+    const count = task?.result?.[0]?.items?.length ?? 0;
+    $.export("$summary", `Retrieved ${count} domain keywords for "${this.target}".`);
components/dataforseo/actions/get-technologies-domain-list/get-technologies-domain-list.mjs (1)

10-18: Expose limit as an optional prop to match API capabilities

The endpoint supports pagination/limits. Surfacing limit improves usability and parity with other actions.

   props: {
     dataforseo,
     target: {
       propDefinition: [
         dataforseo,
         "target",
       ],
     },
+    limit: {
+      propDefinition: [
+        dataforseo,
+        "limit",
+      ],
+    },
   },
   async run({ $ }) {
     const response = await this.dataforseo.getTechnologiesDomainList({
       $,
       data: [
         {
           target: this.target,
+          limit: this.limit,
         },
       ],
     });

Also applies to: 23-26

components/dataforseo/actions/get-google-my-business-info/get-google-my-business-info.mjs (1)

12-16: If the endpoint truly requires IDs, adjust props and summary accordingly

Should the docs confirm IDs are required, expose businessId/placeId/googleId props and tailor the summary.

I can draft a revised version with multiple lookup options and conditional payload construction once the correct required fields are confirmed.

Also applies to: 50-51

components/dataforseo/actions/get-keyword-ideas-live/get-keyword-ideas-live.mjs (1)

58-59: Minor: Make the summary resilient if keywords isn’t an array

If keywords ever comes through as a string (depending on propDefinition behavior), accessing .length will misleadingly report character count.

-    $.export("$summary", `Successfully retrieved keyword ideas for ${this.keywords.length} keywords.`);
+    const kwCount = Array.isArray(this.keywords) ? this.keywords.length : 1;
+    $.export("$summary", `Successfully retrieved keyword ideas for ${kwCount} keyword${kwCount === 1 ? "" : "s"}.`);
components/dataforseo/actions/get-content-citations/get-content-citations.mjs (1)

17-22: Optional: Add pagination controls (offset) to support larger result sets

The endpoint supports pagination; exposing offset improves utility for workflows fetching more than limit.

   props: {
     dataforseo,
     keyword: {
       type: "string",
       label: "Keyword",
       description: "The keyword to search for",
     },
     limit: {
       propDefinition: [
         dataforseo,
         "limit",
       ],
     },
+    offset: {
+      propDefinition: [
+        dataforseo,
+        "offset",
+      ],
+    },
   },
   async run({ $ }) {
     const response = await this.dataforseo.getContentCitations({
       $,
       data: [
         {
           keyword: this.keyword,
           limit: this.limit,
+          offset: this.offset,
         },
       ],
     });

Also applies to: 28-31

components/dataforseo/actions/get-google-organic-results/get-google-organic-results.mjs (2)

58-64: Guard against empty tasks and accept 20100 (no results) from DataForSEO

Prevent a potential runtime error when tasks is empty and treat 20100 as a successful “no results” outcome, consistent with other actions in this PR.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: Empty tasks array in DataForSEO response");
+    }
+    const task = response.tasks[0];
+    if (![20000, 20100].includes(task.status_code)) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

29-35: Align prop description with actual default behavior for depth

The description says “Default: 100” but no default is set. Either set the default or remove the note to avoid confusion. Suggest setting a default.

     depth: {
       type: "integer",
       label: "Depth",
-      description: "The parsing depth. Default: 100",
+      description: "The parsing depth",
       max: 700,
       optional: true,
+      default: 100,
     },
components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (1)

46-48: Add a guard for missing tasks to avoid a crash

Avoid indexing tasks[0] when tasks could be empty, and reuse the extracted task for readability.

-    if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: Empty tasks array in DataForSEO response");
+    }
+    const task = response.tasks[0];
+    if (task.status_code !== 20000 && task.status_code !== 20100) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (2)

45-47: Guard against empty tasks

Prevent a crash if tasks is empty and improve readability.

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: Empty tasks array in DataForSEO response");
+    }
+    const task = response.tasks[0];
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

12-28: Optional: Validate time range before calling the API

You could pre-validate that dateTimeFrom <= dateTimeTo to fail fast with a clearer message.

If you want, I can propose a small validation snippet to add before the API call.

components/dataforseo/actions/get-google-news-results/get-google-news-results.mjs (1)

46-48: Consider accepting 20100 (no results) and guard against empty tasks

Other actions in this PR treat 20100 as acceptable; also avoid indexing into an empty tasks array.

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: Empty tasks array in DataForSEO response");
+    }
+    const task = response.tasks[0];
+    if (![20000, 20100].includes(task.status_code)) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }
components/dataforseo/actions/get-app-reviews-summary/get-app-reviews-summary.mjs (2)

57-59: Guard against empty tasks and reuse the task object

Avoid a potential crash and keep the code tidy.

-    if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: Empty tasks array in DataForSEO response");
+    }
+    const task = response.tasks[0];
+    if (task.status_code !== 20000 && task.status_code !== 20100) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

13-16: Clarify appId description to match the linked Apple endpoint

The doc link points to the Apple endpoint. If this action targets Apple only, the description “App ID or package name” can mislead (Android uses package name). Suggest clarifying.

-      description: "App ID or package name for the mobile app",
+      description: "App Store app ID (numeric). Example: 544007664",

If the underlying app client supports both Apple and Google stores, consider updating the docs link and description to reflect that.

components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (2)

7-7: Use singular “keyword” in description to match the props

The action takes a single keyword, but the description says “keywords”.

Apply this diff:

-  description: "Retrieve Google Images search results for specified keywords. [See the documentation](https://docs.dataforseo.com/v3/serp/google/images/live/?bash)",
+  description: "Retrieve Google Images search results for a specified keyword. [See the documentation](https://docs.dataforseo.com/v3/serp/google/images/live/?bash)",

46-48: Guard against missing tasks array before indexing

If response.tasks is empty/undefined, accessing [0] can throw. Add a defensive check.

Apply this diff:

+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: No tasks were returned by DataForSEO.");
+    }
     if (response.tasks[0].status_code !== 20000) {
       throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
     }
components/dataforseo/actions/get-competitor-domains/get-competitor-domains.mjs (3)

7-7: Tighten description: this action uses a target domain, not keywords

The description mentions “keywords or target domain,” but the payload only supports target.

Apply this diff:

-  description: "Find competing domains for specified keywords or target domain. [See the documentation](https://docs.dataforseo.com/v3/dataforseo_labs/google/competitors_domain/live/?bash)",
+  description: "Find competing domains for the specified target domain. [See the documentation](https://docs.dataforseo.com/v3/dataforseo_labs/google/competitors_domain/live/?bash)",

38-48: Optionally sanitize the target to strip protocol/www

Docs typically expect domains without scheme or www. We can normalize input defensively.

Apply this diff:

   async run({ $ }) {
-    const response = await this.dataforseo.getCompetitorDomains({
+    const target = String(this.target)
+      .replace(/^https?:\/\/(www\.)?/i, "")
+      .replace(/^www\./i, "");
+    const response = await this.dataforseo.getCompetitorDomains({
       $,
       data: [
         {
-          target: this.target,
+          target,
           location_code: this.locationCode,
           language_code: this.languageCode,
           limit: this.limit,
         },
       ],
     });

54-56: Guard against missing tasks array before indexing

Avoid a potential runtime error when tasks is empty/undefined.

Apply this diff:

+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: No tasks were returned by DataForSEO.");
+    }
     if (response.tasks[0].status_code !== 20000) {
       throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
     }
components/dataforseo/actions/get-keyword-suggestions/get-keyword-suggestions.mjs (2)

7-7: Align description with single-input prop

The description says “seed keywords” (plural), but the action takes a single keyword.

Apply this diff:

-  description: "Get keyword ideas and related terms for specified seed keywords. [See the documentation](https://docs.dataforseo.com/v3/dataforseo_labs/google/keyword_suggestions/live/?bash)",
+  description: "Get keyword ideas and related terms for a specified seed keyword. [See the documentation](https://docs.dataforseo.com/v3/dataforseo_labs/google/keyword_suggestions/live/?bash)",

53-55: Guard against missing tasks array before indexing

Prevents a crash when tasks is empty/undefined.

Apply this diff:

+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: No tasks were returned by DataForSEO.");
+    }
     if (response.tasks[0].status_code !== 20000) {
       throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
     }
components/dataforseo/actions/get-domain-intersection/get-domain-intersection.mjs (3)

46-51: Normalize domains before sending to the API

Strip protocol and www from both inputs to reduce user error; matches the prop descriptions.

Apply this diff:

-          target1: this.target1,
-          target2: this.target2,
+          target1: String(this.target1).replace(/^https?:\/\/(www\.)?/i, "").replace(/^www\./i, ""),
+          target2: String(this.target2).replace/^https?:\/\/(www\.)?/i, "").replace(/^www\./i, ""),

Note: If you prefer readability, compute `const t1`/`t2` above the payload instead.

---

`59-61`: **Guard against missing tasks array before indexing**

Avoid a potential runtime error when `tasks` is empty.

Apply this diff:

```diff
+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: No tasks were returned by DataForSEO.");
+    }
     if (response.tasks[0].status_code !== 20000) {
       throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
     }

63-63: Make summary consistent with other actions (quote the domains)

Other actions quote user-provided strings in the summary.

Apply this diff:

-    $.export("$summary", `Successfully retrieved domain intersection for domains ${this.target1} and ${this.target2}.`);
+    $.export("$summary", `Successfully retrieved domain intersection for domains "${this.target1}" and "${this.target2}".`);
components/dataforseo/actions/get-yahoo-organic-results/get-yahoo-organic-results.mjs (3)

7-7: Use singular “keyword” in description to match the props

This action accepts a single keyword.

Apply this diff:

-  description: "Retrieve Yahoo organic search results for specified keywords. [See the documentation](https://docs.dataforseo.com/v3/serp/yahoo/organic/live/?bash)",
+  description: "Retrieve Yahoo organic search results for a specified keyword. [See the documentation](https://docs.dataforseo.com/v3/serp/yahoo/organic/live/?bash)",

30-35: Optional: Set default depth to 100 to match description

The description claims a default of 100. You can encode that as the prop default for clarity.

Apply this diff:

     depth: {
       type: "integer",
       label: "Depth",
       description: "The parsing depth. Default: 100",
       max: 700,
       optional: true,
+      default: 100,
     },

62-64: Guard against missing tasks array before indexing

Prevents a crash when tasks is empty/undefined.

Apply this diff:

+    if (!response.tasks?.length) {
+      throw new ConfigurationError("Error: No tasks were returned by DataForSEO.");
+    }
     if (response.tasks[0].status_code !== 20000) {
       throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
     }
components/dataforseo/actions/get-google-ads-keywords-for-keywords-completed-tasks/get-google-ads-keywords-for-keywords-completed-tasks.mjs (1)

26-26: Optional: Summarize the number of ready tasks for better UX

A dynamic summary helps users immediately see what was returned.

-    $.export("$summary", "Successfully retrieved Google Ads keywords for keywords completed tasks.");
+    $.export("$summary", `Successfully retrieved ${response.tasks?.length ?? 0} completed task(s).`);
components/dataforseo/actions/get-google-ads-keywords-for-keywords/get-google-ads-keywords-for-keywords.mjs (3)

36-43: Avoid sending undefined fields by conditionally including optional params

DataForSEO will ignore missing fields; being explicit avoids sending undefined values and keeps requests clean.

       data: [
         {
           keywords: this.keywords,
-          location_code: this.locationCode,
-          language_code: this.languageCode,
+          ...(this.locationCode != null && { location_code: this.locationCode }),
+          ...(this.languageCode != null && { language_code: this.languageCode }),
         },
       ],

49-51: Make nested status checks resilient to unexpected response shapes

While the live endpoint typically returns one task, adding a simple guard prevents brittle failures if the API shape changes or an upstream error returns no tasks.

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: No task information found in response.");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

53-54: Optional: Provide a more informative summary (items count)

Surfacing the number of related keywords returned can improve usability.

-    $.export("$summary", "Successfully retrieved Google Ads keywords for keywords.");
+    $.export("$summary", `Successfully retrieved ${(response.tasks?.[0]?.result?.[0]?.items?.length) ?? 0} related keyword(s).`);

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

🔭 Outside diff range comments (2)
components/dataforseo/dataforseo.app.mjs (2)

12-21: Fix variable misnaming and ensure option value type matches prop type

  • Variable languageCodes is misnamed in the locationCode options loader; it actually holds locations.
  • locationCode prop is typed as a string, but location_code may be numeric. Cast to string to avoid type mismatch issues in the UI/runtime.
  • Add null-safe access to avoid crashes if the response shape changes or is empty.

Apply this diff:

-      async options() {
-        const response = await this.getLocations();
-        const languageCodes = response.tasks[0].result;
-        return languageCodes.map(({
-          location_name, location_code,
-        }) => ({
-          value: location_code,
-          label: location_name,
-        }));
-      },
+      async options() {
+        const response = await this.getLocations();
+        const results = response?.tasks?.[0]?.result ?? [];
+        return results.map(({ location_name, location_code }) => ({
+          value: String(location_code),
+          label: location_name,
+        }));
+      },

164-168: Close the parenthesis and add a period in the Targets description

User-facing copy has an unmatched parenthesis.

Apply this diff:

-      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`",
+      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",
♻️ Duplicate comments (2)
components/dataforseo/dataforseo.app.mjs (2)

42-42: Add a period at the end of the backlinks target description

Parenthesis was fixed previously; just add the final period to keep user-facing copy polished.

Apply this diff:

-      description: "Domain, subdomain or webpage to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`)",
+      description: "Domain, subdomain or webpage to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",

119-135: Descriptions say “Default is true” but no defaults are set

Align runtime behavior with documentation by setting default: true on all three boolean props.

Apply this diff:

     includeSubdomains: {
       type: "boolean",
       label: "Include Subdomains",
       description: "Whether the subdomains of the `target` will be included in the search. Default is `true`",
+      default: true,
       optional: true,
     },
     includeIndirectLinks: {
       type: "boolean",
       label: "Include Indirect Links",
       description: "Whether indirect links to the target will be included in the results. Default is `true`",
+      default: true,
       optional: true,
     },
     excludeInternalBacklinks: {
       type: "boolean",
       label: "Exclude Internal Backlinks",
       description: "Indicates if internal backlinks from subdomains to the target will be excluded from the results. Default is `true`",
+      default: true,
       optional: true,
     },
🧹 Nitpick comments (7)
components/dataforseo/dataforseo.app.mjs (7)

37-38: Tighten up the Target description (consistency and readability)

Use “URL” and keep formatting consistent with other props.

Apply this diff:

-      description: "The domain name or the url of the target website or page. The domain should be specified without https:// and www.",
+      description: "The domain name or the URL of the target website or page. The domain should be specified without `https://` and `www`.",

79-87: Harden the languageCode options loader with null-safe access

Avoid potential runtime errors when tasks or result are absent.

Apply this diff:

-        const response = await this.getLanguageCode();
-        const languageCodes = response.tasks[0].result;
-        return languageCodes.map(({
+        const response = await this.getLanguageCode();
+        const languageCodes = response?.tasks?.[0]?.result ?? [];
+        return languageCodes.map(({
           language_name, language_code,
         }) => ({
           value: language_code,
           label: language_name,
         }));

210-215: Deduplicate language endpoints: make getLanguageCode an alias

Both getLanguageCode and getGoogleAdsLanguages hit the same endpoint. Prefer a single source of truth.

Apply this diff:

-    getLanguageCode(args = {}) {
-      return this._makeRequest({
-        path: "/keywords_data/google_ads/languages",
-        ...args,
-      });
-    },
+    getLanguageCode(args = {}) {
+      // Backwards-compatible alias to `getGoogleAdsLanguages`
+      return this.getGoogleAdsLanguages(args);
+    },

242-249: Validate required parameter for getGoogleAdsLocations

Throw early if countryCode is missing to avoid sending an invalid URL.

Apply this diff:

-    getGoogleAdsLocations({
-      countryCode, ...args
-    }) {
-      return this._makeRequest({
-        path: `/keywords_data/google_ads/locations/${countryCode}`,
-        ...args,
-      });
-    },
+    getGoogleAdsLocations({ countryCode, ...args }) {
+      if (!countryCode) {
+        throw new Error("countryCode is required");
+      }
+      return this._makeRequest({
+        path: `/keywords_data/google_ads/locations/${countryCode}`,
+        ...args,
+      });
+    },

427-447: Add convenience “tasks_ready” getters for review task_post endpoints (consistency)

You added tasks-ready helpers for several Google Ads endpoints. Consider doing the same for Trustpilot/Tripadvisor/Google review tasks for parity.

Follow the existing pattern:

  • trustpilot: GET /business_data/trustpilot/reviews/tasks_ready
  • tripadvisor: GET /business_data/tripadvisor/reviews/tasks_ready
  • google: GET /business_data/google/reviews/tasks_ready

454-474: Consider adding tasks_ready helpers for App Store / Google Play tasks

These use task_post; providing corresponding tasks_ready helpers would simplify client actions, similar to your Google Ads helpers.

Suggested additions:

  • getAppStoreSearchCompletedTasks: /app_data/apple/app_searches/tasks_ready
  • getGooglePlaySearchCompletedTasks: /app_data/google/app_searches/tasks_ready
  • getAppReviewsSummaryCompletedTasks: /app_data/apple/app_reviews/tasks_ready

69-73: Enforce numeric bounds on limit prop
Add min and max to the integer prop so it can’t exceed the documented “Maximum: 1000.”

• File: components/dataforseo/dataforseo.app.mjs (around lines 69–73)
• Prop: limit

Suggested diff:

       label: "Limit",
       description: "The maximum number of results to return. Maximum: 1000",
+      min: 1,
+      max: 1000,
       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 8ab8b55 and af64fbb.

📒 Files selected for processing (4)
  • components/dataforseo/actions/get-app-intersection/get-app-intersection.mjs (1 hunks)
  • components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1 hunks)
  • components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (1 hunks)
  • components/dataforseo/dataforseo.app.mjs (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • components/dataforseo/actions/get-app-intersection/get-app-intersection.mjs
  • components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs
  • components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-08T16:42:59.225Z
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14229
File: components/americommerce/actions/update-customer/update-customer.mjs:89-94
Timestamp: 2024-10-08T16:42:59.225Z
Learning: When defining boolean properties in AmeriCommerce components (e.g., in `update-customer.mjs`), ensure that the label and description are consistent and clearly indicate the intent, especially when using negations like "No Account", to avoid confusion.

Applied to files:

  • components/dataforseo/dataforseo.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). (3)
  • GitHub Check: Lint Code Base
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
🔇 Additional comments (5)
components/dataforseo/dataforseo.app.mjs (5)

101-111: LGTM: Rank scale defaults and options read clearly

The options and default value are sensible and match the description.


137-157: LGTM: Backlinks status type options and default

Clear options with a reasonable default of “live”.


174-188: LGTM: Non-async _makeRequest is fine; axios already returns a Promise

Signature simplification is OK and keeps auth centralized.


222-496: LGTM: Consistent method expansion and endpoint coverage

The new methods follow a uniform pattern, align with DataForSEO paths, and clearly separate live vs task-based endpoints. Naming is consistent and easy to reason about.


216-221: Confirm you’re using the right DataForSEO locations endpoint
Your getLocations call currently uses the SERP Ads Search endpoint (/serp/google/ads_search/locations), which is correct if you need location codes for Google Ads SERP Ads Search.
If your goal is to fetch location codes for Google Ads Keywords Data (keyword metrics, planning, etc.), you should instead call:

/keywords_data/google_ads/locations

Using the wrong endpoint can lead to code mismatches or runtime errors. Please verify your intended use case and update accordingly.

• File: components/dataforseo/dataforseo.app.mjs
Lines: 216–221 (getLocations)

lcaresia
lcaresia previously approved these changes Aug 19, 2025
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: 4

Caution

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

⚠️ Outside diff range comments (1)
components/dataforseo/dataforseo.app.mjs (1)

164-168: Close the parenthesis in the targets description.

Minor documentation typo in a user-facing field.

Apply this diff:

-      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`",
+      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",
♻️ Duplicate comments (4)
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (2)

15-16: Fix time format in descriptions (use hh:mm:ss, not hh-mm-ss)

Use the correct time separator and keep wording consistent.

-      description: "The start time for filtering results in the format \"yyyy-mm-dd hh-mm-ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. If include_metadata is set to true, minimum value: a month before the current datetime. If include_metadata is set to false, minimum value: six months before the current datetime",
+      description: "The start time for filtering results in the format \"yyyy-mm-dd hh:mm:ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. If include_metadata is set to true, minimum value: a month before the current datetime. If include_metadata is set to false, minimum value: six months before the current datetime",
@@
-      description: "The finish time for filtering results in the format \"yyyy-mm-dd hh-mm-ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`",
+      description: "The finish time for filtering results in the format \"yyyy-mm-dd hh:mm:ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`",

Also applies to: 20-21


85-91: Harden error handling against missing/empty tasks

Avoid accessing response.tasks[0] without verifying existence.

-    if (response.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.status_message}`);
-    }
-
-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (response?.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response?.status_message || "Unexpected API response"}`);
+    }
+    const task = response.tasks?.[0];
+    if (!task || task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task?.status_message || "No task result returned"}`);
+    }
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1)

15-21: Fix datetime format in descriptions (prior feedback still pending).

The descriptions still show hh-mm-ss (hyphens) while your validation error message expects hh:mm:ss (colons). Aligning both avoids confusion.

Apply this diff:

-      description: "The start time for filtering results in the format "yyyy-mm-dd hh-mm-ss +00:00". Example: `2023-01-15 12:57:46 +00:00`. Allows filtering results by the datetime parameter within the range of the last 7 days.",
+      description: "The start time for filtering results in the format \"yyyy-mm-dd hh:mm:ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. Allows filtering results by the datetime parameter within the range of the last 7 days.",
...
-      description: "The finish time for filtering results in the format "yyyy-mm-dd hh-mm-ss +00:00". Example: `2023-01-15 12:57:46 +00:00`. Allows filtering results by the datetime parameter within the range of the last 7 days.",
+      description: "The finish time for filtering results in the format \"yyyy-mm-dd hh:mm:ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. Allows filtering results by the datetime parameter within the range of the last 7 days.",
components/dataforseo/dataforseo.app.mjs (1)

119-135: Boolean props state “Default is true” but don’t set default (prior feedback still pending).

Set default: true to match the description and avoid surprises.

Apply this diff:

     includeSubdomains: {
       type: "boolean",
       label: "Include Subdomains",
       description: "Whether the subdomains of the `target` will be included in the search. Default is `true`",
+      default: true,
       optional: true,
     },
     includeIndirectLinks: {
       type: "boolean",
       label: "Include Indirect Links",
       description: "Whether indirect links to the target will be included in the results. Default is `true`",
+      default: true,
       optional: true,
     },
     excludeInternalBacklinks: {
       type: "boolean",
       label: "Exclude Internal Backlinks",
       description: "Indicates if internal backlinks from subdomains to the target will be excluded from the results. Default is `true`",
+      default: true,
       optional: true,
     },
🧹 Nitpick comments (12)
components/dataforseo/common/utils.mjs (1)

9-12: Optionally avoid hard failure on malformed JSON input in parseObjectEntries

Current code will throw if value is a string that isn’t valid JSON. If you prefer resilience, use optionalParseAsJSON here too and guard against non-objects.

-export function parseObjectEntries(value = {}) {
-  const obj = typeof value === "string"
-    ? JSON.parse(value)
-    : value;
+export function parseObjectEntries(value = {}) {
+  const obj = typeof value === "string"
+    ? optionalParseAsJSON(value)
+    : value;
+  const safeObj = obj && typeof obj === "object" ? obj : {};
-  return Object.fromEntries(
-    Object.entries(obj).map(([
+  return Object.fromEntries(
+    Object.entries(safeObj).map(([
       key,
       value,
     ]) => [
       key,
       optionalParseAsJSON(value),
     ]),
   );
 }
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (1)

53-59: Guard against missing/empty tasks in API response

Avoid accessing response.tasks[0] without checking it exists.

-    if (response.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.status_message}`);
-    }
-
-    if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (response?.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response?.status_message || "Unexpected API response"}`);
+    }
+    const task = response.tasks?.[0];
+    if (!task || (task.status_code !== 20000 && task.status_code !== 20100)) {
+      throw new ConfigurationError(`Error: ${task?.status_message || "No task result returned"}`);
+    }
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (2)

10-29: Props look good for keyword + location_coordinate flow; consider supporting direct business identifiers

Many users also have place_id/cid/g_cid/data_id to target a specific business without keyword + location coordinate. Consider optional mutually exclusive props for those identifiers to broaden applicability.

If you’d like, I can draft a diff adding optional props (placeId, cid, gCid, dataId) and select the first provided to send to the API.


42-48: Null-safe error handling for response and tasks

Prevent potential runtime errors when tasks is empty or undefined.

-    if (response.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.status_message}`);
-    }
-
-    if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (response?.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response?.status_message || "Unexpected API response"}`);
+    }
+    const task = response.tasks?.[0];
+    if (!task || (task.status_code !== 20000 && task.status_code !== 20100)) {
+      throw new ConfigurationError(`Error: ${task?.status_message || "No task result returned"}`);
+    }
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (1)

46-49: Normalize to ISO 8601 before Date parsing to avoid engine-dependent failures

The provided format with a space (yyyy-mm-dd hh:mm:ss +00:00) isn’t reliably parsed by JS Date across environments. Normalize to ISO by inserting “T”.

-    validateDateRange(dateTimeFrom, dateTimeTo, includeMetadata) {
-      const from = new Date(dateTimeFrom);
-      const to = new Date(dateTimeTo);
+    validateDateRange(dateTimeFrom, dateTimeTo, includeMetadata) {
+      const toIso = (s) => (typeof s === "string" ? s.replace(" ", "T") : s);
+      const from = new Date(toIso(dateTimeFrom));
+      const to = new Date(toIso(dateTimeTo));
components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (1)

42-48: Add null-safe checks for robustness

Prevent runtime errors if tasks is missing/empty.

-    if (response.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.status_message}`);
-    }
-
-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (response?.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response?.status_message || "Unexpected API response"}`);
+    }
+    const task = response.tasks?.[0];
+    if (!task || task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task?.status_message || "No task result returned"}`);
+    }
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (2)

66-72: Guard against missing tasks to avoid runtime errors.

Accessing response.tasks[0] can throw if tasks is empty or undefined. Add a defensive check.

Apply this diff:

-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("No tasks returned in response");
+    }
+    if (task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

52-64: Consider DRYing date validation across actions.

Other actions (e.g., get-keyword-data-id-list) implement similar date-range validation. Centralize this helper (e.g., components/dataforseo/common/date.mjs) and import it to reduce duplication and inconsistencies.

I can factor out a shared validator and update all usages in this PR if you want.

components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (3)

73-75: Guard against missing tasks in the initial response.

Avoid response.tasks[0] access if tasks is empty or undefined.

Apply this diff:

-      if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-        throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-      }
+      const task = response.tasks?.[0];
+      if (!task) {
+        throw new ConfigurationError("No tasks returned in response");
+      }
+      if (![20000, 20100].includes(task.status_code)) {
+        throw new ConfigurationError(`Error: ${task.status_message}`);
+      }

53-56: Nit: remove extra space in assignment.

Minor formatting for readability.

Apply this diff:

-      let postbackUrl  = this.postbackUrl;
+      let postbackUrl = this.postbackUrl;

36-41: Clarify postback URL behavior.

Description says it’s only applicable when “Wait for Results” = FALSE, but the code sets a postback_url to the Pipedream resume URL when waiting = TRUE (overriding any user-provided value). Consider clarifying the description to reflect this behavior.

Apply this diff:

-      description: "The URL to receive the search results. Only applicable when \"Wait for Results\" = `FALSE`",
+      description: "Optional custom URL to receive results when \"Wait for Results\" = `FALSE`. When \"Wait for Results\" = `TRUE`, this action will set a Pipedream resume URL automatically and ignore any value provided here.",
components/dataforseo/dataforseo.app.mjs (1)

13-21: Nit: rename local var to reflect locations, not language codes.

Using languageCodes in the locations options is confusing.

Apply this diff:

-        const languageCodes = response.tasks[0].result;
-        return languageCodes.map(({
+        const locations = response.tasks[0].result;
+        return locations.map(({
           location_name, location_code,
         }) => ({
           value: location_code,
           label: location_name,
         }));
📜 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 af64fbb and 8a5824d.

📒 Files selected for processing (18)
  • components/dataforseo/actions/get-app-intersection/get-app-intersection.mjs (1 hunks)
  • components/dataforseo/actions/get-app-reviews-summary/get-app-reviews-summary.mjs (1 hunks)
  • components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (1 hunks)
  • components/dataforseo/actions/get-bing-organic-results/get-bing-organic-results.mjs (1 hunks)
  • components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (1 hunks)
  • components/dataforseo/actions/get-google-my-business-info/get-google-my-business-info.mjs (1 hunks)
  • components/dataforseo/actions/get-google-news-results/get-google-news-results.mjs (1 hunks)
  • components/dataforseo/actions/get-google-organic-results/get-google-organic-results.mjs (1 hunks)
  • components/dataforseo/actions/get-google-play-search/get-google-play-search.mjs (1 hunks)
  • components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1 hunks)
  • components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1 hunks)
  • components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (1 hunks)
  • components/dataforseo/actions/get-sentiment-analysis/get-sentiment-analysis.mjs (1 hunks)
  • components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (1 hunks)
  • components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs (1 hunks)
  • components/dataforseo/actions/get-yahoo-organic-results/get-yahoo-organic-results.mjs (1 hunks)
  • components/dataforseo/common/utils.mjs (1 hunks)
  • components/dataforseo/dataforseo.app.mjs (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • components/dataforseo/actions/get-google-organic-results/get-google-organic-results.mjs
  • components/dataforseo/actions/get-google-my-business-info/get-google-my-business-info.mjs
  • components/dataforseo/actions/get-google-news-results/get-google-news-results.mjs
  • components/dataforseo/actions/get-sentiment-analysis/get-sentiment-analysis.mjs
  • components/dataforseo/actions/get-yahoo-organic-results/get-yahoo-organic-results.mjs
  • components/dataforseo/actions/get-bing-organic-results/get-bing-organic-results.mjs
  • components/dataforseo/actions/get-google-play-search/get-google-play-search.mjs
  • components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs
  • components/dataforseo/actions/get-app-intersection/get-app-intersection.mjs
  • components/dataforseo/actions/get-app-reviews-summary/get-app-reviews-summary.mjs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-08T16:42:59.225Z
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14229
File: components/americommerce/actions/update-customer/update-customer.mjs:89-94
Timestamp: 2024-10-08T16:42:59.225Z
Learning: When defining boolean properties in AmeriCommerce components (e.g., in `update-customer.mjs`), ensure that the label and description are consistent and clearly indicate the intent, especially when using negations like "No Account", to avoid confusion.

Applied to files:

  • components/dataforseo/dataforseo.app.mjs
🧬 Code Graph Analysis (6)
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1)
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (3)
  • from (47-47)
  • to (48-48)
  • response (72-83)
components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (3)
components/dataforseo/dataforseo.app.mjs (2)
  • response (13-13)
  • response (79-79)
components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-google-play-search/get-google-play-search.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (1)
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (3)
  • from (31-31)
  • to (32-32)
  • response (55-64)
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (3)
components/dataforseo/dataforseo.app.mjs (2)
  • response (13-13)
  • response (79-79)
components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-google-my-business-info/get-google-my-business-info.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (1)
components/dataforseo/dataforseo.app.mjs (2)
  • response (13-13)
  • response (79-79)
components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (2)
components/dataforseo/dataforseo.app.mjs (2)
  • response (13-13)
  • response (79-79)
components/dataforseo/actions/get-google-news-results/get-google-news-results.mjs (1)
  • response (31-40)
⏰ 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). (3)
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base
🔇 Additional comments (9)
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1)

30-40: LGTM overall

Action shape, mapping, and acceptance of 20100 for task_post endpoints are consistent with the rest of the suite.

Also applies to: 50-52

components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (1)

69-83: Solid structure and validation strategy

Nice internal validateDateRange helper and clear $summary. Mapping to API fields looks correct.

Also applies to: 93-96

components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (1)

10-29: Well-structured action; aligns with live/advanced endpoint

Props, payload mapping, and single 20000 task status check (live endpoint) look good.

Also applies to: 31-40, 50-52

components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1)

74-76: LGTM: Clear summary and return of full API response.

The $summary and return shape follow the established pattern in this integration.

components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (1)

78-84: LGTM: Async callback handling and clear summary.

The run.runs pattern and summary export are consistent with other actions.

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

42-43: Backlinks target description parentheses now correct — thanks.

The missing closing parenthesis issue is resolved here.


69-73: Limit prop: integer type and description look good.

This aligns with usage across actions and the API’s expectations.


210-221: Non-async wrappers returning Promises via axios look good.

Switching to non-async methods is fine since _makeRequest returns a Promise; callers remain unaffected.


8-12: No lingering string-typed definitions or casts for locationCode or limit

The repo-wide search shows:

  • No type: "string" definitions for locationCode or limit.
  • No manual String(...) or .toString() casts used when passing these props.

All downstream components/actions already expect integers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
components/dataforseo/dataforseo.app.mjs (1)

167-168: Close the parenthesis in targets description and end the sentence

The user-facing description is missing a closing parenthesis and period.

-      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`",
+      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",
♻️ Duplicate comments (7)
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (2)

15-21: Fix datetime format in user-facing descriptions (hh:mm:ss, not hh-mm-ss)

Align with the example and standard formatting.

-      description: "The start time for filtering results in the format \"yyyy-mm-dd hh-mm-ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. If include_metadata is set to true, minimum value: a month before the current datetime. If include_metadata is set to false, minimum value: six months before the current datetime",
+      description: "The start time for filtering results in the format \"yyyy-mm-dd hh:mm:ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. If include_metadata is set to true, minimum value: a month before the current datetime. If include_metadata is set to false, minimum value: six months before the current datetime",
...
-      description: "The finish time for filtering results in the format \"yyyy-mm-dd hh-mm-ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`",
+      description: "The finish time for filtering results in the format \"yyyy-mm-dd hh:mm:ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`",

85-91: Harden error handling against missing/empty tasks

Avoid direct indexing of tasks[0]; this can throw if tasks is missing.

-    if (response.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.status_message}`);
-    }
-
-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (response?.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response?.status_message || "Unexpected API response"}`);
+    }
+    const task = response.tasks?.[0];
+    if (!task || task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task?.status_message || "No task result returned"}`);
+    }
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1)

15-21: Fix datetime format in user-facing descriptions (hh:mm:ss, not hh-mm-ss)

Match the example and standard time separator.

-      description: "The start time for filtering results in the format \"yyyy-mm-dd hh-mm-ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. Allows filtering results by the datetime parameter within the range of the last 7 days.",
+      description: "The start time for filtering results in the format \"yyyy-mm-dd hh:mm:ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. Allows filtering results by the datetime parameter within the range of the last 7 days.",
...
-      description: "The finish time for filtering results in the format \"yyyy-mm-dd hh-mm-ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. Allows filtering results by the datetime parameter within the range of the last 7 days.",
+      description: "The finish time for filtering results in the format \"yyyy-mm-dd hh:mm:ss +00:00\". Example: `2023-01-15 12:57:46 +00:00`. Allows filtering results by the datetime parameter within the range of the last 7 days.",
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (2)

10-38: TripAdvisor reviews/live requires url_path (not keyword/location) and doesn’t support sort_by

The current props don’t match the API. The TripAdvisor “reviews/task_post” endpoint expects a business identifier via url_path; keyword + location_code won’t return reviews directly. Also, sort_by isn’t supported for this endpoint.

Apply this diff to align with the API:

   props: {
     dataforseo,
-    keyword: {
-      type: "string",
-      label: "Keyword",
-      description: "The keyword to search for",
-    },
-    locationCode: {
-      propDefinition: [
-        dataforseo,
-        "locationCode",
-      ],
-    },
+    urlPath: {
+      type: "string",
+      label: "TripAdvisor URL Path",
+      description: "The `url_path` portion of the TripAdvisor URL that uniquely identifies the business (e.g., \"Hotel_Review-g60763-d23462501-Reviews-Margaritaville_Times_Square-New_York_City_New_York.html\").",
+    },
     languageCode: {
       propDefinition: [
         dataforseo,
         "languageCode",
       ],
     },
-    sortBy: {
-      type: "string",
-      label: "Sort By",
-      description: "Sort reviews by specific criteria",
-      options: [
-        "most_recent",
-        "detailed_reviews",
-      ],
-      optional: true,
-    },
+    translateReviews: {
+      type: "boolean",
+      label: "Translate Reviews",
+      description: "Translate reviews to the specified language when available. Default is `true`.",
+      default: true,
+      optional: true,
+    },
   },

41-51: Use url_path and translate_reviews in payload; remove unsupported params

Map props to the correct request body fields and drop unsupported ones.

     const response = await this.dataforseo.getTripadvisorReviews({
       $,
       data: [
         {
-          keyword: this.keyword,
-          location_code: this.locationCode,
-          language_code: this.languageCode,
-          sort_by: this.sortBy,
+          url_path: this.urlPath,
+          language_code: this.languageCode,
+          translate_reviews: this.translateReviews,
         },
       ],
     });
components/dataforseo/dataforseo.app.mjs (2)

42-43: Fix minor punctuation in backlinksTarget description

Close the sentence with a period.

-      description: "Domain, subdomain or webpage to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`)",
+      description: "Domain, subdomain or webpage to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",

121-135: Boolean props state “Default is true” but don’t set a default

Set default: true to match the description and avoid surprising behavior.

     includeSubdomains: {
       type: "boolean",
       label: "Include Subdomains",
       description: "Whether the subdomains of the `target` will be included in the search. Default is `true`",
+      default: true,
       optional: true,
     },
     includeIndirectLinks: {
       type: "boolean",
       label: "Include Indirect Links",
       description: "Whether indirect links to the target will be included in the results. Default is `true`",
+      default: true,
       optional: true,
     },
     excludeInternalBacklinks: {
       type: "boolean",
       label: "Exclude Internal Backlinks",
       description: "Indicates if internal backlinks from subdomains to the target will be excluded from the results. Default is `true`",
+      default: true,
       optional: true,
     },
🧹 Nitpick comments (10)
components/dataforseo/common/utils.mjs (1)

24-30: Normalize parseArray output and avoid over-eager falsy checks

Current logic treats 0/false as “empty” and returns [], and may return non-arrays. Normalize to arrays, treat only nullish/empty string as empty, and guard JSON.parse with a CSV fallback.

-export function parseArray(value) {
-  if (!value) return [];
-  if (typeof value === "string") {
-    return JSON.parse(value);
-  }
-  return value;
-}
+export function parseArray(value) {
+  // Treat only null/undefined/empty-string as empty
+  if (value == null || value === "") return [];
+  if (typeof value === "string") {
+    try {
+      const parsed = JSON.parse(value);
+      return Array.isArray(parsed) ? parsed : [parsed];
+    } catch {
+      // Fallback: simple CSV string input
+      return value.split(",").map((s) => s.trim()).filter(Boolean);
+    }
+  }
+  // Ensure array output
+  return Array.isArray(value) ? value : [value];
+}
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1)

42-48: Harden error handling against missing/empty tasks

Avoid potential TypeError when tasks is missing or empty and improve messages.

-    if (response.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.status_message}`);
-    }
-
-    if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (response?.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response?.status_message || "Unexpected API response"}`);
+    }
+    const task = response.tasks?.[0];
+    if (!task || (task.status_code !== 20000 && task.status_code !== 20100)) {
+      throw new ConfigurationError(`Error: ${task?.status_message || "No task result returned"}`);
+    }
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (2)

46-52: Make date parsing deterministic across runtimes

new Date("yyyy-mm-dd hh:mm:ss +00:00") isn’t guaranteed portable. Normalize to ISO 8601 before parsing to avoid false “Invalid date” errors.

-    validateDateRange(dateTimeFrom, dateTimeTo, includeMetadata) {
-      const from = new Date(dateTimeFrom);
-      const to = new Date(dateTimeTo);
+    validateDateRange(dateTimeFrom, dateTimeTo, includeMetadata) {
+      const from = new Date(this.toIso8601(dateTimeFrom));
+      const to = new Date(this.toIso8601(dateTimeTo));
 
-      if (isNaN(from) || isNaN(to)) {
-        throw new ConfigurationError("Invalid date format. Use yyyy-mm-dd hh:mm:ss +00:00");
+      if (Number.isNaN(from.getTime()) || Number.isNaN(to.getTime())) {
+        throw new ConfigurationError("Invalid date format. Use yyyy-mm-dd hh:mm:ss +00:00");
       }

Add this helper inside the methods object (outside the selected lines):

toIso8601(s) {
  // "2023-01-15 12:57:46 +00:00" -> "2023-01-15T12:57:46+00:00"
  return String(s).replace(" ", "T").replace(" +", "+");
}

64-66: Also validate DateTimeTo against the minimum date window

Docs imply the entire range should be within the allowed window. Currently, only DateTimeFrom is checked.

-      if (from < minDate) {
-        throw new ConfigurationError(`DateTimeFrom must not be earlier than ${monthsBack} month(s) ago`);
-      }
+      if (from < minDate) {
+        throw new ConfigurationError(`DateTimeFrom must not be earlier than ${monthsBack} month(s) ago`);
+      }
+      if (to < minDate) {
+        throw new ConfigurationError(`DateTimeTo must not be earlier than ${monthsBack} month(s) ago`);
+      }
components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs (1)

39-45: Harden error handling against missing/empty tasks

Use null-safe access and clearer messages to avoid TypeError when tasks is empty.

-    if (response.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.status_message}`);
-    }
-
-    if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (response?.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response?.status_message || "Unexpected API response"}`);
+    }
+    const task = response.tasks?.[0];
+    if (!task || (task.status_code !== 20000 && task.status_code !== 20100)) {
+      throw new ConfigurationError(`Error: ${task?.status_message || "No task result returned"}`);
+    }
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (2)

31-37: Make date parsing deterministic across runtimes

Normalize to ISO 8601 before parsing to avoid runtime-specific failures.

-    validateDateRange(dateTimeFrom, dateTimeTo) {
-      const from = new Date(dateTimeFrom);
-      const to = new Date(dateTimeTo);
+    validateDateRange(dateTimeFrom, dateTimeTo) {
+      const from = new Date(this.toIso8601(dateTimeFrom));
+      const to = new Date(this.toIso8601(dateTimeTo));
       const now = new Date();
 
-      if (isNaN(from) || isNaN(to)) {
+      if (Number.isNaN(from.getTime()) || Number.isNaN(to.getTime())) {
         throw new ConfigurationError("Invalid date format. Use yyyy-mm-dd hh:mm:ss +00:00");
       }

Add this helper inside the methods object (outside the selected lines):

toIso8601(s) {
  // "2023-01-15 12:57:46 +00:00" -> "2023-01-15T12:57:46+00:00"
  return String(s).replace(" ", "T").replace(" +", "+");
}

66-72: Harden error handling against missing/empty tasks

Prevent potential crashes and improve diagnostics.

-    if (response.status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.status_message}`);
-    }
-
-    if (response.tasks[0].status_code !== 20000) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    if (response?.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${response?.status_message || "Unexpected API response"}`);
+    }
+    const task = response.tasks?.[0];
+    if (!task || task.status_code !== 20000) {
+      throw new ConfigurationError(`Error: ${task?.status_message || "No task result returned"}`);
+    }
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (2)

53-59: Guard against missing tasks array before dereferencing

Defensive check prevents runtime errors when response has no tasks.

-    if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-      throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-    }
+    const task = response.tasks?.[0];
+    if (!task) {
+      throw new ConfigurationError("Error: No task information returned by DataForSEO.");
+    }
+    if (task.status_code !== 20000 && task.status_code !== 20100) {
+      throw new ConfigurationError(`Error: ${task.status_message}`);
+    }

61-62: Update summary to reflect url_path

-    $.export("$summary", `Successfully retrieved TripAdvisor reviews for "${this.keyword}".`);
+    $.export("$summary", `Successfully retrieved TripAdvisor reviews for "${this.urlPath}".`);
components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (1)

69-76: Optional: Harden task access for initial request

A small guard makes the code more robust if the API shape changes.

-      if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-        throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-      }
+      const task = response.tasks?.[0];
+      if (!task) {
+        throw new ConfigurationError("Error: No task information returned by DataForSEO.");
+      }
+      if (task.status_code !== 20000 && task.status_code !== 20100) {
+        throw new ConfigurationError(`Error: ${task.status_message}`);
+      }
📜 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 af64fbb and 8a5824d.

📒 Files selected for processing (18)
  • components/dataforseo/actions/get-app-intersection/get-app-intersection.mjs (1 hunks)
  • components/dataforseo/actions/get-app-reviews-summary/get-app-reviews-summary.mjs (1 hunks)
  • components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (1 hunks)
  • components/dataforseo/actions/get-bing-organic-results/get-bing-organic-results.mjs (1 hunks)
  • components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (1 hunks)
  • components/dataforseo/actions/get-google-my-business-info/get-google-my-business-info.mjs (1 hunks)
  • components/dataforseo/actions/get-google-news-results/get-google-news-results.mjs (1 hunks)
  • components/dataforseo/actions/get-google-organic-results/get-google-organic-results.mjs (1 hunks)
  • components/dataforseo/actions/get-google-play-search/get-google-play-search.mjs (1 hunks)
  • components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1 hunks)
  • components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1 hunks)
  • components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (1 hunks)
  • components/dataforseo/actions/get-sentiment-analysis/get-sentiment-analysis.mjs (1 hunks)
  • components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (1 hunks)
  • components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs (1 hunks)
  • components/dataforseo/actions/get-yahoo-organic-results/get-yahoo-organic-results.mjs (1 hunks)
  • components/dataforseo/common/utils.mjs (1 hunks)
  • components/dataforseo/dataforseo.app.mjs (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • components/dataforseo/actions/get-sentiment-analysis/get-sentiment-analysis.mjs
  • components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs
  • components/dataforseo/actions/get-bing-organic-results/get-bing-organic-results.mjs
  • components/dataforseo/actions/get-google-my-business-info/get-google-my-business-info.mjs
  • components/dataforseo/actions/get-google-organic-results/get-google-organic-results.mjs
  • components/dataforseo/actions/get-app-reviews-summary/get-app-reviews-summary.mjs
  • components/dataforseo/actions/get-google-news-results/get-google-news-results.mjs
  • components/dataforseo/actions/get-google-play-search/get-google-play-search.mjs
  • components/dataforseo/actions/get-app-intersection/get-app-intersection.mjs
  • components/dataforseo/actions/get-yahoo-organic-results/get-yahoo-organic-results.mjs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-08T16:42:59.225Z
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14229
File: components/americommerce/actions/update-customer/update-customer.mjs:89-94
Timestamp: 2024-10-08T16:42:59.225Z
Learning: When defining boolean properties in AmeriCommerce components (e.g., in `update-customer.mjs`), ensure that the label and description are consistent and clearly indicate the intent, especially when using negations like "No Account", to avoid confusion.

Applied to files:

  • components/dataforseo/dataforseo.app.mjs
🧬 Code Graph Analysis (6)
components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (1)
components/dataforseo/dataforseo.app.mjs (2)
  • response (13-13)
  • response (79-79)
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (3)
components/dataforseo/actions/get-app-reviews-summary/get-app-reviews-summary.mjs (1)
  • response (41-51)
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs (1)
  • response (29-37)
components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs (3)
components/dataforseo/actions/get-app-reviews-summary/get-app-reviews-summary.mjs (1)
  • response (41-51)
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (1)
  • response (41-51)
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1)
components/dataforseo/dataforseo.app.mjs (2)
  • response (13-13)
  • response (79-79)
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (1)
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (3)
  • from (31-31)
  • to (32-32)
  • response (55-64)
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1)
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (3)
  • from (47-47)
  • to (48-48)
  • response (72-83)
⏰ 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). (3)
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Lint Code Base
🔇 Additional comments (4)
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1)

31-40: Client method and payload mapping verified

  • Found getGoogleReviews(args = {}) in components/dataforseo/dataforseo.app.mjs (lines 441–444), which spreads ...args into the request config.
  • Your action’s call
    await this.dataforseo.getGoogleReviews({
      $,
      data: [
        {
          keyword: this.keyword,
          location_coordinate: this.locationCoordinate,
          language_code: this.languageCode,
        },
      ],
    });
    correctly passes $ and a data array with the expected keys (keyword, location_coordinate, language_code).

No further changes required here.

components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs (1)

29-37: getTrustpilotReviews client method and payload mapping are correct

  • Located getTrustpilotReviews(args = {}) in components/dataforseo/dataforseo.app.mjs (lines 427–432), issuing a POST to /business_data/trustpilot/reviews/task_post and spreading the passed args.
  • Confirmed the action passes a data array with objects containing domain and sort_by, matching the client’s accepted payload.

No changes needed.

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

174-188: Non-async _makeRequest is fine; it returns a Promise

Returning the axios Promise without async/await is idiomatic here and doesn’t break callers using await.


468-474: Method name vs endpoint mismatch: getAppReviewsSummary points to app_reviews endpoint

The method name implies a “summary/overview” endpoint, but the path is for raw reviews. Please confirm the intended endpoint and either rename the method or update the path.

Options:

  • If you want the summary endpoint:
-        path: "/app_data/apple/app_reviews/task_post",
+        path: "/app_data/apple/app_reviews_summary/task_post",
  • If the method should fetch raw reviews, rename it to getAppReviews to avoid confusion.

Also check if you need a Google Play counterpart for parity.

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

Caution

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

⚠️ Outside diff range comments (1)
components/dataforseo/dataforseo.app.mjs (1)

181-186: Close the parenthesis and end the sentence in “targets” description.

User-facing typo; the opening parenthesis before “including” isn’t closed.

-      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`",
+      description: "Up to 1000 domains, subdomains or webpages to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",
♻️ Duplicate comments (5)
components/dataforseo/dataforseo.app.mjs (3)

136-140: Boolean prop says “Default is true” but default is not set (includeSubdomains).

Set default: true to match the description and avoid surprising behavior.

     includeSubdomains: {
       type: "boolean",
       label: "Include Subdomains",
       description: "Whether the subdomains of the `target` will be included in the search. Default is `true`",
+      default: true,
       optional: true,
     },

142-146: Boolean prop says “Default is true” but default is not set (includeIndirectLinks).

Same rationale as above.

     includeIndirectLinks: {
       type: "boolean",
       label: "Include Indirect Links",
       description: "Whether indirect links to the target will be included in the results. Default is `true`",
+      default: true,
       optional: true,
     },

148-152: Boolean prop says “Default is true” but default is not set (excludeInternalBacklinks).

Same rationale as above.

     excludeInternalBacklinks: {
       type: "boolean",
       label: "Exclude Internal Backlinks",
       description: "Indicates if internal backlinks from subdomains to the target will be excluded from the results. Default is `true`",
+      default: true,
       optional: true,
     },
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (2)

12-47: TripAdvisor reviews/task_post requires url_path (not keyword/location_code); remove unsupported sort_by.

The TripAdvisor reviews endpoint expects a business identifier via url_path. keyword + location_code isn’t valid for direct reviews retrieval, and sort_by isn’t supported here. First search to obtain url_path, then request reviews.

Apply this diff to fix props:

   props: {
     dataforseo,
-    keyword: {
-      type: "string",
-      label: "Keyword",
-      description: "The keyword to search for",
-    },
-    countryCode: {
-      type: "string",
-      label: "Country Code",
-      description: "The country code of the target location. Ex: `US`",
-    },
-    locationCode: {
-      propDefinition: [
-        dataforseo,
-        "tripAdvisorLocationCode",
-        (c) => ({
-          countryCode: c.countryCode,
-        }),
-      ],
-    },
+    urlPath: {
+      type: "string",
+      label: "TripAdvisor URL Path",
+      description: "Path segment that uniquely identifies the business on TripAdvisor, e.g. \"Hotel_Review-g60763-d23462501-Reviews-Margaritaville_Times_Square-New_York_City_New_York.html\"",
+    },
     languageCode: {
       propDefinition: [
         dataforseo,
         "languageCode",
       ],
       optional: true,
     },
-    sortBy: {
-      type: "string",
-      label: "Sort By",
-      description: "Sort reviews by specific criteria",
-      options: [
-        "most_recent",
-        "detailed_reviews",
-      ],
-      optional: true,
-    },
+    translateReviews: {
+      type: "boolean",
+      label: "Translate Reviews",
+      description: "Translate reviews to the specified language when available",
+      optional: true,
+      default: true,
+    },

71-87: Send url_path (and translate_reviews) to the API; drop unsupported fields.

Request payload currently sends keyword, location_code, and sort_by, which won’t return results for reviews. Use url_path and translate_reviews.

-      response = await this.dataforseo.getTripadvisorReviews({
+      response = await this.dataforseo.getTripadvisorReviews({
         $,
         data: [
           {
-            keyword: this.keyword,
-            location_code: this.locationCode,
-            language_code: this.languageCode,
-            sort_by: this.sortBy,
+            url_path: this.urlPath,
+            language_code: this.languageCode,
+            translate_reviews: this.translateReviews,
             postback_url: postbackUrl,
           },
         ],
       });
🧹 Nitpick comments (9)
components/dataforseo/dataforseo.app.mjs (6)

12-21: Rename misnamed variable to avoid confusion (locations vs languageCodes).

The options() for locationCode stores the locations array in a variable named languageCodes. This is misleading and increases cognitive load when reading the code.

Apply this diff:

-        const languageCodes = response.tasks[0].result;
-        return languageCodes.map(({
+        const locations = response.tasks[0].result;
+        return locations.map(({
           location_name, location_code,
         }) => ({
           value: location_code,
           label: location_name,
         }));

23-39: Disambiguate label to “TripAdvisor Location Code”.

Both locationCode and tripAdvisorLocationCode render with the label “Location Code”, which can be confusing in actions that include multiple selectors. Rename the label here for clarity.

-      label: "Location Code",
+      label: "TripAdvisor Location Code",

54-55: Unify formatting for protocol/domain guidance in “target” description.

Backticks are used elsewhere (backlinksTarget). Align formatting and clarify page vs domain input.

-      description: "The domain name or the url of the target website or page. The domain should be specified without https:// and www.",
+      description: "The domain name or the URL of the target website or page. A domain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",

59-60: Add terminal period for consistency in user-facing text.

Minor copy nit to keep descriptions consistent.

-      description: "Domain, subdomain or webpage to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`)",
+      description: "Domain, subdomain or webpage to get data for. A domain or a subdomain should be specified without `https://` and `www`. A page should be specified with absolute URL (including `http://` or `https://`).",

86-90: Consider enforcing max limit at call sites to prevent rejected tasks.

You document “Maximum: 1000” but there’s no enforcement. To avoid API rejections, clamp the limit in actions (before calling _makeRequest) or add validation to props where supported.

If Pipedream props support numeric constraints, add max: 1000 here. Otherwise, add runtime validation in actions that accept limit. Also verify DataForSEO’s per-endpoint max as some differ slightly.


191-205: Centralize API error shaping for DX and easier debugging.

Currently, _makeRequest returns raw axios responses. Consider catching DataForSEO error payloads and rethrowing with status_code/status_message to surface actionable error info in Pipedream UI.

-      return axios($, {
-        ...otherOpts,
-        url: this._baseUrl() + path,
-        auth: {
-          username: `${this.$auth.api_login}`,
-          password: `${this.$auth.api_password}`,
-        },
-      });
+      return axios($, {
+        ...otherOpts,
+        url: this._baseUrl() + path,
+        auth: {
+          username: `${this.$auth.api_login}`,
+          password: `${this.$auth.api_password}`,
+        },
+      }).catch((err) => {
+        const res = err.response?.data;
+        const status = res?.status_code;
+        const message = res?.status_message || err.message;
+        const details = res?.tasks?.[0]?.result || res?.tasks?.[0]?.status_message;
+        const enriched = new Error(`[DataForSEO] ${message}${status ? ` (code ${status})` : ""}${details ? ` — ${JSON.stringify(details).slice(0, 500)}` : ""}`);
+        enriched.cause = err;
+        throw enriched;
+      });
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (3)

8-8: Bump version due to breaking prop changes.

Switching from keyword/location_code to url_path is a breaking change. Bump version to at least 0.1.0.

-  version: "0.0.1",
+  version: "0.1.0",

89-95: Guard against missing tasks array to avoid runtime errors.

If the API returns a non-standard error payload, response.tasks[0] can throw. Add defensive checks.

-      if (response.tasks[0].status_code !== 20000 && response.tasks[0].status_code !== 20100) {
-        throw new ConfigurationError(`Error: ${response.tasks[0].status_message}`);
-      }
+      const firstTask = response.tasks?.[0];
+      if (!firstTask) {
+        throw new ConfigurationError("Error: Missing task metadata in response.");
+      }
+      if (firstTask.status_code !== 20000 && firstTask.status_code !== 20100) {
+        throw new ConfigurationError(`Error: ${firstTask.status_message}`);
+      }

102-103: Update summary (no longer using keyword).

After switching to url_path, referencing this.keyword is incorrect.

-    $.export("$summary", `Successfully retrieved TripAdvisor reviews for "${this.keyword}".`);
-    return response;
+    $.export("$summary", "Successfully retrieved TripAdvisor reviews.");
+    return response;
📜 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 8a5824d and d8e4e1f.

📒 Files selected for processing (2)
  • components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (1 hunks)
  • components/dataforseo/dataforseo.app.mjs (8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-08T16:42:59.225Z
Learnt from: GTFalcao
PR: PipedreamHQ/pipedream#14229
File: components/americommerce/actions/update-customer/update-customer.mjs:89-94
Timestamp: 2024-10-08T16:42:59.225Z
Learning: When defining boolean properties in AmeriCommerce components (e.g., in `update-customer.mjs`), ensure that the label and description are consistent and clearly indicate the intent, especially when using negations like "No Account", to avoid confusion.

Applied to files:

  • components/dataforseo/dataforseo.app.mjs
🧬 Code graph analysis (2)
components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (2)
components/dataforseo/dataforseo.app.mjs (3)
  • response (13-13)
  • response (28-30)
  • response (96-96)
components/dataforseo/actions/get-app-store-search/get-app-store-search.mjs (4)
  • response (44-44)
  • context (45-45)
  • run (46-50)
  • postbackUrl (53-53)
components/dataforseo/dataforseo.app.mjs (14)
components/dataforseo/actions/get-app-reviews-summary/get-app-reviews-summary.mjs (1)
  • response (41-51)
components/dataforseo/actions/get-app-intersection/get-app-intersection.mjs (1)
  • response (45-56)
components/dataforseo/actions/get-bing-organic-results/get-bing-organic-results.mjs (1)
  • response (45-56)
components/dataforseo/actions/get-google-images-results/get-google-images-results.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-google-my-business-info/get-google-my-business-info.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-google-play-search/get-google-play-search.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-google-news-results/get-google-news-results.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-google-organic-results/get-google-organic-results.mjs (1)
  • response (45-56)
components/dataforseo/actions/get-google-reviews/get-google-reviews.mjs (1)
  • response (31-40)
components/dataforseo/actions/get-keyword-data-errors/get-keyword-data-errors.mjs (1)
  • response (55-64)
components/dataforseo/actions/get-sentiment-analysis/get-sentiment-analysis.mjs (1)
  • response (19-26)
components/dataforseo/actions/get-trustpilot-reviews/get-trustpilot-reviews.mjs (1)
  • response (29-37)
components/dataforseo/actions/get-keyword-data-id-list/get-keyword-data-id-list.mjs (1)
  • response (72-83)
components/dataforseo/actions/get-yahoo-organic-results/get-yahoo-organic-results.mjs (1)
  • response (45-56)
⏰ 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: Lint Code Base
  • GitHub Check: pnpm publish
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
🔇 Additional comments (2)
components/dataforseo/dataforseo.app.mjs (1)

41-44: Radius units are confirmed as kilometers
Verified against the official DataForSEO documentation: the third value in every location_coordinate triple (including for /business_data/google/reviews/task_post and /business_data/business_listings/search/live) is measured in kilometers. No change is needed to the current description.

components/dataforseo/actions/get-tripadvisor-reviews/get-tripadvisor-reviews.mjs (1)

98-100: Confirm postback payload shape and type on rerun path.

Some providers POST raw JSON (object) while others send text. If run.callback_request.body is a string, JSON.parse may be required.

You can add a small guard like:

response = typeof run.callback_request?.body === "string"
  ? JSON.parse(run.callback_request.body)
  : run.callback_request?.body;

Please confirm the postback payload type used by DataForSEO webhooks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[ACTION] DataForSEO
2 participants