Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
119 changes: 0 additions & 119 deletions components/csvbox/actions/submit-spreadsheet/submit-spreadsheet.mjs

This file was deleted.

5 changes: 5 additions & 0 deletions components/csvbox/common/constants.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const BASE_URL = "https://api.csvbox.io/1.1/pipedream";

export default {
BASE_URL
};
147 changes: 147 additions & 0 deletions components/csvbox/csvbox-new-row/csvbox-new-row.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import app from "../csvbox.app.mjs";

export default {
key: "csvbox-new-row",
name: "New Import",
description: "Emit new events when a new row is added to a CSVBox sheet",
version: "0.0.1",
type: "source",
dedupe: "unique",
props: {
db: "$.service.db",
app,
sheetId: {
propDefinition: [app, "sheetId"],
description: "Select the sheet to receive data from",
},
http: "$.interface.http",
},
hooks: {
async activate() {
if (!this.sheetId) {
throw new Error("Sheet selection is required before activation.");
}

try {
const { data } = await this.app.createHook({
data: {
sheet_slug: this.sheetId,
webhook_url: this.http.endpoint,
},
});

const { webhookId, sample_response } = data;
const hookId = webhookId;
this._setHookID(hookId);

if (!Array.isArray(sample_response) || sample_response.length === 0) {
throw new Error("Unable to fetch sample data from selected sheet.");
}

const first = sample_response[0];
this.$emit({
import_id: `sample_${Date.now()}`,
sheet_id: this.sheetId,
sheet_name: first.sheet_name || "Sample Data",
row_number: first.row_number || 1,
row_data: first.row_data || first,
total_rows: first.total_rows || 10,
env_name: first.env_name || "default",
custom_fields: first.custom_fields || { user_id: "default123" },
import_description: first.import_description || "This is a sample test import",
original_filename: first.original_filename || "product_details.csv",
}, {
id: `sample_${Date.now()}`,
summary: `Sample data loaded from sheet - ${first.sheet_name} `,
ts: Date.now(),
});


this._setSampleRow(first);
} catch (err) {
console.error("Error during source activation:", err);
throw new Error(err?.message || "Failed to register webhook or fetch sample data.");
}
},
async deactivate() {
try {
const hookId = this._getHookID();
if (hookId) {
await this.app.deleteHook({
data: {
webhook_id: hookId,
sheet_slug: this.sheetId,
},
});
this.db.set("hookId", null);
this.db.set("sampleRow", null);
}
} catch (err) {
console.error("Deactivation Error:", err);
}
},
async deploy() {
const sampleRow = this._getSampleRow();
if (sampleRow) {
this.$emit(sampleRow, {
id: `sample_${Date.now()}`,
summary: "Sample row event",
ts: Date.now(),
});
}
else {
console.log("No sample row data found to emit during deploy.");
return;
}
},
},
methods: {
_getHookID() {
return this.db.get("hookId");
},
_setHookID(hookID) {
this.db.set("hookId", hookID);
},
_getSampleRow() {
return this.db.get("sampleRow");
},
_setSampleRow(rowData) {
this.db.set("sampleRow", rowData);
},
},
async run(event) {
const { body } = event;
if (!body) {
console.error("Received empty webhook body");
return;
}

this.$emit(body, {
id: body[0].import_id || `${body[0].sheet_id}_${Date.now()}`,
summary: `New data imported to sheet ${body[0].sheet_name}`,
ts: Date.now(),
});
Comment on lines +112 to +123
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

Guard against empty or non-array webhook payloads.

CSVBox sends webhook payloads as JSON arrays of rows, but chunking and configuration can yield empty arrays; dereferencing body[0] without checking will throw and drop the delivery. Add a shape check and iterate the rows (or at least guard the first element) before emitting.(help.csvbox.io)

   async run(event) {
     const { body } = event;
-    if (!body) {
-      console.error("Received empty webhook body");
-      return;
-    }
-
-    this.$emit(body, {
-      id: body[0].import_id || `${body[0].sheet_id}_${Date.now()}`,
-      summary: `New data imported to sheet ${body[0].sheet_name}`,
-      ts: Date.now(),
-    });
+    if (!Array.isArray(body) || body.length === 0) {
+      console.error("Received webhook payload without row data");
+      return;
+    }
+
+    for (const row of body) {
+      this.$emit(row, {
+        id: row.import_id || `${row.sheet_id}_${Date.now()}`,
+        summary: `New data imported to sheet ${row.sheet_name}`,
+        ts: Date.now(),
+      });
+    }
   },
📝 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 run(event) {
const { body } = event;
if (!body) {
console.error("Received empty webhook body");
return;
}
this.$emit(body, {
id: body[0].import_id || `${body[0].sheet_id}_${Date.now()}`,
summary: `New data imported to sheet ${body[0].sheet_name}`,
ts: Date.now(),
});
async run(event) {
const { body } = event;
if (!Array.isArray(body) || body.length === 0) {
console.error("Received webhook payload without row data");
return;
}
for (const row of body) {
this.$emit(row, {
id: row.import_id || `${row.sheet_id}_${Date.now()}`,
summary: `New data imported to sheet ${row.sheet_name}`,
ts: Date.now(),
});
}
}

},
sampleEvents: [
{
import_id: 79418895,
sheet_id: 55,
sheet_name: "Products",
row_number: 1,
row_data: {
"col1": "",
"col2": "",
"col3": "",
"col4": "",
"col5": "",
},
total_rows: 10,
env_name: "default",
custom_fields: {
user_id: "default123"
},
import_description: "This is a sample test import",
original_filename: "product_details.csv",
}
],
};
86 changes: 56 additions & 30 deletions components/csvbox/csvbox.app.mjs
Original file line number Diff line number Diff line change
@@ -1,55 +1,81 @@
import { axios } from "@pipedream/platform";
import constants from "./common/constants.mjs";

export default {
type: "app",
app: "csvbox",
propDefinitions: {
sheetLicenseKey: {
sheetId: {
type: "string",
label: "Sheet License Key",
description: "The unique identifier for your CSVBox sheet. You can find it in **Sheets - Edit - Code Snippet - Sheet License Key**.",
},
userId: {
type: "string",
label: "User ID",
description: "The unique identifier for the user. You can find it in the **Dashboard - Edit - Code Snippet**.",
optional: true,
},
hasHeaders: {
type: "boolean",
label: "Has Headers",
description: "Whether the spreadsheet has headers.",
label: "Sheet",
description: "Select the sheet you want to receive data from",
optional: true,
async options() {
const { data } = await this.listSheets();
return data.map((sheet) => ({
label: sheet.name,
value: sheet.value,
}));
},
},
},

methods: {
getUrl(path) {
return `https://api.csvbox.io/1.1${path}`;
_getAuthKeys() {
return this.$auth.api_key;
},
_getSecretAuthKeys() {
return this.$auth.secret_api_key;
},
getHeaders(headers) {
_getUrl(path) {
return `${constants.BASE_URL}${path}`;
},
_getHeaders(headers) {
return {
"Content-Type": "application/json",
"x-csvbox-api-key": `${this.$auth.api_key}`,
"x-csvbox-secret-api-key": `${this.$auth.secret_api_key}`,
...headers,
accept: "application/json",
"Content-Type": "application/json",
"x-csvbox-api-key": this._getAuthKeys(),
"x-csvbox-secret-api-key": this._getSecretAuthKeys(),
};
},
_makeRequest({
$ = this, path, headers, ...args
} = {}) {
return axios($, {
debug: true,
url: this.getUrl(path),
headers: this.getHeaders(headers),

async _makeRequest({ $ = this, path, headers, ...otherConfig } = {}) {
const config = {
url: this._getUrl(path),
headers: this._getHeaders(headers),
auth: this._getAuthKeys(),
returnFullResponse: true,
...otherConfig,
};
return axios($, config);
},

async createHook({ data, ...args } = {}) {
return this._makeRequest({
method: "POST",
path: "/register-webhook",
data,
...args,
});
},
submitFile(args = {}) {

async deleteHook({ data, ...args } = {}) {
return this._makeRequest({
method: "POST",
path: "/file",
method: "DELETE",
path: `/delete-webhook`,
data,
...args,
});
},

async listSheets(args = {}) {
const res = await this._makeRequest({
method: "GET",
path: "/list-sheets",
...args,
});
return res;
},
},
};
Loading