Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
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 Row",
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;
},
},
};
5 changes: 2 additions & 3 deletions components/csvbox/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/csvbox",
"version": "0.1.0",
"version": "0.0.1",
"description": "Pipedream CSVbox Components",
"main": "csvbox.app.mjs",
"keywords": [
Expand All @@ -13,7 +13,6 @@
"access": "public"
},
"dependencies": {
"@pipedream/platform": "^3.1.0",
"form-data": "^4.0.4"
"@pipedream/platform": "^3.0.3"
}
}