Skip to content

Conversation

@jcortes
Copy link
Collaborator

@jcortes jcortes commented Nov 5, 2025

WHY

Resolves #18724

Summary by CodeRabbit

  • New Features
    • Added actions: Create Application, Create Candidate, Create Interview Schedule, Create Offer, Start Offer Process, Start Offer, and List Applications.
    • UI fields now load live Ashby options (candidates, applications, jobs, plans, stages, sources, users) and accept richer structured/JSON-parsable inputs.
    • Exposed authenticated Ashby operations with built-in pagination for large result sets.
  • Chores
    • Added a JSON parsing utility and bumped the Ashby component version.

@jcortes jcortes self-assigned this Nov 5, 2025
@vercel
Copy link

vercel bot commented Nov 5, 2025

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

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
pipedream-docs Ignored Ignored Nov 7, 2025 3:43pm
pipedream-docs-redirect-do-not-edit Ignored Ignored Nov 7, 2025 3:43pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Walkthrough

Adds a new Ashby component: an expanded app module with HTTP/auth/pagination and API wrappers, seven new action modules (create/list/start operations), a parseJson utility, and package version/dependency updates.

Changes

Cohort / File(s) Summary
Action modules — create/list/start
components/ashby/actions/create-application/create-application.mjs, components/ashby/actions/create-candidate/create-candidate.mjs, components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs, components/ashby/actions/create-offer/create-offer.mjs, components/ashby/actions/list-applications/list-applications.mjs, components/ashby/actions/start-offer-process/start-offer-process.mjs, components/ashby/actions/start-off/start-off.mjs
Seven new action modules. Each exports a default action object (metadata, props) and an async run({ $ }) that reads props, calls the corresponding app.* wrapper (uses utils.parseJson where applicable), exports a summary via $.export("$summary", ...), and returns the API response.
App module
components/ashby/ashby.app.mjs
New/expanded app module adding propDefinitions with async options() for multiple lookups (candidateId, applicationId, jobId, interviewPlanId, interviewStageId, sourceId, userId), new public props (email, name, maxResults, offerProcessId), authentication/header helpers (getAuth, getHeaders), centralized HTTP helpers (makeRequest, post), paginate utility, and many API wrapper methods (createCandidate, createApplication, createOffer, createInterviewSchedule, listApplications, listCandidates, listJobs, listInterviewPlans, listInterviewStages, listSources, listUsers, startOffer, startOfferProcess, etc.).
Utilities
components/ashby/common/utils.mjs
New parseJson(input, maxDepth = 100) utility that recursively parses JSON-like strings into objects/arrays with cycle protection via WeakSet; exported as default { parseJson }.
Package configuration
components/ashby/package.json
Version bumped from 0.0.1 to 0.1.0 and dependencies added: @pipedream/platform: ^3.1.0.

Sequence Diagram(s)

sequenceDiagram
    participant User as Workflow/User
    participant Action as Action Module
    participant App as App Module
    participant API as Ashby API
    rect rgba(0,128,128,0.06)
    User->>Action: run({ $ })
    Action->>Action: read props from this
    Action->>App: call app.createCandidate/createApplication/createOffer/createInterviewSchedule/startOffer/startOfferProcess(...)
    App->>App: getAuth() / getHeaders()
    App->>App: makeRequest / post (build request)
    App->>API: HTTP request
    API-->>App: response
    App-->>Action: return response
    Action->>Action: $.export("$summary", "...")
    Action-->>User: return response
    end
