Skip to content

Conversation

@GTFalcao
Copy link
Collaborator

@GTFalcao GTFalcao commented Jan 10, 2026

Closes #19564

Note: components are currently using the servicenow_oauth_ app as it is configured with the proper authentication, so the components can be tested.

This auth will be migrated to the main servicenow app right before we merge the PR, when I'll adjust the app slugs to servicenow.

Summary by CodeRabbit

  • New Features

    • Delete table records; retrieve record counts grouped by field with aggregates; fetch multiple records and single records by ID; improved create/update table record flows.
  • Refactor

    • Migrated to OAuth-based, table-centric ServiceNow integration with dynamic table lookup, consolidated CRUD methods, and streamlined response/configuration options; component version bumped to 0.8.0.
  • Removed

    • Legacy ticket-focused actions (Create Case, Create Incident) and older record-by-sysid action.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 10, 2026

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

1 Skipped Deployment
Project Deployment Review Updated (UTC)
pipedream-docs-redirect-do-not-edit Ignored Ignored Jan 14, 2026 11:38pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 10, 2026

Walkthrough

Migrates ServiceNow integration to an OAuth-enabled, table-centric app: removes legacy ticket/sysid actions, adds table CRUD and aggregate actions, consolidates HTTP logic into a unified _makeRequest and new app methods, updates action keys/props, and bumps package version.

Changes

Cohort / File(s) Summary
Removed legacy ticket actions
components/servicenow/actions/create-case/create-case.mjs, components/servicenow/actions/create-incident/create-incident.mjs, components/servicenow/actions/get-table-record-by-sysid/get-table-record-by-sysid.mjs
Deleted three ticket-centric modules and their exported action objects (create case, create incident, get-by-sysid) that performed manual HTTP requests.
New table-focused actions
components/servicenow/actions/delete-table-record/delete-table-record.mjs, components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs, components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs
Added actions for deleting a table record, retrieving aggregate counts by field, and getting a record by id. Each uses the new servicenow app methods, validates inputs, exports summaries, and returns responses.
Refactored table operations (action updates)
components/servicenow/actions/create-table-record/create-table-record.mjs, components/servicenow/actions/get-table-records/get-table-records.mjs, components/servicenow/actions/update-table-record/update-table-record.mjs
Reworked actions to use app-level methods (createTableRecord, getTableRecords, updateTableRecord) instead of manual axios calls; consolidated and renamed props (table, recordId, recordData/updateFields, responseDataFormat, etc.), added validation, and updated action keys to servicenow_oauth_ prefix.
Core app rewrite & package bump
components/servicenow/servicenow.app.mjs, components/servicenow/package.json
Rewrote app to servicenow_oauth_, added _makeRequest and CRUD/aggregate helpers (createTableRecord, updateTableRecord, deleteTableRecord, getTableRecordById, getTableRecords, getRecordCountsByField), added table/recordId propDefinitions and response-shaping propDefinitions, and bumped version 0.7.0 → 0.8.0.

Sequence Diagram(s)

sequenceDiagram
    participant Action as Action
    participant App as ServiceNow App
    participant Req as _makeRequest
    participant API as ServiceNow API

    Note over Action,API: OAuth table-based flow
    Action->>App: invoke e.g. createTableRecord({ $, table, data, params })
    App->>Req: build request (method, path, headers with OAuth token)
    Req->>API: HTTP request to /table/{table} or /stats/{table}
    API-->>Req: response
    Req-->>App: parsed response
    App-->>Action: return response (and $.export("$summary"))
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal and does not follow the required template. It lacks the 'WHY' section that the template specifies, providing only issue reference and implementation note. Add a 'WHY' section explaining the business rationale or motivation for adding these new ServiceNow components.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ServiceNow new components' accurately and directly describes the main change: introducing new ServiceNow action components to the codebase.
Linked Issues check ✅ Passed The PR implements all primary coding requirements from issue #19564: OAuth-enabled ServiceNow app, CRUD operations (create/get/update/delete records), Aggregate API support, and search filters.
Out of Scope Changes check ✅ Passed All changes are within scope: refactored ServiceNow app components to use OAuth, replaced legacy ticket-focused actions with table-centric CRUD operations, and added new aggregate functionality as specified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In @components/servicenow/actions/create-table-record/create-table-record.mjs:
- Around line 22-26: The input schema for the create-table-record action allows
an empty object for recordData which can produce blank records; add a non-empty
validation check for recordData (referencing the recordData input in
create-table-record) and reject empty objects before making the ServiceNow API
call—e.g., verify Object.keys(recordData).length > 0 (or equivalent) in the
action handler and return/throw a clear validation error if it's empty so
accidental `{}` inputs are blocked.

