Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions components/box/actions/create-sign-request/create-sign-request.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { ConfigurationError } from "@pipedream/platform";
import app from "../../box.app.mjs";
import utils from "../../common/utils.mjs";

export default {
name: "Create Box Sign Request",
description: "Creates a signature request. This involves preparing a document for signing and sending the signature request to signers. [See the documentation](https://developer.box.com/reference/post-sign-requests/).",
key: "box-create-sign-request",
version: "0.0.1",
type: "action",
props: {
app,
signers: {
type: "string[]",
label: "Signers",
description: "Array of signers for the signature request. Each signer should be a JSON object with at least an email property. [See the documentation](https://developer.box.com/reference/post-sign-requests/#param-signers) for more information. Example: `{\"email\": \"[email protected]\", \"role\": \"signer\"}`",
},
name: {
type: "string",
label: "Request Name",
description: "Name of the signature request",
optional: true,
},
parentFolder: {
propDefinition: [
app,
"parentId",
],
label: "Parent Folder",
description: "The destination folder to place final, signed document and signing log. Uses root folder (0) if not specified",
optional: true,
},
emailSubject: {
type: "string",
label: "Email Subject",
description: "Subject of sign request email. If not provided, a default subject will be used",
optional: true,
},
emailMessage: {
type: "string",
label: "Email Message",
description: "Message to include in sign request email. If not provided, a default message will be used",
optional: true,
},
redirectUrl: {
type: "string",
label: "Redirect URL",
description: "The URI that a signer will be redirected to after signing a document",
optional: true,
},
declinedRedirectUrl: {
type: "string",
label: "Declined Redirect URL",
description: "The URI that a signer will be redirected to after declining to sign a document",
optional: true,
},
additionalOptions: {
type: "object",
label: "Additional Options",
description: "Additional parameters to send in the request. See the [documentation](https://developer.box.com/reference/post-sign-requests/) for all available parameters. Values will be parsed as JSON where applicable",
optional: true,
},
},
async run({ $ }) {
const {
signers,
name,
parentFolder,
emailSubject,
emailMessage,
redirectUrl,
declinedRedirectUrl,
additionalOptions,
} = this;

const parsedSigners = signers.map((signer) => {
try {
return typeof signer === "string"
? JSON.parse(signer)
: signer;
} catch (error) {
throw new ConfigurationError(`Error parsing signer as JSON: ${error}`);
}
});

const data = {
signers: parsedSigners,
name,
email_subject: emailSubject,
email_message: emailMessage,
redirect_url: redirectUrl,
declined_redirect_url: declinedRedirectUrl,
...(additionalOptions
? utils.parseObjectEntries(additionalOptions)
: {}),
};
Comment on lines +86 to +96
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

Missing required source_files — API will 400 without it.

Box’s POST /sign_requests requires source_files (or an alternative like prefill_template/draft_id). The action doesn’t expose this, relying on additionalOptions, which is easy to miss.

Add a dedicated, required prop for source files and map to the payload:

@@
   props: {
     app,
     signers: {
       type: "string[]",
       label: "Signers",
       description: "Array of signers for the signature request. Each signer should be a JSON object with at least an email property. [See the documentation](https://developer.box.com/reference/post-sign-requests/#param-signers) for more information. Example: `{\"email\": \"[email protected]\", \"role\": \"signer\"}`",
     },
+    sourceFiles: {
+      type: "string[]",
+      label: "Source Files",
+      description: "Array of Box file IDs to include in the sign request.",
+    },
@@
   async run({ $ }) {
     const {
       signers,
+      sourceFiles,
       name,
       parentFolder,
       emailSubject,
       emailMessage,
       redirectUrl,
       declinedRedirectUrl,
       additionalOptions,
     } = this;
@@
-    const data = {
+    if (!Array.isArray(sourceFiles) || sourceFiles.length === 0) {
+      throw new ConfigurationError("At least one source file is required (sourceFiles).");
+    }
+
+    const data = {
       signers: parsedSigners,
+      source_files: sourceFiles.map((id) => ({
+        id,
+        type: "file",
+      })),
       name,
       email_subject: emailSubject,
       email_message: emailMessage,
       redirect_url: redirectUrl,
       declined_redirect_url: declinedRedirectUrl,
       ...(additionalOptions
         ? utils.parseObjectEntries(additionalOptions)
         : {}),
     };

If you prefer to keep additionalOptions as the only path, at minimum validate that additionalOptions.source_files exists and is non-empty, and throw a clear ConfigurationError otherwise.

📝 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
const data = {
signers: parsedSigners,
name,
email_subject: emailSubject,
email_message: emailMessage,
redirect_url: redirectUrl,
declined_redirect_url: declinedRedirectUrl,
...(additionalOptions
? utils.parseObjectEntries(additionalOptions)
: {}),
};
// In the action definition:
props: {
app,
signers: {
type: "string[]",
label: "Signers",
description: "Array of signers for the signature request. Each signer should be a JSON object with at least an email property. [See the documentation](https://developer.box.com/reference/post-sign-requests/#param-signers) for more information. Example: `{\"email\": \"[email protected]\", \"role\": \"signer\"}`",
},
sourceFiles: {
type: "string[]",
label: "Source Files",
description: "Array of Box file IDs to include in the sign request.",
},
parentFolder: {
type: "string",
// ...other existing props...
},
emailSubject: {
type: "string",
// ...
},
emailMessage: {
type: "string",
// ...
},
redirectUrl: {
type: "string",
// ...
},
declinedRedirectUrl: {
type: "string",
// ...
},
additionalOptions: {
type: "object",
// ...
},
},
async run({ $ }) {
const {
signers,
sourceFiles,
name,
parentFolder,
emailSubject,
emailMessage,
redirectUrl,
declinedRedirectUrl,
additionalOptions,
} = this;
// Validate required sourceFiles
if (!Array.isArray(sourceFiles) || sourceFiles.length === 0) {
throw new ConfigurationError(
"At least one source file is required (sourceFiles)."
);
}
const data = {
signers: parsedSigners,
source_files: sourceFiles.map((id) => ({
id,
type: "file",
})),
name,
email_subject: emailSubject,
email_message: emailMessage,
redirect_url: redirectUrl,
declined_redirect_url: declinedRedirectUrl,
...(additionalOptions
? utils.parseObjectEntries(additionalOptions)
: {}),
};
// ...rest of the implementation...
}
🤖 Prompt for AI Agents
In components/box/actions/create-sign-request/create-sign-request.mjs around
lines 86 to 96, the payload for the Box API call is missing the required
source_files property, causing a 400 error. Add a dedicated required parameter
for source_files to the function and include it explicitly in the data object
sent to the API. Alternatively, if you want to keep using additionalOptions, add
validation to check that additionalOptions.source_files exists and is not empty,
and throw a ConfigurationError with a clear message if this validation fails.


if (parentFolder) {
data.parent_folder = {
id: parentFolder,
type: "folder",
};
}

const response = await this.app.createSignRequest({
$,
data,
});

$.export("$summary", `Successfully created Box sign request (ID: ${response.id})`);
return response;
},
};
7 changes: 7 additions & 0 deletions components/box/box.app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -319,5 +319,12 @@ export default {
...args,
});
},
async createSignRequest(args = {}) {
return this._makeRequest({
method: "POST",
path: "/sign_requests",
...args,
});
},
},
};
22 changes: 22 additions & 0 deletions components/box/common/utils.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
import constants from "./constants.mjs";
import { getFileStreamAndMetadata } from "@pipedream/platform";

