From bd37d5be007a1db7e05658803403b8f97f81187d Mon Sep 17 00:00:00 2001 From: Sumit Yewale Date: Wed, 5 Nov 2025 14:56:19 +0530 Subject: [PATCH 1/4] Code: Custom CSVBOX source implemented --- components/csvbox/common/constants.mjs | 5 + .../csvbox/csvbox-new-row/csvbox-new-row.mjs | 136 ++++++++++++++++++ components/csvbox/csvbox.app.mjs | 98 ++++++++++++- components/csvbox/package.json | 3 + 4 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 components/csvbox/common/constants.mjs create mode 100644 components/csvbox/csvbox-new-row/csvbox-new-row.mjs diff --git a/components/csvbox/common/constants.mjs b/components/csvbox/common/constants.mjs new file mode 100644 index 0000000000000..ef40706498c53 --- /dev/null +++ b/components/csvbox/common/constants.mjs @@ -0,0 +1,5 @@ +const BASE_URL = "https://apistaging.csvbox.io/1.1/pipedream"; + +export default { + BASE_URL +}; \ No newline at end of file diff --git a/components/csvbox/csvbox-new-row/csvbox-new-row.mjs b/components/csvbox/csvbox-new-row/csvbox-new-row.mjs new file mode 100644 index 0000000000000..a7831494858ea --- /dev/null +++ b/components/csvbox/csvbox-new-row/csvbox-new-row.mjs @@ -0,0 +1,136 @@ +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() { + try { + const { data } = await this.app.createHook({ + data: { + sheet_id: this.sheetId, + webhook_url: this.http.endpoint, + }, + }); + this._setHookID(data); + + const rows = await this.app.getRows({ + sheetId: this.sheetId, + }); + + console.log("Fetched sample rows:", rows); + // Store sample row if available + if (rows?.length > 0) { + this._setSampleRow(rows[0]); + } + } catch (err) { + console.log("Activation Error:", err); + throw err; + } + }, + async deactivate() { + try { + const hookId = this._getHookID(); + console.log("Deactivate webhook getHookId ", hookId); + + await this.app.deleteHook({ + data: { + webhook_id: hookId, + sheet_id: this.sheetId, + }, + }); + this.db.set("hookId", null); + // if (hookId) { + // } + } catch (err) { + console.error("Deactivation Error:", err); + } + }, + async deploy() { + console.log("Deploy hook called"); + const sampleRow = this._getSampleRow(); + if(sampleRow) { + this.$emit({ + import_id: 79418895, + sheet_id: 5, + sheet_name: "Products", + row_number: 1, + row_data: sampleRow, + total_rows: 1, + env_name: "default", + custom_fields: { + user_id: "default123" + }, + import_description: "This is a sample test import", + original_filename: "product_details.csv" + }, { + id: `sample_${Date.now()}`, + summary: "Sample row event", + ts: Date.now(), + }); + } + }, + }, + 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(), + }); + }, + 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", + } + ], +}; diff --git a/components/csvbox/csvbox.app.mjs b/components/csvbox/csvbox.app.mjs index 649830c196202..2c7dc546fb951 100644 --- a/components/csvbox/csvbox.app.mjs +++ b/components/csvbox/csvbox.app.mjs @@ -1,11 +1,101 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "csvbox", - propDefinitions: {}, + propDefinitions: { + sheetId: { + type: "string", + 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: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _getAuthKeys() { + return this.$auth.api_key; + }, + _getSecretAuthKeys() { + return this.$auth.secret_api_key; + }, + _getUrl(path) { + return `${constants.BASE_URL}${path}`; + }, + _getHeaders(headers) { + return { + ...headers, + accept: "application/json", + "Content-Type": "application/json", + "x-csvbox-api-key": this._getAuthKeys(), + "x-csvbox-secret-api-key": this._getSecretAuthKeys(), + }; + }, + + 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, + }); + }, + + async deleteHook({ data, ...args } = {}) { + console.log("delete hook data ", data); + + // if (!hookId) { + // throw new Error("Hook ID is required"); + // } + return this._makeRequest({ + method: "DELETE", + path: `/delete-webhook`, + data, + ...args, + }); + }, + + async listSheets(args = {}) { + console.log("Listing sheets...", this.methods); + const res = await this._makeRequest({ + method: "GET", + path: "/list-sheets", + ...args, + }); + console.log("Sheets response:", res); + return res; + }, + + async getRows({ sheetId, ...args } = {}) { + const res = await this._makeRequest({ + method: "GET", + path: `/sheets/${sheetId}`, + ...args, + }); + + const data = res?.data ?? res; + console.log("get sample row ", data); + + return data; }, }, }; \ No newline at end of file diff --git a/components/csvbox/package.json b/components/csvbox/package.json index f24a1858e330d..2468e8cf4ad50 100644 --- a/components/csvbox/package.json +++ b/components/csvbox/package.json @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } \ No newline at end of file From fca35f35bd4c0474d2d05566e8a4971ad9e12cb3 Mon Sep 17 00:00:00 2001 From: Sumit Yewale Date: Thu, 6 Nov 2025 18:12:22 +0530 Subject: [PATCH 2/4] code: fixes added for calling csvbox api we getting response from API --- components/csvbox/common/constants.mjs | 2 +- .../csvbox/csvbox-new-row/csvbox-new-row.mjs | 87 +++++++++++-------- components/csvbox/csvbox.app.mjs | 20 ----- 3 files changed, 50 insertions(+), 59 deletions(-) diff --git a/components/csvbox/common/constants.mjs b/components/csvbox/common/constants.mjs index ef40706498c53..8be9fb7c080e9 100644 --- a/components/csvbox/common/constants.mjs +++ b/components/csvbox/common/constants.mjs @@ -1,4 +1,4 @@ -const BASE_URL = "https://apistaging.csvbox.io/1.1/pipedream"; +const BASE_URL = "https://apistagingishwar.csvbox.io/1.1/pipedream"; export default { BASE_URL diff --git a/components/csvbox/csvbox-new-row/csvbox-new-row.mjs b/components/csvbox/csvbox-new-row/csvbox-new-row.mjs index a7831494858ea..523dd8eb2c30d 100644 --- a/components/csvbox/csvbox-new-row/csvbox-new-row.mjs +++ b/components/csvbox/csvbox-new-row/csvbox-new-row.mjs @@ -18,70 +18,81 @@ export default { }, hooks: { async activate() { + if (!this.sheetId) { + throw new Error("Sheet selection is required before activation."); + } + try { const { data } = await this.app.createHook({ data: { - sheet_id: this.sheetId, + sheet_slug: this.sheetId, webhook_url: this.http.endpoint, }, }); - this._setHookID(data); - const rows = await this.app.getRows({ - sheetId: this.sheetId, - }); + const { webhookId, sample_response } = data; + const hookId = webhookId; + this._setHookID(hookId); - console.log("Fetched sample rows:", rows); - // Store sample row if available - if (rows?.length > 0) { - this._setSampleRow(rows[0]); + 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.log("Activation Error:", err); - throw 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(); - console.log("Deactivate webhook getHookId ", hookId); - - await this.app.deleteHook({ - data: { - webhook_id: hookId, - sheet_id: this.sheetId, - }, - }); - this.db.set("hookId", null); - // if (hookId) { - // } + 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() { - console.log("Deploy hook called"); const sampleRow = this._getSampleRow(); - if(sampleRow) { - this.$emit({ - import_id: 79418895, - sheet_id: 5, - sheet_name: "Products", - row_number: 1, - row_data: sampleRow, - total_rows: 1, - env_name: "default", - custom_fields: { - user_id: "default123" - }, - import_description: "This is a sample test import", - original_filename: "product_details.csv" - }, { + 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: { diff --git a/components/csvbox/csvbox.app.mjs b/components/csvbox/csvbox.app.mjs index 2c7dc546fb951..3b861c7e965f6 100644 --- a/components/csvbox/csvbox.app.mjs +++ b/components/csvbox/csvbox.app.mjs @@ -61,11 +61,6 @@ export default { }, async deleteHook({ data, ...args } = {}) { - console.log("delete hook data ", data); - - // if (!hookId) { - // throw new Error("Hook ID is required"); - // } return this._makeRequest({ method: "DELETE", path: `/delete-webhook`, @@ -75,27 +70,12 @@ export default { }, async listSheets(args = {}) { - console.log("Listing sheets...", this.methods); const res = await this._makeRequest({ method: "GET", path: "/list-sheets", ...args, }); - console.log("Sheets response:", res); return res; }, - - async getRows({ sheetId, ...args } = {}) { - const res = await this._makeRequest({ - method: "GET", - path: `/sheets/${sheetId}`, - ...args, - }); - - const data = res?.data ?? res; - console.log("get sample row ", data); - - return data; - }, }, }; \ No newline at end of file From 5cc5ea586fdd63f458a90436c0ed253655b6814b Mon Sep 17 00:00:00 2001 From: Sumit Yewale Date: Fri, 7 Nov 2025 12:05:10 +0530 Subject: [PATCH 3/4] Fix: updated API from constant --- components/csvbox/common/constants.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/csvbox/common/constants.mjs b/components/csvbox/common/constants.mjs index 8be9fb7c080e9..cf18bd2dfe4d1 100644 --- a/components/csvbox/common/constants.mjs +++ b/components/csvbox/common/constants.mjs @@ -1,4 +1,4 @@ -const BASE_URL = "https://apistagingishwar.csvbox.io/1.1/pipedream"; +const BASE_URL = "https://api.csvbox.io/1.1/pipedream"; export default { BASE_URL From fa298358a24b7d89bfbf2c7f088a07f6418502a5 Mon Sep 17 00:00:00 2001 From: Sumit Yewale Date: Fri, 7 Nov 2025 16:19:04 +0530 Subject: [PATCH 4/4] Fixed: Removed unnecessory files and updated the source name --- .../submit-spreadsheet/submit-spreadsheet.mjs | 119 ------------------ .../csvbox/csvbox-new-row/csvbox-new-row.mjs | 2 +- .../csvbox/sources/new-import/new-import.mjs | 33 ----- .../csvbox/sources/new-import/test-event.mjs | 97 -------------- 4 files changed, 1 insertion(+), 250 deletions(-) delete mode 100644 components/csvbox/actions/submit-spreadsheet/submit-spreadsheet.mjs delete mode 100644 components/csvbox/sources/new-import/new-import.mjs delete mode 100644 components/csvbox/sources/new-import/test-event.mjs diff --git a/components/csvbox/actions/submit-spreadsheet/submit-spreadsheet.mjs b/components/csvbox/actions/submit-spreadsheet/submit-spreadsheet.mjs deleted file mode 100644 index 7e6f6baa6a1f2..0000000000000 --- a/components/csvbox/actions/submit-spreadsheet/submit-spreadsheet.mjs +++ /dev/null @@ -1,119 +0,0 @@ -import FormData from "form-data"; -import { getFileStream } from "@pipedream/platform"; -import app from "../../csvbox.app.mjs"; - -export default { - key: "csvbox-submit-spreadsheet", - name: "Submit Spreadsheet", - description: "Submit a spreadsheet file via public URL or local file path to CSVBox for processing. [See documentation](https://help.csvbox.io/advanced-installation/rest-file-api)", - version: "0.0.1", - type: "action", - props: { - app, - file: { - type: "string", - label: "File Path Or URL", - description: "Provide either a file URL or a path to a file in the `/tmp` directory (for example, `/tmp/spreadsheet.csv`).", - }, - sheetLicenseKey: { - propDefinition: [ - app, - "sheetLicenseKey", - ], - }, - userId: { - propDefinition: [ - app, - "userId", - ], - }, - hasHeaders: { - propDefinition: [ - app, - "hasHeaders", - ], - }, - syncDir: { - type: "dir", - accessMode: "read", - sync: true, - optional: true, - }, - }, - annotations: { - readOnlyHint: false, - destructiveHint: false, - openWorldHint: true, - }, - methods: { - booleanToNumber(value) { - return value === true || value === "true" || value === "1" || value === 1 - ? 1 - : 0; - }, - }, - async run({ $ }) { - const { - app, - booleanToNumber, - file, - sheetLicenseKey, - userId, - hasHeaders, - } = this; - let data; - - const isUrl = file?.startsWith("http://") || file?.startsWith("https://"); - - const otherFields = { - ...(userId - ? { - user: { - user_id: userId, - }, - } - : {} - ), - ...(hasHeaders - ? { - options: { - has_header: booleanToNumber(hasHeaders), - }, - } - : {} - ), - }; - - if (isUrl) { - data = { - import: { - public_file_url: file, - sheet_license_key: sheetLicenseKey, - ...otherFields, - }, - }; - - } else { - data = new FormData(); - data.append("file", await getFileStream(file)); - data.append("import", JSON.stringify({ - sheet_license_key: sheetLicenseKey, - ...otherFields, - })); - } - - const response = await app.submitFile({ - $, - headers: !isUrl - ? { - "Content-Type": "multipart/form-data", - } - : undefined, - data, - }); - - $.export("$summary", "Successfully submitted spreadsheet"); - - return response; - }, -}; diff --git a/components/csvbox/csvbox-new-row/csvbox-new-row.mjs b/components/csvbox/csvbox-new-row/csvbox-new-row.mjs index 523dd8eb2c30d..61bd1e64621dc 100644 --- a/components/csvbox/csvbox-new-row/csvbox-new-row.mjs +++ b/components/csvbox/csvbox-new-row/csvbox-new-row.mjs @@ -2,7 +2,7 @@ import app from "../csvbox.app.mjs"; export default { key: "csvbox-new-row", - name: "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", diff --git a/components/csvbox/sources/new-import/new-import.mjs b/components/csvbox/sources/new-import/new-import.mjs deleted file mode 100644 index d78bc28421356..0000000000000 --- a/components/csvbox/sources/new-import/new-import.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { ConfigurationError } from "@pipedream/platform"; -import app from "../../csvbox.app.mjs"; -import sampleEvent from "./test-event.mjs"; - -export default { - key: "csvbox-new-import", - name: "New Import", - description: "Emit new event when CSVBox receives and processes a new import. [See documentation](https://help.csvbox.io/destinations#api-webhook)", - version: "0.0.1", - type: "source", - dedupe: "unique", - props: { - app, - http: "$.interface.http", - }, - async run({ - headers, body, - }) { - - if (!headers["content-type"] || headers["content-type"] !== "application/json") { - throw new ConfigurationError("Invalid content type. Please check your CSVBox webhook configuration so that the content type is set to `JSON`."); - } - - const ts = Date.now(); - - this.$emit(body, { - id: ts, - summary: "New import has been processed", - ts, - }); - }, - sampleEvent, -}; diff --git a/components/csvbox/sources/new-import/test-event.mjs b/components/csvbox/sources/new-import/test-event.mjs deleted file mode 100644 index 597a3d023f151..0000000000000 --- a/components/csvbox/sources/new-import/test-event.mjs +++ /dev/null @@ -1,97 +0,0 @@ -export default [ - { - "import_id": 1841488, - "sheet_id": 19247, - "sheet_name": "Test 100", - "row_number": 1, - "total_rows": 5, - "env_name": "default", - "original_filename": "basicScience.csv", - "custom_fields": [], - "row_data": { - "ID": "2", - "Question": "What type of paper detects acids and alkali in liquid?", - "Answer": "Litmus Paper", - "Option B": "Filter paper", - "Option C": "Hydrion Paper", - "Option D": "Proton Paper" - }, - "import_description": "" - }, - { - "import_id": 1841488, - "sheet_id": 19247, - "sheet_name": "Test 100", - "row_number": 2, - "total_rows": 5, - "env_name": "default", - "original_filename": "basicScience.csv", - "custom_fields": [], - "row_data": { - "ID": "3", - "Question": "What is the only metal to be liquid at room temperature? ", - "Answer": "Mercury", - "Option B": "Nickel", - "Option C": "Chromium", - "Option D": "Zirconium" - }, - "import_description": "" - }, - { - "import_id": 1841488, - "sheet_id": 19247, - "sheet_name": "Test 100", - "row_number": 3, - "total_rows": 5, - "env_name": "default", - "original_filename": "basicScience.csv", - "custom_fields": [], - "row_data": { - "ID": "4", - "Question": "What is the chemical name of aqua fortis?", - "Answer": "Nitric Acid", - "Option B": "Hydrochloric Acid", - "Option C": "Benzoic Acid", - "Option D": "Acetic Acid" - }, - "import_description": "" - }, - { - "import_id": 1841488, - "sheet_id": 19247, - "sheet_name": "Test 100", - "row_number": 4, - "total_rows": 5, - "env_name": "default", - "original_filename": "basicScience.csv", - "custom_fields": [], - "row_data": { - "ID": "5", - "Question": "What is the fourth state of matter after solid and liquid and gas?", - "Answer": "Plasma", - "Option B": "Ground State", - "Option C": "Metal ", - "Option D": "Flora" - }, - "import_description": "" - }, - { - "import_id": 1841488, - "sheet_id": 19247, - "sheet_name": "Test 100", - "row_number": 5, - "total_rows": 5, - "env_name": "default", - "original_filename": "basicScience.csv", - "custom_fields": [], - "row_data": { - "ID": "6", - "Question": "What is the chemical symbol for Plutonium? ", - "Answer": "Pu", - "Option B": "Pa", - "Option C": "Po", - "Option D": "P" - }, - "import_description": "" - } -];