In @components/servicenow/actions/delete-table-record/delete-table-record.mjs:
- Around line 4-12: The action object for the "Delete Table Record" action (key:
"servicenow_oauth_-delete-table-record", name: "Delete Table Record") has
version set to "0.0.1"; update the version property to "1.0.0" to match the
other new/rewritten table actions in this PR so versioning is consistent across
actions.

In
@components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs:
- Around line 90-121: The validation wrongly rejects "count"-only requests
because hasAggregateFields is initialized false and never considers
sysparm_count; change the logic so hasAggregateFields is initialized or set
based on this.count (e.g., set hasAggregateFields = !!this.count before the
avg/min/max/sum checks, or explicitly check if this.count is truthy and set
hasAggregateFields = true) so that the existing check that throws
ConfigurationError recognizes count as a valid aggregate operation; update the
block around params and the hasAggregateFields variable (the symbol names:
params, hasAggregateFields, this.count, ConfigurationError) accordingly.
- Around line 82-97: Remove the standalone sysparm_order_by entry from the
params object in the run method (where params is constructed) and instead inject
the orderBy value into sysparm_query using ServiceNow encoded-query ordering
syntax (prepend/append ^ORDERBYfield_name or ^ORDERBYDESCfield_name to
this.query/sysparm_query when this.orderBy is set); also update the orderBy prop
description to reference ORDERBY/ORDERBYDESC (not ^ORDER_BY/^ORDER_BYDESC) so
callers know to supply a field name and direction indicator that will be merged
into sysparm_query.

In @components/servicenow/actions/update-table-record/update-table-record.mjs:
- Around line 28-38: The action accepts updateFields and replaceRecord but
doesn't validate that updateFields is an object with at least one key, which can
produce a no-op for PATCH or accidentally clear data for PUT; add a fail-fast
validation that ensures updateFields is a non-empty object (typeof updateFields
=== "object" && Object.keys(updateFields).length > 0) and throw/return a clear
validation error when it fails; additionally, if replaceRecord is true enforce
the same non-empty check (or provide a specific error telling the caller that
replacing requires at least one field) so the action never proceeds with an
empty payload.
- Around line 4-7: The component key string
"servicenow_oauth_-update-table-record" contains a temporary oauth_ prefix;
change it to the permanent identifier "servicenow-update-table-record" in the
key field of the Update Table Record component (the object that defines
key/name/version), and if you intend to rename instead of restoring the
canonical key, increment the major version and add migration notes; otherwise
simply remove the oauth_ prefix so the component keeps its stable identity.

In @components/servicenow/servicenow.app.mjs:
- Around line 12-22: The options method is returning potentially large result
sets from getTableRecords for table "sys_db_object"; add a sysparm_limit to the
params to cap results (e.g., 20 or configurable) to improve responsiveness.
Modify the params object passed to getTableRecords in the options({ query })
function to include a sysparm_limit property alongside sysparm_query and
sysparm_fields, ensuring the call to getTableRecords still returns the same
fields but only up to the configured limit.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4dd4105 and 44272f2.

📒 Files selected for processing (11)
  • components/servicenow/actions/create-case/create-case.mjs
  • components/servicenow/actions/create-incident/create-incident.mjs
  • components/servicenow/actions/create-table-record/create-table-record.mjs
  • components/servicenow/actions/delete-table-record/delete-table-record.mjs
  • components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs
  • components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs
  • components/servicenow/actions/get-table-record-by-sysid/get-table-record-by-sysid.mjs
  • components/servicenow/actions/get-table-records/get-table-records.mjs
  • components/servicenow/actions/update-table-record/update-table-record.mjs
  • components/servicenow/package.json
  • components/servicenow/servicenow.app.mjs
💤 Files with no reviewable changes (3)
  • components/servicenow/actions/get-table-record-by-sysid/get-table-record-by-sysid.mjs
  • components/servicenow/actions/create-case/create-case.mjs
  • components/servicenow/actions/create-incident/create-incident.mjs
🧰 Additional context used
🧬 Code graph analysis (6)
components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs (3)
components/servicenow/servicenow.app.mjs (1)
  • response (98-105)
components/servicenow/actions/create-table-record/create-table-record.mjs (1)
  • response (59-70)
components/servicenow/actions/update-table-record/update-table-record.mjs (1)
  • response (71-84)
components/servicenow/servicenow.app.mjs (5)
components/servicenow/actions/get-table-records/get-table-records.mjs (2)
  • query (92-92)
  • response (117-129)
components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs (1)
  • response (123-127)
components/servicenow/actions/create-table-record/create-table-record.mjs (1)
  • response (59-70)
components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs (1)
  • response (60-71)
components/servicenow/actions/update-table-record/update-table-record.mjs (1)
  • response (71-84)
components/servicenow/actions/create-table-record/create-table-record.mjs (4)
components/servicenow/servicenow.app.mjs (1)
  • response (98-105)
components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs (1)
  • response (60-71)
components/servicenow/actions/update-table-record/update-table-record.mjs (1)
  • response (71-84)
components/servicenow/actions/get-table-records/get-table-records.mjs (1)
  • response (117-129)
components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs (1)
components/servicenow/servicenow.app.mjs (1)
  • response (98-105)
components/servicenow/actions/get-table-records/get-table-records.mjs (3)
components/servicenow/servicenow.app.mjs (1)
  • response (98-105)
components/servicenow/actions/create-table-record/create-table-record.mjs (1)
  • response (59-70)
components/servicenow/actions/update-table-record/update-table-record.mjs (1)
  • response (71-84)
components/servicenow/actions/update-table-record/update-table-record.mjs (4)
components/servicenow/servicenow.app.mjs (1)
  • response (98-105)
components/servicenow/actions/create-table-record/create-table-record.mjs (1)
  • response (59-70)
components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs (1)
  • response (60-71)
components/servicenow/actions/get-table-records/get-table-records.mjs (1)
  • response (117-129)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
  • GitHub Check: pnpm publish
  • GitHub Check: Lint Code Base
🔇 Additional comments (8)
components/servicenow/package.json (1)

3-4: Package metadata update looks fine.
0.8.0 + description tweak match the scope of the refactor.

components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs (1)

59-71: Good use of the centralized app method; relies on safe param serialization.
If _makeRequest strips undefined params (recommended), this params object is good and consistent with other actions.

components/servicenow/actions/create-table-record/create-table-record.mjs (1)

58-70: Migration to this.servicenow.createTableRecord looks consistent with the new app API.
Param mapping matches the other table actions.

components/servicenow/actions/get-table-records/get-table-records.mjs (1)

91-115: Good guardrail: require query or at least one filter before querying the table.
This prevents accidental full-table scans.

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

93-107: Harden _makeRequest for empty responses + strip undefined query params.

DELETE may return 204/no body; and passing params with undefined keys can leak empty query params depending on serialization. Centralizing both behaviors here avoids repeating fixes across actions.