function optionalParseAsJSON(value) {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}

export default {
parseObjectEntries(value = {}) {
const obj = typeof value === "string"
? JSON.parse(value)
: value;
return Object.fromEntries(
Object.entries(obj).map(([
key,
value,
]) => [
key,
optionalParseAsJSON(value),
]),
);
},
Comment on lines +13 to +26
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against invalid top-level JSON in parseObjectEntries.

If value is a string and not valid JSON, JSON.parse will throw and crash the action. Wrap parsing in try/catch and surface a clear configuration error.

-import constants from "./constants.mjs";
-import { getFileStreamAndMetadata } from "@pipedream/platform";
+import constants from "./constants.mjs";
+import { getFileStreamAndMetadata, ConfigurationError } from "@pipedream/platform";
@@
 export default {
-  parseObjectEntries(value = {}) {
-    const obj = typeof value === "string"
-      ? JSON.parse(value)
-      : value;
+  parseObjectEntries(value = {}) {
+    let obj = value;
+    if (typeof value === "string") {
+      try {
+        obj = JSON.parse(value);
+      } catch (e) {
+        throw new ConfigurationError("Invalid JSON for Additional Options. Provide a valid JSON object.");
+      }
+    }
     return Object.fromEntries(
       Object.entries(obj).map(([
         key,
         value,
       ]) => [
         key,
         optionalParseAsJSON(value),
       ]),
     );
   },
📝 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
parseObjectEntries(value = {}) {
const obj = typeof value === "string"
? JSON.parse(value)
: value;
return Object.fromEntries(
Object.entries(obj).map(([
key,
value,
]) => [
key,
optionalParseAsJSON(value),
]),
);
},
import constants from "./constants.mjs";
import { getFileStreamAndMetadata, ConfigurationError } from "@pipedream/platform";
export default {
parseObjectEntries(value = {}) {
let obj = value;
if (typeof value === "string") {
try {
obj = JSON.parse(value);
} catch (e) {
throw new ConfigurationError(
"Invalid JSON for Additional Options. Provide a valid JSON object."
);
}
}
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [
key,
optionalParseAsJSON(value),
]),
);
},
// …other methods
};
🤖 Prompt for AI Agents
In components/box/common/utils.mjs around lines 13 to 26, the parseObjectEntries
function directly calls JSON.parse on a string input, which can throw an error
if the string is not valid JSON. To fix this, wrap the JSON.parse call in a
try/catch block, and if parsing fails, throw a clear and descriptive
configuration error to prevent the action from crashing unexpectedly.

async getFileData(filePath) {
const {
stream, metadata,
Expand Down
2 changes: 1 addition & 1 deletion components/box/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/box",
"version": "0.4.1",
"version": "0.5.0",
"description": "Pipedream Box Components",
"main": "box.app.mjs",
"keywords": [
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading