diff --git a/components/gocanvas/actions/create-dispatch/create-dispatch.mjs b/components/gocanvas/actions/create-dispatch/create-dispatch.mjs new file mode 100644 index 0000000000000..cc21e5d520f90 --- /dev/null +++ b/components/gocanvas/actions/create-dispatch/create-dispatch.mjs @@ -0,0 +1,49 @@ +import gocanvas from "../../gocanvas.app.mjs"; + +export default { + key: "gocanvas-create-dispatch", + name: "Create Dispatch", + description: "Creates a dispatch item in GoCanvas. [See the documentation](https://help.gocanvas.com/hc/en-us/article_attachments/26468076609559)", + version: "0.0.1", + type: "action", + props: { + gocanvas, + form: { + propDefinition: [ + gocanvas, + "form", + ], + description: "The name of the form you want to create a prepopulated submission for", + }, + entries: { + type: "object", + label: "Entries", + description: `DIEntry elements consisting of label/value pairs. + \n Label: Either the Export Label or plain Label field attribute (caseinsensitive) as defined in the form builder. + \n Value: The value assigned to this Dispatch Item entry. + `, + }, + }, + async run({ $ }) { + let entriesString = ""; + for (const [ + key, + value, + ] of Object.entries(this.entries)) { + entriesString += ``; + } + const response = await this.gocanvas.dispatchItems({ + $, + data: ` + + + + ${entriesString} + + + `, + }); + $.export("$summary", `Successfully created dispatch item in ${this.form}`); + return response; + }, +}; diff --git a/components/gocanvas/actions/create-or-update-reference-data/create-or-update-reference-data.mjs b/components/gocanvas/actions/create-or-update-reference-data/create-or-update-reference-data.mjs new file mode 100644 index 0000000000000..3280cc055cde4 --- /dev/null +++ b/components/gocanvas/actions/create-or-update-reference-data/create-or-update-reference-data.mjs @@ -0,0 +1,71 @@ +import gocanvas from "../../gocanvas.app.mjs"; +import { parse } from "csv-parse/sync"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + key: "gocanvas-create-or-update-reference-data", + name: "Create or Update Reference Data", + description: "Creates or updates GoCanvas reference data. [See the documentation](https://help.gocanvas.com/hc/en-us/article_attachments/26468076609559)", + version: "0.0.1", + type: "action", + props: { + gocanvas, + name: { + type: "string", + label: "Reference Data Name", + description: "The attribute name of the dataset to operate on. Will be created if it doesn't already exist.", + }, + data: { + type: "string", + label: "Data", + description: `A string of comma separated values representing the data to create/update. **Include Column names**: + \n Example: + \n Column1,Column2,Column3 + \n Data1Column1,Data1Column2,Data1Column3 + \n Data2Column1,Data2Column2,Data3Column3 + `, + }, + }, + methods: { + csvToXml(data) { + const records = parse(data, { + columns: true, + trim: true, + }); + + if (!records?.length) { + throw new ConfigurationError("No data items found to create/update. Please enter column names and at least 1 row of data."); + } + + // Extract columns + const columns = Object.keys(records[0]); + let result = ""; + result += columns.map((col) => `${col}`).join(""); + result += "\n\n"; + + // Extract rows + result += records + .map((row) => { + const rowValues = columns.map((col) => `${row[col]}`).join(""); + return ` ${rowValues}`; + }) + .join("\n"); + + result += "\n"; + return result; + }, + }, + async run({ $ }) { + const response = await this.gocanvas.createUpdateReferenceData({ + $, + data: ` + + + ${await this.csvToXml(this.data)} + + `, + }); + $.export("$summary", "Successfully created/updated reference data"); + return response; + }, +}; diff --git a/components/gocanvas/actions/delete-dispatch/delete-dispatch.mjs b/components/gocanvas/actions/delete-dispatch/delete-dispatch.mjs new file mode 100644 index 0000000000000..ddafebef637d6 --- /dev/null +++ b/components/gocanvas/actions/delete-dispatch/delete-dispatch.mjs @@ -0,0 +1,46 @@ +import gocanvas from "../../gocanvas.app.mjs"; + +export default { + key: "gocanvas-delete-dispatch", + name: "Delete Dispatch", + description: "Removes a specific dispatch from GoCanvas. [See the documentation](https://help.gocanvas.com/hc/en-us/article_attachments/26468076609559)", + version: "0.0.1", + type: "action", + props: { + gocanvas, + form: { + propDefinition: [ + gocanvas, + "form", + ], + }, + dispatchId: { + propDefinition: [ + gocanvas, + "dispatchId", + (c) => ({ + form: c.form, + }), + ], + }, + }, + async run({ $ }) { + const description = await this.gocanvas.getDispatchDescription({ + $, + dispatchId: this.dispatchId, + }); + const response = await this.gocanvas.dispatchItems({ + $, + data: ` + + + + + + + `, + }); + $.export("$summary", `Successfully deleted dispatch with ID ${this.dispatchId}`); + return response; + }, +}; diff --git a/components/gocanvas/gocanvas.app.mjs b/components/gocanvas/gocanvas.app.mjs index 45e1ba69a4cb3..40ec3505be34a 100644 --- a/components/gocanvas/gocanvas.app.mjs +++ b/components/gocanvas/gocanvas.app.mjs @@ -1,11 +1,151 @@ +import { axios } from "@pipedream/platform"; +import xml2js from "xml2js"; + export default { type: "app", app: "gocanvas", - propDefinitions: {}, + propDefinitions: { + form: { + type: "string", + label: "Form", + description: "The identifier of a form", + async options() { + const forms = await this.listForms(); + return forms?.map((form) => form.Name[0]) || []; + }, + }, + dispatchId: { + type: "string", + label: "Dispatch ID", + description: "Identifier of a dispatch", + async options({ + page, form, + }) { + const dispatches = await this.getActiveDispatches({ + form, + data: { + page: page + 1, + }, + }); + return dispatches?.map(({ + $, Description: desc, + }) => ({ + value: $.Id, + label: desc[0], + })) || []; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://www.gocanvas.com/apiv2/"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + params, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + params: { + ...params, + username: `${this.$auth.username}`, + }, + headers: { + Authorization: `Bearer ${this.$auth.api_key}`, + }, + }); + }, + async getActiveDispatches({ + form, ...opts + }) { + const response = await this._makeRequest({ + path: "/dispatch_export", + ...opts, + }); + const { CanvasResult: { Dispatches: dispatches } } = await new xml2js + .Parser().parseStringPromise(response); + if (!dispatches?.length) { + return []; + } + return dispatches + .flatMap((d) => d.Dispatch || []) + .filter((d) => d.Status[0] !== "deleted") + .filter((d) => !form || d.Form[0] === form); + }, + async listForms(opts = {}) { + const response = await this._makeRequest({ + path: "/forms", + ...opts, + }); + const { CanvasResult: { Forms: forms } } = await new xml2js + .Parser().parseStringPromise(response); + if (!forms?.length) { + return []; + } + return forms.flatMap((form) => form.Form || []); + }, + async listSubmissions(opts = {}) { + const response = await this._makeRequest({ + path: "/submissions", + ...opts, + }); + const { CanvasResult: { Submissions: submissions } } = await new xml2js + .Parser().parseStringPromise(response); + if (!submissions?.length) { + return []; + } + return submissions.flatMap((sub) => sub.Submission || []); + }, + async getDispatchDescription({ + dispatchId, ...opts + }) { + const dispatches = await this.getActiveDispatches({ + ...opts, + }); + const dispatch = dispatches.find(({ $ }) => $.Id === dispatchId); + return dispatch.Description[0]; + }, + dispatchItems(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/dispatch_items", + ...opts, + }); + }, + createUpdateReferenceData(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/reference_datas", + ...opts, + }); + }, + async *paginate({ + fn, + params, + max, + }) { + params = { + ...params, + page: 1, + }; + let total, count = 0; + do { + const results = await fn({ + params, + }); + for (const item of results) { + yield item; + if (max && ++count >= max) { + return; + } + } + total = results?.length; + params.page++; + } while (total); }, }, }; diff --git a/components/gocanvas/package.json b/components/gocanvas/package.json new file mode 100644 index 0000000000000..d5437280de080 --- /dev/null +++ b/components/gocanvas/package.json @@ -0,0 +1,20 @@ +{ + "name": "@pipedream/gocanvas", + "version": "0.0.1", + "description": "Pipedream GoCanvas Components", + "main": "gocanvas.app.mjs", + "keywords": [ + "pipedream", + "gocanvas" + ], + "homepage": "https://pipedream.com/apps/gocanvas", + "author": "Pipedream (https://pipedream.com/)", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "csv-parse": "^5.5.6", + "xml2js": "^0.6.2" + } +} diff --git a/components/gocanvas/sources/new-submission-received/new-submission-received.mjs b/components/gocanvas/sources/new-submission-received/new-submission-received.mjs new file mode 100644 index 0000000000000..5b655fb8f18df --- /dev/null +++ b/components/gocanvas/sources/new-submission-received/new-submission-received.mjs @@ -0,0 +1,103 @@ +import gocanvas from "../../gocanvas.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + key: "gocanvas-new-submission-received", + name: "New Submission Recieved", + description: "Emit new event when a new submission is uploaded to GoCanvas.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + gocanvas, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + form: { + propDefinition: [ + gocanvas, + "form", + ], + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastSubmissionDate() { + return this.db.get("lastSubmissionDate"); + }, + _setLastSubmissionDate(lastSubmissionDate) { + this.db.set("lastSubmissionDate", lastSubmissionDate); + }, + generateMeta(submission) { + return { + id: submission.ResponseID, + summary: `New Submission: ${submission.ResponseID}`, + ts: Date.parse(submission.Date), + }; + }, + currentDate() { + const currentDate = new Date(); + return `${String(currentDate.getMonth() + 1) + .padStart(2, "0")}/${String(currentDate.getDate()) + .padStart(2, "0")}/${currentDate.getFullYear()}`; + }, + formatResponse(obj) { + if (Array.isArray(obj) && obj.length === 1) { + return this.formatResponse(obj[0]); + } else if (typeof obj === "object" && obj !== null) { + return Object.fromEntries( + Object.entries(obj).map(([ + key, + value, + ]) => [ + key, + this.formatResponse(value), + ]), + ); + } else { + return obj; + } + }, + async processEvent(max) { + let lastSubmissionDate = this._getLastSubmissionDate(); + + const params = { + form_name: this.form, + }; + if (lastSubmissionDate) { + params.begin_date = new Date(lastSubmissionDate).toLocaleDateString("en-US"); + params.end_date = this.currentDate(); + } + + const results = this.gocanvas.paginate({ + fn: this.gocanvas.listSubmissions, + params, + max, + }); + + for await (const result of results) { + const submission = this.formatResponse(result); + const meta = this.generateMeta(submission); + this.$emit(submission, meta); + + if (!lastSubmissionDate + || Date.parse(submission.Date) >= Date.parse(lastSubmissionDate)) { + lastSubmissionDate = submission.Date; + } + } + + this._setLastSubmissionDate(lastSubmissionDate); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48e44a6b26ac2..67a0ced94a27a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4080,6 +4080,16 @@ importers: components/gobio_link: specifiers: {} + components/gocanvas: + specifiers: + '@pipedream/platform': ^3.0.3 + csv-parse: ^5.5.6 + xml2js: ^0.6.2 + dependencies: + '@pipedream/platform': 3.0.3 + csv-parse: 5.5.6 + xml2js: 0.6.2 + components/godaddy: specifiers: {} @@ -24384,6 +24394,10 @@ packages: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} dev: false + /csv-parse/5.5.6: + resolution: {integrity: sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A==} + dev: false + /current-module-paths/1.1.1: resolution: {integrity: sha512-8Ga5T8oMXBaSsHq9Gj+bddX7kHSaJKsl2vaAd3ep51eQLkr4W18eFEmEZM5bLo1zrz8tt3jE1U8QK9QGhaLR4g==} engines: {node: '>=12.17'}