Proposed change
   methods: {
     async _makeRequest({
       $ = this,
       headers,
       ...args
     }) {
+      // Avoid emitting `foo=` in querystrings for undefined values.
+      if (args?.params && typeof args.params === "object") {
+        args.params = Object.fromEntries(
+          Object.entries(args.params).filter(([, v]) => v !== undefined)
+        );
+      }
       const response = await axios($, {
         baseURL: `https://${this.$auth.instance_name}.service-now.com/api/now`,
         headers: {
           ...headers,
           "Authorization": `Bearer ${this.$auth.oauth_access_token}`,
         },
         ...args,
       });
-      return response.result;
+      // Some endpoints (e.g., DELETE) can return an empty body.
+      return response?.result ?? null;
     },
⛔ Skipped due to learnings
Learnt from: GTFalcao
Repo: PipedreamHQ/pipedream PR: 18362
File: components/leonardo_ai/actions/generate-image/generate-image.mjs:103-105
Timestamp: 2025-09-15T22:01:11.472Z
Learning: In Pipedream components, pipedream/platform's axios implementation automatically excludes undefined values from HTTP requests, so there's no need to manually check for truthiness before including properties in request payloads.
components/servicenow/actions/delete-table-record/delete-table-record.mjs (1)

35-43: This concern is not applicable to this codebase. The pattern of unconditionally including optional parameters is standard across all servicenow actions (get-table-records, get-table-record-by-id, update-table-record, create-table-record). axios automatically strips undefined values from query parameters according to its standard behavior, so there is no risk of empty query params being sent. Applying conditional assignment only to this action would create inconsistency with the established pattern used throughout the component.

Likely an incorrect or invalid review comment.

components/servicenow/actions/update-table-record/update-table-record.mjs (2)

39-68: Add excludeReferenceLinks for parity with other actions (smaller, more predictable responses).
get-table-record-by-id and create-table-record map sysparm_exclude_reference_link, but this action doesn’t, so the update response shape/payload can differ for no obvious reason.

Proposed fix (prop + param)
   props: {
     servicenow,
@@
     responseDataFormat: {
       propDefinition: [
         servicenow,
         "responseDataFormat",
       ],
     },
+    excludeReferenceLinks: {
+      propDefinition: [
+        servicenow,
+        "excludeReferenceLinks",
+      ],
+    },
     responseFields: {
       propDefinition: [
         servicenow,
         "responseFields",
       ],
     },
@@
   async run({ $ }) {
     const response = await this.servicenow.updateTableRecord({
@@
       params: {
         sysparm_display_value: this.responseDataFormat,
+        sysparm_exclude_reference_link: this.excludeReferenceLinks,
         sysparm_fields: this.responseFields?.join?.() || this.responseFields,
         sysparm_input_display_value: this.inputDisplayValue,
         sysparm_view: this.responseView,
         sysparm_query_no_domain: this.queryNoDomain,
       },
     });

Also applies to: 77-83

⛔ Skipped due to learnings
Learnt from: GTFalcao
Repo: PipedreamHQ/pipedream PR: 16954
File: components/salesloft/salesloft.app.mjs:14-23
Timestamp: 2025-06-04T17:52:05.780Z
Learning: In the Salesloft API integration (components/salesloft/salesloft.app.mjs), the _makeRequest method returns response.data which directly contains arrays for list endpoints like listPeople, listCadences, listUsers, and listAccounts. The propDefinitions correctly call .map() directly on these responses without needing to destructure a nested data property.

70-92: > Likely an incorrect or invalid review comment.

Comment on lines +82 to +97
orderBy: {
type: "string",
label: "Order By",
description: "Field to sort results by. Prefix with `^ORDER_BY` for ascending or `^ORDER_BYDESC` for descending (e.g., `^ORDER_BYDESC` + field name)",
optional: true,
},
},
async run({ $ }) {
const params = {
sysparm_count: this.count,
sysparm_query: this.query,
sysparm_group_by: this.groupByField,
sysparm_display_value: this.responseDataFormat,
sysparm_having: this.havingQuery,
sysparm_order_by: this.orderBy,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

ServiceNow Aggregate API /api/now/stats table sysparm_order_by sysparm_display_value parameters

💡 Result:

Short answer

  • sysparm_display_value (true | false | all) on the stats (/api/now/stats/{table}) call controls whether reference/field display text or raw values (or both) are returned. Use true for display text, false for raw values (sys_id / internal value), and all for both. [1][2]

  • There is no separate, reliable sysparm_order_by for the Aggregate/Stats API. To sort results use ORDERBY / ORDERBYDESC inside an encoded sysparm_query (e.g. sysparm_query=active=true^ORDERBYsys_created_on). Putting an order clause after the & (as a separate sysparm) is not treated as part of the encoded query. [1][3]

Examples

  • Return counts with display values:
    /api/now/stats/incident?sysparm_count=true&sysparm_query=priority=1&sysparm_display_value=true. [1]

  • Order by created date (use in sysparm_query):
    /api/now/stats/incident?sysparm_count=true&sysparm_query=active=true^ORDERBYsys_created_on. [3]

Notes / gotchas

  • sysparm_display_value can have performance implications—use only when needed. [1]
  • If you need per-field control of display vs raw values, consider dot-walking / explicit fields or using sysparm_display_value=all and reading the display_value/value pairs. Community posts document behaviors and edge cases. [2][3]

Sources

  1. ServiceNow developer docs — REST query parameter guidance (sysparm_display_value / encoded queries). [developer.servicenow.com]. [2]
  2. ServiceNow Community / StackOverflow discussions on sysparm_display_value (true/false/all) and examples. [1][2]
  3. Community guidance showing ORDERBY / ORDERBYDESC must be in sysparm_query (not a separate sysparm_orderby). [3]

If you want, I can draft the exact stats URL you need (count, group_by, having, order) for your table and fields — tell me the table name and desired aggregation.


🏁 Script executed:

cat components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs

Repository: PipedreamHQ/pipedream

Length of output: 4723


🏁 Script executed:

rg "responseDataFormat" components/servicenow/ -A 5 -B 2

Repository: PipedreamHQ/pipedream

Length of output: 10880


🏁 Script executed:

sed -n '/responseDataFormat:/,/options:/p' components/servicenow/servicenow.app.mjs | head -20

Repository: PipedreamHQ/pipedream

Length of output: 250


🏁 Script executed:

sed -n '/responseDataFormat:/,/^\s*},/p' components/servicenow/servicenow.app.mjs | head -30

Repository: PipedreamHQ/pipedream

Length of output: 358


🏁 Script executed:

cat components/servicenow/servicenow.app.mjs | grep -A 25 '"responseDataFormat":'

Repository: PipedreamHQ/pipedream

Length of output: 47


🏁 Script executed:

sed -n '50,150p' components/servicenow/servicenow.app.mjs | grep -A 20 "responseDataFormat"

Repository: PipedreamHQ/pipedream

Length of output: 47


🏁 Script executed:

rg "responseDataFormat" components/servicenow/servicenow.app.mjs -A 15

Repository: PipedreamHQ/pipedream

Length of output: 503


🏁 Script executed:

rg "responseDataFormat" components/servicenow/servicenow.app.mjs -A 20 | tail -10

Repository: PipedreamHQ/pipedream

Length of output: 314


Fix orderBy implementation: remove sysparm_order_by parameter and use ORDERBY/ORDERBYDESC in encoded query instead.

The ServiceNow Aggregate API (/api/now/stats/{table}) does not support a separate sysparm_order_by parameter. Ordering must be done using ORDERBY or ORDERBYDESC directly inside the encoded sysparm_query (e.g., sysparm_query=active=true^ORDERBYfield_name or ^ORDERBYDESCfield_name).

Current issues:

  • sysparm_order_by: this.orderBy as a standalone parameter is ignored by the API
  • The orderBy prop description incorrectly mentions ^ORDER_BY syntax; should document that ordering goes inside the query string using ORDERBY/ORDERBYDESC
  • sysparm_display_value values (true/false/all) are correct and supported
🤖 Prompt for AI Agents
In
@components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs
around lines 82 - 97, Remove the standalone sysparm_order_by entry from the
params object in the run method (where params is constructed) and instead inject
the orderBy value into sysparm_query using ServiceNow encoded-query ordering
syntax (prepend/append ^ORDERBYfield_name or ^ORDERBYDESCfield_name to
this.query/sysparm_query when this.orderBy is set); also update the orderBy prop
description to reference ORDERBY/ORDERBYDESC (not ^ORDER_BY/^ORDER_BYDESC) so
callers know to supply a field name and direction indicator that will be merged
into sysparm_query.

Comment on lines +90 to +121
const params = {
sysparm_count: this.count,
sysparm_query: this.query,
sysparm_group_by: this.groupByField,
sysparm_display_value: this.responseDataFormat,
sysparm_having: this.havingQuery,
sysparm_order_by: this.orderBy,
};

let hasAggregateFields = false;

// Add aggregate functions for each field type
if (this.avgFields?.length) {
params.sysparm_avg_fields = this.avgFields.join?.() || this.avgFields;
hasAggregateFields = true;
}
if (this.minFields?.length) {
params.sysparm_min_fields = this.minFields.join?.() || this.minFields;
hasAggregateFields = true;
}
if (this.maxFields?.length) {
params.sysparm_max_fields = this.maxFields.join?.() || this.maxFields;
hasAggregateFields = true;
}
if (this.sumFields?.length) {
params.sysparm_sum_fields = this.sumFields.join?.() || this.sumFields;
hasAggregateFields = true;
}

if (!hasAggregateFields) {
throw new ConfigurationError("You must provide at least one Aggregate Field. [See the documentation](https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/inbound-rest/concept/c_AggregateAPI.html#title_aggregate-GET-stats) for more information.");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the exact file and verify it exists
fd "get-record-counts-by-field.mjs" -t f

Repository: PipedreamHQ/pipedream

Length of output: 152


🏁 Script executed:

# Read the file to see the actual code
cat components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs

Repository: PipedreamHQ/pipedream

Length of output: 4723


🏁 Script executed:

# Search for where this.count is defined (likely in the props or defaults)
rg -A 5 -B 5 "count:" components/servicenow/actions/get-record-counts-by-field/

Repository: PipedreamHQ/pipedream

Length of output: 2676


🏁 Script executed:

# Look for tests related to this action
fd "get-record-counts-by-field" -t f --glob "*.test.*" --glob "*.spec.*"

Repository: PipedreamHQ/pipedream

Length of output: 215


🏁 Script executed:

# Search for the getRecordCountsByField method implementation
rg "getRecordCountsByField" components/servicenow/

Repository: PipedreamHQ/pipedream

Length of output: 296


🏁 Script executed:

# Check the servicenow.app.mjs for the actual API call
rg -A 20 "getRecordCountsByField" components/servicenow/servicenow.app.mjs

Repository: PipedreamHQ/pipedream

Length of output: 236


🏁 Script executed:

# Search for test files in the repo
find components/servicenow -name "*test*" -o -name "*spec*" | head -20

Repository: PipedreamHQ/pipedream

Length of output: 47


🏁 Script executed:

# Look for any documentation or examples of this action
rg -i "get-record-counts-by-field" --type md --type txt

Repository: PipedreamHQ/pipedream

Length of output: 47


🏁 Script executed:

# Check if there are any other references to the aggregate fields validation
rg -B 5 -A 5 "hasAggregateFields" components/servicenow/

Repository: PipedreamHQ/pipedream

Length of output: 4011


Bug: count-only requests fail validation despite count defaulting to true.

The action cannot be used in its primary configuration. With count enabled by default and no other aggregate fields provided, the validation throws a ConfigurationError, preventing the most common "group + count" usage. The validation logic does not account for sysparm_count as a valid aggregate operation.

Initialize hasAggregateFields to account for the count parameter:

Proposed fix
-    let hasAggregateFields = false;
+    // Count itself is a valid aggregate for this endpoint.
+    let hasAggregateFields = !!this.count;
🤖 Prompt for AI Agents
In
@components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs
around lines 90 - 121, The validation wrongly rejects "count"-only requests
because hasAggregateFields is initialized false and never considers
sysparm_count; change the logic so hasAggregateFields is initialized or set
based on this.count (e.g., set hasAggregateFields = !!this.count before the
avg/min/max/sum checks, or explicitly check if this.count is truthy and set
hasAggregateFields = true) so that the existing check that throws
ConfigurationError recognizes count as a valid aggregate operation; update the
block around params and the hasAggregateFields variable (the symbol names:
params, hasAggregateFields, this.count, ConfigurationError) accordingly.

Comment on lines +34 to 45
filterCreatedAtDate: {
type: "string",
label: "Filter by Date Created",
description: "Return records created only after the given date, in the format `YYYY-MM-DD HH:MM:SS` (e.g. `2026-01-01 00:00:00`).",
optional: true,
},
sysparm_display_value: {
filterUpdatedAtDate: {
type: "string",
description: "Return field display values (true), actual values (false), or both (all) (default: false).",
label: "Filter by Date Updated",
description: "Return records updated only after the given date, in the format `YYYY-MM-DD HH:MM:SS` (e.g. `2026-01-01 00:00:00`).",
optional: true,
options: [
"true",
"false",
"all",
],
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Validate the date filter format (or switch to a date-time prop type if supported).
Right now any string is accepted, which can generate invalid encoded queries and confusing results.

Also applies to: 98-104

Comment on lines 12 to 22
async options({ query }) {
if (!(query?.length > 1)) {
return [];
}
const data = await this.getTableRecords({
table: "sys_db_object",
params: {
sysparm_query: `nameLIKE${query}^ORlabelLIKE${query}`,
sysparm_fields: "name,label",
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a sysparm_limit for table lookup options.
sys_db_object can be large; limiting results improves responsiveness and avoids loading huge option lists.

Proposed change
         const data = await this.getTableRecords({
           table: "sys_db_object",
           params: {
             sysparm_query: `nameLIKE${query}^ORlabelLIKE${query}`,
             sysparm_fields: "name,label",
+            sysparm_limit: 50,
           },
         });
📝 Committable suggestion

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

Suggested change
async options({ query }) {
if (!(query?.length > 1)) {
return [];
}
const data = await this.getTableRecords({
table: "sys_db_object",
params: {
sysparm_query: `nameLIKE${query}^ORlabelLIKE${query}`,
sysparm_fields: "name,label",
},
});
async options({ query }) {
if (!(query?.length > 1)) {
return [];
}
const data = await this.getTableRecords({
table: "sys_db_object",
params: {
sysparm_query: `nameLIKE${query}^ORlabelLIKE${query}`,
sysparm_fields: "name,label",
sysparm_limit: 50,
},
});
🤖 Prompt for AI Agents
In @components/servicenow/servicenow.app.mjs around lines 12 - 22, The options
method is returning potentially large result sets from getTableRecords for table
"sys_db_object"; add a sysparm_limit to the params to cap results (e.g., 20 or
configurable) to improve responsiveness. Modify the params object passed to
getTableRecords in the options({ query }) function to include a sysparm_limit
property alongside sysparm_query and sysparm_fields, ensuring the call to
getTableRecords still returns the same fields but only up to the configured
limit.

jcortes
jcortes previously approved these changes Jan 12, 2026
Copy link
Collaborator

@jcortes jcortes left a comment

Choose a reason for hiding this comment

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

Hi @GTFalcao lgtm! Ready for QA!

@vunguyenhung
Copy link
Collaborator

Hello everyone, I have tested this PR and there're some test cases failed or needed improvement.

Please check test reports below for more information:

Throw a ConfigurationError if the search term is invalid.
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

🤖 Fix all issues with AI agents
In `@components/servicenow/servicenow.app.mjs`:
- Around line 93-107: The _makeRequest currently unconditionally returns
response.result which yields undefined for ServiceNow DELETE 204 responses;
update _makeRequest to detect a 204 No Content (response.status === 204) and
return a consistent value (e.g., an empty object or a standardized payload like
{ success: true } ) instead of undefined so deleteTableRecord and other callers
get a predictable result; modify the return logic inside _makeRequest to check
response.status and return the chosen consistent value for 204, otherwise return
response.result, and verify deleteTableRecord continues to call _makeRequest
unchanged.
♻️ Duplicate comments (1)
components/servicenow/servicenow.app.mjs (1)

12-22: Add sysparm_limit to prevent loading large option lists.

The previous review feedback about adding sysparm_limit to cap results from sys_db_object has not been addressed. Large tables can degrade responsiveness.

Proposed fix
         const data = await this.getTableRecords({
           table: "sys_db_object",
           params: {
             sysparm_query: `nameLIKE${query}^ORlabelLIKE${query}`,
             sysparm_fields: "name,label",
+            sysparm_limit: 50,
           },
         });
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44272f2 and 63c6bae.

📒 Files selected for processing (1)
  • components/servicenow/servicenow.app.mjs
🧰 Additional context used
🧬 Code graph analysis (1)
components/servicenow/servicenow.app.mjs (5)
components/servicenow/actions/get-table-records/get-table-records.mjs (2)
  • query (92-92)
  • response (117-129)
components/servicenow/actions/update-table-record/update-table-record.mjs (1)
  • response (71-84)
components/servicenow/actions/create-table-record/create-table-record.mjs (1)
  • response (59-70)
components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs (1)
  • response (60-71)
components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs (1)
  • response (123-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Verify TypeScript components
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Lint Code Base
🔇 Additional comments (7)
components/servicenow/servicenow.app.mjs (7)

1-5: Imports and app definition look good.

The servicenow_oauth_ app identifier is noted as temporary per PR description and will be migrated to the main servicenow app before merging.


31-35: LGTM!

Clear and straightforward record ID definition.


36-90: LGTM!

The prop definitions correctly map to ServiceNow Table API parameters (sysparm_display_value, sysparm_exclude_reference_link, sysparm_fields, sysparm_input_display_value, sysparm_view, sysparm_query_no_domain).


108-116: LGTM!

Correctly implements POST to the Table API endpoint.


117-127: LGTM!

Correctly uses PUT for full replacement and PATCH for partial updates per ServiceNow Table API semantics.


128-136: LGTM!

Endpoint structure is correct. The potential undefined return value is addressed in the _makeRequest comment above.


137-160: LGTM!

All GET methods correctly implement their respective ServiceNow API endpoints:

  • /table/{table}/{recordId} for single record retrieval
  • /table/{table} for record listing
  • /stats/{table} for Aggregate API counts

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +93 to 107
async _makeRequest({
$ = this,
headers,
...args
}) {
const response = await axios($, {
baseURL: `https://${this.$auth.instance_name}.service-now.com/api/now`,
headers: {
...headers,
"Authorization": `Bearer ${this.$auth.oauth_access_token}`,
},
...args,
});
return response.result;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for deleteTableRecord method
rg -A 10 "deleteTableRecord" components/servicenow/

Repository: PipedreamHQ/pipedream

Length of output: 1901


🏁 Script executed:

#!/bin/bash
# Also search for any DELETE method calls or patterns
rg "method.*DELETE|DELETE.*method" components/servicenow/ -i

Repository: PipedreamHQ/pipedream

Length of output: 131


🌐 Web query:

ServiceNow Table API DELETE request response 204 No Content result field

💡 Result:

Short answer: A successful Table API DELETE returns HTTP 204 No Content and therefore contains no JSON "result" field — 204 means the server intentionally returns an empty body. [1][2][3]

Notes / options:

  • If you need confirmation/data back, call GET before/after delete or use an API that returns the deleted record (or return 200 with a body from a custom endpoint). [2][3]
  • Client libraries (e.g., pysnow) treat 204 as success for DELETE and will not expect a "result" field. [1]

Sources:

  1. pysnow request handling (treats DELETE 204 as success). [turn0search4]. [1]
  2. ServiceNow Table API spec / examples showing DELETE -> 204 (no content). [turn0search2]. [2]
  3. HTTP RFC / Postman explanation of 204 semantics (no message body). [turn1search6][turn1search2]. [3]

🏁 Script executed:

#!/bin/bash
# Search for all files that reference deleteTableRecord
rg "deleteTableRecord" --type mjs

Repository: PipedreamHQ/pipedream

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Check what the other methods in servicenow.app.mjs return
rg -B 2 -A 8 "async (createTableRecord|updateTableRecord|getTableRecordById)" components/servicenow/servicenow.app.mjs

Repository: PipedreamHQ/pipedream

Length of output: 750


🏁 Script executed:

#!/bin/bash
# Search for all deleteTableRecord usages across the repo
rg "deleteTableRecord" -A 2 -B 2

Repository: PipedreamHQ/pipedream

Length of output: 835


🏁 Script executed:

#!/bin/bash
# Also check the delete-table-record action completely
cat -n components/servicenow/actions/delete-table-record/delete-table-record.mjs

Repository: PipedreamHQ/pipedream

Length of output: 1687


Address inconsistent return behavior for DELETE operations.

The _makeRequest method always returns response.result, but ServiceNow's DELETE endpoint returns 204 No Content with no response body. This causes deleteTableRecord to return undefined, unlike other CRUD methods which return data.

While the current action doesn't depend on the return value, this creates an inconsistency in the API pattern that could confuse future callers or lead to issues if other code assumes deleteTableRecord returns a truthy value.

Proposed fix
     async _makeRequest({
       $ = this,
       headers,
       ...args
     }) {
       const response = await axios($, {
         baseURL: `https://${this.$auth.instance_name}.service-now.com/api/now`,
         headers: {
           ...headers,
           "Authorization": `Bearer ${this.$auth.oauth_access_token}`,
         },
         ...args,
       });
-      return response.result;
+      return response?.result ?? response;
     },
🤖 Prompt for AI Agents
In `@components/servicenow/servicenow.app.mjs` around lines 93 - 107, The
_makeRequest currently unconditionally returns response.result which yields
undefined for ServiceNow DELETE 204 responses; update _makeRequest to detect a
204 No Content (response.status === 204) and return a consistent value (e.g., an
empty object or a standardized payload like { success: true } ) instead of
undefined so deleteTableRecord and other callers get a predictable result;
modify the return logic inside _makeRequest to check response.status and return
the chosen consistent value for 204, otherwise return response.result, and
verify deleteTableRecord continues to call _makeRequest unchanged.

@vunguyenhung
Copy link
Collaborator

Hello everyone, I have tested this PR and there're some test cases failed or needed improvement.

Please check test reports below for more information:

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

🤖 Fix all issues with AI agents
In `@components/servicenow/servicenow.app.mjs`:
- Around line 13-16: The options method currently uses console.log for user
guidance which is not visible in the Pipedream UI; replace the console.log and
empty-array return with a user-facing placeholder option when the query is empty
or too short (e.g., return [{ label: "Please input a search term", value: "" }])
so the dropdown shows guidance; update the conditional in the same method (the
block that checks query?.length > 1) to return that placeholder option instead
of logging to console.
♻️ Duplicate comments (2)
components/servicenow/servicenow.app.mjs (2)

12-23: Add sysparm_limit to prevent large result sets.

The sys_db_object table can contain thousands of entries. Without a limit, this query may return an excessively large option list, degrading performance and UX.

Proposed fix
         const data = await this.getTableRecords({
           table: "sys_db_object",
           params: {
             sysparm_query: `nameLIKE${query}^ORlabelLIKE${query}`,
             sysparm_fields: "name,label",
+            sysparm_limit: 50,
           },
         });

94-108: Handle DELETE 204 No Content responses consistently.

ServiceNow's DELETE endpoint returns 204 No Content with no response body. The current implementation returns response.result, which will be undefined for DELETE operations, creating an inconsistent return type across CRUD methods.

Proposed fix
     async _makeRequest({
       $ = this,
       headers,
       ...args
     }) {
       const response = await axios($, {
         baseURL: `https://${this.$auth.instance_name}.service-now.com/api/now`,
         headers: {
           ...headers,
           "Authorization": `Bearer ${this.$auth.oauth_access_token}`,
         },
         ...args,
       });
-      return response.result;
+      return response?.result ?? response;
     },
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b3503c and fb84488.

📒 Files selected for processing (1)
  • components/servicenow/servicenow.app.mjs
🧰 Additional context used
🧬 Code graph analysis (1)
components/servicenow/servicenow.app.mjs (5)
components/servicenow/actions/get-table-records/get-table-records.mjs (2)
  • query (92-92)
  • response (117-129)
components/servicenow/actions/update-table-record/update-table-record.mjs (1)
  • response (71-84)
components/servicenow/actions/create-table-record/create-table-record.mjs (1)
  • response (59-70)
components/servicenow/actions/get-table-record-by-id/get-table-record-by-id.mjs (1)
  • response (60-71)
components/servicenow/actions/get-record-counts-by-field/get-record-counts-by-field.mjs (1)
  • response (123-127)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pnpm publish
  • GitHub Check: Lint Code Base
  • GitHub Check: Publish TypeScript components
  • GitHub Check: Verify TypeScript components
🔇 Additional comments (4)
components/servicenow/servicenow.app.mjs (4)

32-91: LGTM!

The propDefinitions are well-structured with appropriate types, labels, and descriptions that align with ServiceNow's Table API parameters.


109-134: LGTM!

The createTableRecord and updateTableRecord methods correctly implement ServiceNow Table API patterns with appropriate HTTP methods and Content-Type headers. The replace parameter elegantly toggles between PUT and PATCH semantics.


135-151: LGTM!

The deleteTableRecord and getTableRecordById methods correctly construct the Table API URLs. The DELETE return value concern is addressed at the _makeRequest level.


152-167: LGTM!

The getTableRecords and getRecordCountsByField methods correctly target the Table API (/table/{table}) and Aggregate API (/stats/{table}) endpoints respectively.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@vunguyenhung
Copy link
Collaborator

Hello everyone, I have tested this PR and there're some test cases failed or needed improvement.

Please check test reports below for more information:

2 similar comments
@vunguyenhung
Copy link
Collaborator

Hello everyone, I have tested this PR and there're some test cases failed or needed improvement.

Please check test reports below for more information:

@vunguyenhung
Copy link
Collaborator

Hello everyone, I have tested this PR and there're some test cases failed or needed improvement.

Please check test reports below for more information:

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.

[APP] ServiceNow - OAuth Enabled

4 participants