Loading
sequenceDiagram
    participant User as Workflow/User
    participant ListAction as List Applications Action
    participant App as App Module
    participant Paginate as Paginate Utility
    participant API as Ashby API
    rect rgba(0,128,128,0.06)
    User->>ListAction: run({ $ })
    ListAction->>App: app.paginate({ fn: app.listApplications, fnArgs, max, keyField })
    loop pages
        Paginate->>App: app.listApplications(fnArgs)
        App->>API: HTTP POST with syncToken
        API-->>App: { results: [...], nextCursor? }
        App-->>Paginate: page
        Paginate->>Paginate: accumulate results
        alt nextCursor exists and under max
            Paginate->>Paginate: update fnArgs.syncToken
        else
            Paginate->>Paginate: stop
        end
    end
    Paginate-->>ListAction: aggregated items
    ListAction->>ListAction: $.export("$summary", count)
    ListAction-->>User: return aggregated items
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on components/ashby/ashby.app.mjs for authentication/header correctness, request/error handling, response normalization, and correct exposure of public methods.
  • Verify paginate cursor handling and mapping in async options() implementations (candidate/application/job/interview lookups).
  • Inspect components/ashby/common/utils.mjs::parseJson for recursion depth, cycle protection, and edge-case behavior.
  • Check each action's run for proper prop extraction, optional/dependent prop handling (e.g., interviewStageId dependence on interviewPlanId), and correct use of utils.parseJson where arrays/JSON strings are accepted.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title '[Components] Ashby - new components' clearly describes the main change: introducing new Ashby components for the Pipedream integration.
Description check ✅ Passed The PR description is minimal but complete, containing only the required 'WHY' section with a reference to the linked issue #18724.
Linked Issues check ✅ Passed The code changes comprehensively implement the requested objectives: new actions for managing candidates, applications, interview schedules, offers, and listing jobs, applications, and candidates [#18724].
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the Ashby integration actions requested in issue #18724; no unrelated modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ashby-new-components

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

@jcortes jcortes force-pushed the ashby-new-components branch from 260b59e to 1ef0fae Compare November 5, 2025 23:00
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2331fe and 1ef0fae.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • components/ashby/actions/create-application/create-application.mjs (1 hunks)
  • components/ashby/actions/create-candidate/create-candidate.mjs (1 hunks)
  • components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1 hunks)
  • components/ashby/actions/create-offer/create-offer.mjs (1 hunks)
  • components/ashby/actions/list-applications/list-applications.mjs (1 hunks)
  • components/ashby/ashby.app.mjs (1 hunks)
  • components/ashby/common/utils.mjs (1 hunks)
  • components/ashby/package.json (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-12-12T19:23:09.039Z
Learnt from: jcortes
Repo: PipedreamHQ/pipedream PR: 14935
File: components/sailpoint/package.json:15-18
Timestamp: 2024-12-12T19:23:09.039Z
Learning: When developing Pipedream components, do not add built-in Node.js modules like `fs` to `package.json` dependencies, as they are native modules provided by the Node.js runtime.

Applied to files:

  • components/ashby/package.json
📚 Learning: 2024-10-30T15:24:39.294Z
Learnt from: jcortes
Repo: PipedreamHQ/pipedream PR: 14467
File: components/gainsight_px/actions/create-account/create-account.mjs:4-6
Timestamp: 2024-10-30T15:24:39.294Z
Learning: In `components/gainsight_px/actions/create-account/create-account.mjs`, the action name should be "Create Account" instead of "Create Memory".

Applied to files:

  • components/ashby/actions/create-offer/create-offer.mjs
  • components/ashby/actions/create-candidate/create-candidate.mjs
  • components/ashby/actions/create-application/create-application.mjs
📚 Learning: 2025-06-04T17:52:05.780Z
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.

Applied to files:

  • components/ashby/ashby.app.mjs
📚 Learning: 2025-09-15T22:01:11.472Z
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.

Applied to files:

  • components/ashby/ashby.app.mjs
🧬 Code graph analysis (6)
components/ashby/actions/create-offer/create-offer.mjs (2)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1)
  • response (59-65)
components/ashby/ashby.app.mjs (2)
  • response (247-252)
  • response (345-352)
components/ashby/ashby.app.mjs (5)
components/ashby/actions/create-application/create-application.mjs (1)
  • response (82-93)
components/ashby/actions/create-candidate/create-candidate.mjs (1)
  • response (112-135)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1)
  • response (59-65)
components/ashby/actions/create-offer/create-offer.mjs (1)
  • response (53-62)
components/ashby/actions/list-applications/list-applications.mjs (1)
  • response (75-91)
