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/common/constants.mjs b/components/csvbox/common/constants.mjs new file mode 100644 index 0000000000000..cf18bd2dfe4d1 --- /dev/null +++ b/components/csvbox/common/constants.mjs @@ -0,0 +1,5 @@ +const BASE_URL = "https://api.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..61bd1e64621dc --- /dev/null +++ b/components/csvbox/csvbox-new-row/csvbox-new-row.mjs @@ -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(), + }); + }, + 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 ca1e2e6e85bdb..2c8b9c02413be 100644 --- a/components/csvbox/csvbox.app.mjs +++ b/components/csvbox/csvbox.app.mjs @@ -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; }, }, }; diff --git a/components/csvbox/package.json b/components/csvbox/package.json index e561d4c5deded..7f71abcc89347 100644 --- a/components/csvbox/package.json +++ b/components/csvbox/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/csvbox", - "version": "0.1.0", + "version": "0.0.1", "description": "Pipedream CSVbox Components", "main": "csvbox.app.mjs", "keywords": [ @@ -13,7 +13,6 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^3.1.0", - "form-data": "^4.0.4" + "@pipedream/platform": "^3.0.3" } } 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": "" - } -];