components/ashby/actions/create-candidate/create-candidate.mjs (2)
components/ashby/actions/create-application/create-application.mjs (1)
  • response (82-93)
components/ashby/ashby.app.mjs (2)
  • response (247-252)
  • response (345-352)
components/ashby/actions/list-applications/list-applications.mjs (1)
components/ashby/ashby.app.mjs (2)
  • response (247-252)
  • response (345-352)
components/ashby/actions/create-application/create-application.mjs (5)
components/ashby/actions/create-candidate/create-candidate.mjs (1)
  • response (112-135)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1)
  • response (59-65)
components/ashby/actions/create-offer/create-offer.mjs (1)
  • response (53-62)
components/ashby/actions/list-applications/list-applications.mjs (1)
  • response (75-91)
components/ashby/ashby.app.mjs (2)
  • response (247-252)
  • response (345-352)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (2)
components/ashby/actions/create-offer/create-offer.mjs (1)
  • response (53-62)
components/ashby/ashby.app.mjs (2)
  • response (247-252)
  • response (345-352)
⏰ 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 (1)
components/ashby/actions/create-candidate/create-candidate.mjs (1)

124-133: Consider filtering undefined location fields.

The current logic includes all location fields (city, region, country) if any one is truthy, resulting in undefined values for unset fields. While the API may ignore these, it's cleaner to only include defined fields.

Apply this diff for cleaner object construction:

-      ...(city || region || country
-        ? {
-          location: {
-            city,
-            region,
-            country,
-          },
-        }
-        : undefined
-      ),
+      ...(city || region || country
+        ? {
+          location: Object.fromEntries(
+            Object.entries({ city, region, country })
+              .filter(([_, v]) => v !== undefined)
+          ),
+        }
+        : {}
+      ),
⛔ 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.

michelle0927
michelle0927 previously approved these changes Nov 6, 2025
Copy link
Collaborator

@michelle0927 michelle0927 left a comment

Choose a reason for hiding this comment

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

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:

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

♻️ Duplicate comments (2)
components/ashby/common/utils.mjs (1)

3-36: Fix recursion depth handling and array cycle detection.

maxDepth never decreases, so the guard at Line 4 is dead code—deep structures keep recursing until the stack blows up. Arrays also skip the seen WeakSet, so a cyclic array still causes infinite recursion (this was already raised earlier). Please enforce the depth counter on every recursive call and register arrays in seen.

-const parseJson = (input, maxDepth = 100) => {
-  const seen = new WeakSet();
-  const parse = (value) => {
-    if (maxDepth <= 0) {
+const parseJson = (input, maxDepth = 100) => {
+  const seen = new WeakSet();
+  const parse = (value, depth) => {
+    if (depth <= 0) {
       return value;
     }
     if (typeof(value) === "string") {
       // Only parse if the string looks like a JSON object or array
       const trimmed = value.trim();
       if (
         (trimmed.startsWith("{") && trimmed.endsWith("}")) ||
         (trimmed.startsWith("[") && trimmed.endsWith("]"))
       ) {
         try {
-          return parseJson(JSON.parse(value), maxDepth - 1);
+          return parse(JSON.parse(value), depth - 1);
         } catch (e) {
           return value;
         }
       }
       return value;
-    } else if (typeof(value) === "object" && value !== null && !Array.isArray(value)) {
-      if (seen.has(value)) {
-        return value;
-      }
-      seen.add(value);
-      return Object.entries(value)
-        .reduce((acc, [
-          key,
-          val,
-        ]) => Object.assign(acc, {
-          [key]: parse(val),
-        }), {});
-    } else if (Array.isArray(value)) {
-      return value.map((item) => parse(item));
+    } else if (value && typeof(value) === "object") {
+      if (seen.has(value)) {
+        return value;
+      }
+      seen.add(value);
+      if (Array.isArray(value)) {
+        return value.map((item) => parse(item, depth - 1));
+      }
+      return Object.entries(value)
+        .reduce((acc, [
+          key,
+          val,
+        ]) => Object.assign(acc, {
+          [key]: parse(val, depth - 1),
+        }), {});
     }
     return value;
   };
 
-  return parse(input);
+  return parse(input, maxDepth);
 };
components/ashby/ashby.app.mjs (1)

356-371: Stop exposing cursor/limit overrides in paginate

Spreading fnArgs?.data after the enforced limit/cursor lets callers reset those values each iteration, so cursor never advances and the loop can hang. Rework the argument assembly so you capture fnArgs?.data once, but always overwrite limit and the evolving cursor before each call. Also carry over the original cursor seed. This keeps pagination safe across retries.

-    async paginate({
-      max = 600, fn, fnArgs, keyField = "results",
-    } = {}) {
-      const results = [];
-      let cursor;
+    async paginate({
+      max = 600, fn, fnArgs, keyField = "results",
+    } = {}) {
+      const results = [];
+      const {
+        data: {
+          cursor: initialCursor,
+          ...restData
+        } = {},
+        ...restFnArgs
+      } = fnArgs ?? {};
+      let cursor = initialCursor;
       let collected = 0;
 
       while (collected < max) {
         const remainingToFetch = Math.min(max - collected, 100);
 
-        const response = await fn({
-          ...fnArgs,
-          data: {
-            limit: remainingToFetch,
-            cursor,
-            ...fnArgs?.data,
-          },
-        });
+        const response = await fn({
+          ...restFnArgs,
+          data: {
+            ...restData,
+            limit: remainingToFetch,
+            cursor,
+          },
+        });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a540a35 and 92f4ca6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • components/ashby/actions/create-application/create-application.mjs (1 hunks)
  • components/ashby/actions/create-candidate/create-candidate.mjs (1 hunks)
  • components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1 hunks)
  • components/ashby/actions/create-offer/create-offer.mjs (1 hunks)
  • components/ashby/actions/list-applications/list-applications.mjs (1 hunks)
  • components/ashby/actions/start-offer-process/start-offer-process.mjs (1 hunks)
  • components/ashby/actions/start-offer/start-offer.mjs (1 hunks)
  • components/ashby/ashby.app.mjs (1 hunks)
  • components/ashby/common/utils.mjs (1 hunks)
  • components/ashby/package.json (2 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2024-10-30T15:24:39.294Z
Learnt from: jcortes
Repo: PipedreamHQ/pipedream PR: 14467
File: components/gainsight_px/actions/create-account/create-account.mjs:4-6
Timestamp: 2024-10-30T15:24:39.294Z
Learning: In `components/gainsight_px/actions/create-account/create-account.mjs`, the action name should be "Create Account" instead of "Create Memory".

Applied to files:

  • components/ashby/actions/create-candidate/create-candidate.mjs
  • components/ashby/actions/create-application/create-application.mjs
  • components/ashby/actions/create-offer/create-offer.mjs
📚 Learning: 2025-07-09T18:07:12.426Z
Learnt from: GTFalcao
Repo: PipedreamHQ/pipedream PR: 17538
File: components/aircall/sources/new-sms/new-sms.mjs:19-25
Timestamp: 2025-07-09T18:07:12.426Z
Learning: In Aircall API webhook payloads, the `created_at` field is returned as an ISO 8601 string format (e.g., "2020-02-18T20:52:22.000Z"), not as milliseconds since epoch. For Pipedream components, this needs to be converted to milliseconds using `Date.parse()` before assigning to the `ts` field in `generateMeta()`.

Applied to files:

  • components/ashby/actions/create-application/create-application.mjs
📚 Learning: 2024-09-25T16:13:11.505Z
Learnt from: LucBerge
Repo: PipedreamHQ/pipedream PR: 14080
File: components/nocodb/nocodb.app.mjs:133-133
Timestamp: 2024-09-25T16:13:11.505Z
Learning: When implementing pagination with an offset, incrementing `args.params.offset` within the loop ensures correct tracking of the offset, particularly when a maximum count limit (`max`) is used.

Applied to files:

  • components/ashby/ashby.app.mjs
📚 Learning: 2025-09-15T22:01:11.472Z
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.

Applied to files:

  • components/ashby/ashby.app.mjs
📚 Learning: 2025-06-04T17:52:05.780Z
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.

Applied to files:

  • components/ashby/ashby.app.mjs
📚 Learning: 2024-12-12T19:23:09.039Z
Learnt from: jcortes
Repo: PipedreamHQ/pipedream PR: 14935
File: components/sailpoint/package.json:15-18
Timestamp: 2024-12-12T19:23:09.039Z
Learning: When developing Pipedream components, do not add built-in Node.js modules like `fs` to `package.json` dependencies, as they are native modules provided by the Node.js runtime.

Applied to files:

  • components/ashby/package.json
🧬 Code graph analysis (8)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (2)
components/ashby/actions/create-offer/create-offer.mjs (1)
  • response (54-63)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/create-candidate/create-candidate.mjs (2)
components/ashby/actions/create-application/create-application.mjs (1)
  • response (82-93)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/create-application/create-application.mjs (2)
components/ashby/actions/create-candidate/create-candidate.mjs (1)
  • response (112-135)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/create-offer/create-offer.mjs (4)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1)
  • response (59-65)
components/ashby/actions/start-offer-process/start-offer-process.mjs (1)
  • response (29-34)
components/ashby/actions/start-offer/start-offer.mjs (1)
  • response (29-34)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/list-applications/list-applications.mjs (1)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/start-offer/start-offer.mjs (2)
components/ashby/actions/start-offer-process/start-offer-process.mjs (1)
  • response (29-34)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/ashby.app.mjs (7)
components/ashby/actions/create-application/create-application.mjs (1)
  • response (82-93)
components/ashby/actions/create-candidate/create-candidate.mjs (1)
  • response (112-135)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1)
  • response (59-65)
components/ashby/actions/create-offer/create-offer.mjs (1)
  • response (54-63)
components/ashby/actions/list-applications/list-applications.mjs (1)
  • response (75-91)
components/ashby/actions/start-offer-process/start-offer-process.mjs (1)
  • response (29-34)
components/ashby/actions/start-offer/start-offer.mjs (1)
  • response (29-34)
components/ashby/actions/start-offer-process/start-offer-process.mjs (2)
components/ashby/actions/start-offer/start-offer.mjs (1)
  • response (29-34)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
⏰ 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: Publish TypeScript components
  • GitHub Check: pnpm publish
  • GitHub Check: Verify TypeScript components

Comment on lines +11 to +35
async options({ prevContext: { cursor } }) {
if (cursor === null) {
return [];
}
const {
results,
nextCursor,
} = await this.listCandidates({
data: {
cursor,
},
});
return {
options: results.map(({
id: value,
name: label,
}) => ({
label,
value,
})),
context: {
cursor: nextCursor || null,
},
};
},
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

Fix destructuring of prevContext in async options

options() receives { prevContext }, and on the initial invocation Pipedream passes it as undefined. Destructuring prevContext: { cursor } therefore throws a TypeError before any options can load. Default the parameter to {} (and read cursor from it) so the dropdown renders, and mirror this fix for each async options definition in this file.

-      async options({ prevContext: { cursor } }) {
+      async options({ prevContext } = {}) {
+        const cursor = prevContext?.cursor;
         if (cursor === null) {
           return [];
         }
📝 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({ prevContext: { cursor } }) {
if (cursor === null) {
return [];
}
const {
results,
nextCursor,
} = await this.listCandidates({
data: {
cursor,
},
});
return {
options: results.map(({
id: value,
name: label,
}) => ({
label,
value,
})),
context: {
cursor: nextCursor || null,
},
};
},
async options({ prevContext } = {}) {
const cursor = prevContext?.cursor;
if (cursor === null) {
return [];
}
const {
results,
nextCursor,
} = await this.listCandidates({
data: {
cursor,
},
});
return {
options: results.map(({
id: value,
name: label,
}) => ({
label,
value,
})),
context: {
cursor: nextCursor || null,
},
};
},
🤖 Prompt for AI Agents
In components/ashby/ashby.app.mjs around lines 11 to 35, the async options
function destructures prevContext as prevContext: { cursor } which throws when
prevContext is undefined; change the parameter to default prevContext to an
empty object (e.g., options({ prevContext = {} }) or options({ prevContext: {}
}) and then read cursor from it) so initial invocation doesn't TypeError, update
the same pattern for every async options definition in this file to default
prevContext to {} and use cursor from that defaulted object.

@jcortes jcortes force-pushed the ashby-new-components branch from 92f4ca6 to 8e7d153 Compare November 7, 2025 15:43
@jcortes
Copy link
Collaborator Author

jcortes commented Nov 7, 2025

Hi @vunguyenhung about:

ashby - Create Offer - Fail

Improvement needed

  1. offer Process ID and Offer Form ID should offer multiple options for user to select

  2. answer: I've just implemented these new two components to get the proper ids, there is no such endpoint to list them

  3. Action offerProcess.start and offer.start API should be created/ implemented before this step.

  4. answer: I've just implemented these new two components

So please test again with latest push, thanks!

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

♻️ Duplicate comments (5)
components/ashby/ashby.app.mjs (3)

11-35: Critical: Fix destructuring to prevent TypeError on initial load.

The destructuring { prevContext: { cursor } } on line 11 throws a TypeError when prevContext is undefined (which happens on initial invocation). This prevents the dropdown from loading.

Apply this pattern to fix (and apply to all similar options functions in this file):

-      async options({ prevContext: { cursor } }) {
+      async options({ prevContext = {} } = {}) {
+        const cursor = prevContext?.cursor;
         if (cursor === null) {

This same issue affects lines 41, 74, 121, 173, and 203.


251-270: Enhance error handling for better diagnostics.

The error handling on line 268 assumes error.response?.data?.message is a string and may not provide useful context when the error structure differs.

Consider this more robust approach:

   } catch (error) {
-    throw error.response?.data?.message || error;
+    const errorMessage = error.response?.data?.message 
+      || error.message 
+      || JSON.stringify(error.response?.data || error);
+    throw new Error(errorMessage);
   }

355-386: Critical: Fix spread operator order to prevent pagination override.

Lines 367-370 spread fnArgs?.data after setting limit and cursor, which allows callers to override the pagination parameters. This breaks pagination logic and could cause incorrect results or infinite loops.

Apply this diff:

     const response = await fn({
       ...fnArgs,
       data: {
+        ...fnArgs?.data,
         limit: remainingToFetch,
         cursor,
-        ...fnArgs?.data,
       },
     });
components/ashby/common/utils.mjs (1)

33-36: Critical: Arrays not protected from circular references.

The cycle detection using WeakSet is only applied to plain objects (lines 22-24), but arrays can also contain circular references. This will cause infinite recursion and stack overflow if an array references itself.

Apply this diff:

   } else if (Array.isArray(value)) {
+    if (seen.has(value)) {
+      return value;
+    }
+    seen.add(value);
     return value.map((item) => parse(item));
   }
components/ashby/actions/create-application/create-application.mjs (1)

63-68: Consider validating the date format.

The createdAt prop accepts any string but should be a valid ISO 8601 date. Invalid formats will cause API errors downstream.

Consider adding validation before the API call:

if (createdAt && isNaN(Date.parse(createdAt))) {
  throw new Error("createdAt must be a valid ISO 8601 date string");
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92f4ca6 and 8e7d153.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • components/ashby/actions/create-application/create-application.mjs (1 hunks)
  • components/ashby/actions/create-candidate/create-candidate.mjs (1 hunks)
  • components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1 hunks)
  • components/ashby/actions/create-offer/create-offer.mjs (1 hunks)
  • components/ashby/actions/list-applications/list-applications.mjs (1 hunks)
  • components/ashby/actions/start-offer-process/start-offer-process.mjs (1 hunks)
  • components/ashby/actions/start-offer/start-offer.mjs (1 hunks)
  • components/ashby/ashby.app.mjs (1 hunks)
  • components/ashby/common/utils.mjs (1 hunks)
  • components/ashby/package.json (2 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2024-10-30T15:24:39.294Z
Learnt from: jcortes
Repo: PipedreamHQ/pipedream PR: 14467
File: components/gainsight_px/actions/create-account/create-account.mjs:4-6
Timestamp: 2024-10-30T15:24:39.294Z
Learning: In `components/gainsight_px/actions/create-account/create-account.mjs`, the action name should be "Create Account" instead of "Create Memory".

Applied to files:

  • components/ashby/actions/create-candidate/create-candidate.mjs
  • components/ashby/actions/create-offer/create-offer.mjs
  • components/ashby/actions/create-application/create-application.mjs
📚 Learning: 2024-09-25T16:13:11.505Z
Learnt from: LucBerge
Repo: PipedreamHQ/pipedream PR: 14080
File: components/nocodb/nocodb.app.mjs:133-133
Timestamp: 2024-09-25T16:13:11.505Z
Learning: When implementing pagination with an offset, incrementing `args.params.offset` within the loop ensures correct tracking of the offset, particularly when a maximum count limit (`max`) is used.

Applied to files:

  • components/ashby/ashby.app.mjs
📚 Learning: 2025-09-12T07:49:36.125Z
Learnt from: matyascimbulka
Repo: PipedreamHQ/pipedream PR: 18308
File: components/apify/actions/run-task-synchronously/run-task-synchronously.mjs:70-0
Timestamp: 2025-09-12T07:49:36.125Z
Learning: The Apify Task object always contains the `options` field according to the official API documentation, making nested destructuring like `options: { build }` safe to use without additional checks.

Applied to files:

  • components/ashby/ashby.app.mjs
📚 Learning: 2025-09-15T22:01:11.472Z
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.

Applied to files:

  • components/ashby/ashby.app.mjs
📚 Learning: 2025-06-04T17:52:05.780Z
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.

Applied to files:

  • components/ashby/ashby.app.mjs
📚 Learning: 2024-12-12T19:23:09.039Z
Learnt from: jcortes
Repo: PipedreamHQ/pipedream PR: 14935
File: components/sailpoint/package.json:15-18
Timestamp: 2024-12-12T19:23:09.039Z
Learning: When developing Pipedream components, do not add built-in Node.js modules like `fs` to `package.json` dependencies, as they are native modules provided by the Node.js runtime.

Applied to files:

  • components/ashby/package.json
📚 Learning: 2025-07-09T18:07:12.426Z
Learnt from: GTFalcao
Repo: PipedreamHQ/pipedream PR: 17538
File: components/aircall/sources/new-sms/new-sms.mjs:19-25
Timestamp: 2025-07-09T18:07:12.426Z
Learning: In Aircall API webhook payloads, the `created_at` field is returned as an ISO 8601 string format (e.g., "2020-02-18T20:52:22.000Z"), not as milliseconds since epoch. For Pipedream components, this needs to be converted to milliseconds using `Date.parse()` before assigning to the `ts` field in `generateMeta()`.

Applied to files:

  • components/ashby/actions/create-application/create-application.mjs
🧬 Code graph analysis (8)
components/ashby/actions/create-candidate/create-candidate.mjs (2)
components/ashby/actions/create-application/create-application.mjs (1)
  • response (82-93)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/start-offer-process/start-offer-process.mjs (2)
components/ashby/actions/start-offer/start-offer.mjs (1)
  • response (29-34)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (2)
components/ashby/actions/create-offer/create-offer.mjs (1)
  • response (63-72)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/create-offer/create-offer.mjs (3)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1)
  • response (59-65)
components/ashby/actions/start-offer/start-offer.mjs (1)
  • response (29-34)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/start-offer/start-offer.mjs (3)
components/ashby/actions/create-offer/create-offer.mjs (1)
  • response (63-72)
components/ashby/actions/start-offer-process/start-offer-process.mjs (1)
  • response (29-34)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/actions/list-applications/list-applications.mjs (1)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
components/ashby/ashby.app.mjs (7)
components/ashby/actions/create-application/create-application.mjs (1)
  • response (82-93)
components/ashby/actions/create-candidate/create-candidate.mjs (1)
  • response (112-135)
components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (1)
  • response (59-65)
components/ashby/actions/create-offer/create-offer.mjs (1)
  • response (63-72)
components/ashby/actions/list-applications/list-applications.mjs (1)
  • response (75-91)
components/ashby/actions/start-offer-process/start-offer-process.mjs (1)
  • response (29-34)
components/ashby/actions/start-offer/start-offer.mjs (1)
  • response (29-34)
components/ashby/actions/create-application/create-application.mjs (1)
components/ashby/ashby.app.mjs (2)
  • response (255-260)
  • response (365-372)
⏰ 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 (12)
components/ashby/package.json (1)

3-3: LGTM! Version bump and dependency addition are appropriate.

The version bump to 0.1.0 reflects the addition of new components, and the @pipedream/platform dependency is correctly added to support the expanded integration.

Also applies to: 15-16

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

1-2: LGTM! Correct import for Pipedream platform.


271-354: LGTM! Clean and consistent API wrapper methods.

The endpoint wrappers follow a consistent pattern and properly delegate to the post helper.

components/ashby/actions/create-application/create-application.mjs (1)

70-99: LGTM! Clean action implementation.

The run method properly destructures props, calls the API wrapper, and exports an appropriate summary.

components/ashby/actions/create-candidate/create-candidate.mjs (1)

95-141: LGTM! Clean implementation with proper conditional location handling.

The run method correctly builds the candidate data payload and conditionally includes the location object only when at least one location field is provided.

components/ashby/actions/start-offer-process/start-offer-process.mjs (1)

23-40: LGTM! Clean and straightforward action implementation.

components/ashby/actions/create-interview-schedule/create-interview-schedule.mjs (2)

24-50: LGTM! Excellent prop documentation.

The detailed description with example JSON structure provides clear guidance on the expected format for interview events.


52-71: LGTM! Proper use of parseJson utility.

The run method correctly parses the interview events array using the shared utility before sending to the API.

components/ashby/actions/start-offer/start-offer.mjs (1)

23-40: LGTM! Clean and consistent action implementation.

components/ashby/actions/create-offer/create-offer.mjs (1)

56-61: Previous critical issue resolved.

The destructuring now correctly includes app, fixing the ReferenceError that was flagged in the previous review.

components/ashby/actions/list-applications/list-applications.mjs (2)

1-13: LGTM! Metadata is appropriate for a read-only list action.

The readOnlyHint: true annotation correctly reflects the non-mutating nature of this list operation.


64-96: LGTM! Pagination and date handling implemented correctly.

The run method properly:

  • Transforms the createdAfter ISO string to Unix timestamp in milliseconds for the API
  • Uses the pagination helper with appropriate keyField and max parameters
  • Provides a clear summary with the count of retrieved applications

- **path** (string, required): The form field's "path" value
- **value** (string, required): The field value (can be a primitive or complex type depending on field type)
You can find these referebce values in the response of the [offer.start API](https://developers.ashbyhq.com/reference/offerstart).
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

Fix typo in description.

"referebce" should be "reference".

Apply this diff:

-You can find these referebce values in the response of the [offer.start API](https://developers.ashbyhq.com/reference/offerstart).
+You can find these reference values in the response of the [offer.start API](https://developers.ashbyhq.com/reference/offerstart).
📝 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
You can find these referebce values in the response of the [offer.start API](https://developers.ashbyhq.com/reference/offerstart).
You can find these reference values in the response of the [offer.start API](https://developers.ashbyhq.com/reference/offerstart).
🤖 Prompt for AI Agents
In components/ashby/actions/create-offer/create-offer.mjs around line 35,
there's a typo in the inline description: change "referebce" to "reference" so
the sentence reads "You can find these reference values in the response of the
[offer.start API](https://developers.ashbyhq.com/reference/offerstart)." Make
this single-word correction and keep the rest of the text and link unchanged.

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.

Ashby

4 participants