diff --git a/components/channable/actions/list-stock-updates/list-stock-updates.mjs b/components/channable/actions/list-stock-updates/list-stock-updates.mjs new file mode 100644 index 0000000000000..46358dabac838 --- /dev/null +++ b/components/channable/actions/list-stock-updates/list-stock-updates.mjs @@ -0,0 +1,56 @@ +import channable from "../../channable.app.mjs"; + +export default { + key: "channable-list-stock-updates", + name: "List Stock Updates", + description: "List stock updates for a company and project. [See the documentation](https://api.channable.com/v1/docs#tag/stock_updates/operation/get_stock_updates_companies__company_id__projects__project_id__offers_get)", + version: "0.0.1", + type: "action", + props: { + channable, + search: { + type: "string", + label: "Search", + description: "A text based search query", + optional: true, + }, + startDate: { + type: "string", + label: "Start Date", + description: "The start date of the stock updates", + optional: true, + }, + endDate: { + type: "string", + label: "End Date", + description: "The end date of the stock updates", + optional: true, + }, + max: { + type: "integer", + label: "Max", + description: "The maximum number of stock updates to return", + default: 100, + optional: true, + }, + }, + async run({ $ }) { + const stockUpdates = await this.channable.getPaginatedResources({ + fn: this.channable.listStockUpdates, + args: { + $, + params: { + search: this.search, + start_date: this.startDate, + end_date: this.endDate, + }, + max: this.max, + }, + resourceKey: "offers", + }); + $.export("$summary", `Found ${stockUpdates.length} stock update${stockUpdates.length === 1 + ? "" + : "s"}`); + return stockUpdates; + }, +}; diff --git a/components/channable/actions/update-stock-update/update-stock-update.mjs b/components/channable/actions/update-stock-update/update-stock-update.mjs new file mode 100644 index 0000000000000..936bb9ea4059e --- /dev/null +++ b/components/channable/actions/update-stock-update/update-stock-update.mjs @@ -0,0 +1,48 @@ +import channable from "../../channable.app.mjs"; + +export default { + key: "channable-update-stock-update", + name: "Update Stock Update", + description: "Update a stock update for a company and project. [See the documentation](https://api.channable.com/v1/docs#tag/stock_updates/operation/stock_updates_update_companies__company_id__projects__project_id__stock_updates_post)", + version: "0.0.1", + type: "action", + props: { + channable, + stockUpdateId: { + propDefinition: [ + channable, + "stockUpdateId", + ], + }, + stock: { + type: "integer", + label: "Stock", + description: "Whole new stock value for the item, not a delta", + }, + title: { + type: "string", + label: "Title", + description: "The title of the stock update", + }, + gtin: { + type: "string", + label: "GTIN", + description: "The GTIN of the item", + }, + }, + async run({ $ }) { + const response = await this.channable.updateStockUpdate({ + $, + data: [ + { + id: this.stockUpdateId, + stock: this.stock, + title: this.title, + gtin: this.gtin, + }, + ], + }); + $.export("$summary", `Updated stock update ${this.stockUpdateId}`); + return response; + }, +}; diff --git a/components/channable/channable.app.mjs b/components/channable/channable.app.mjs index e120bc11e9cff..50af440abefbc 100644 --- a/components/channable/channable.app.mjs +++ b/components/channable/channable.app.mjs @@ -1,11 +1,95 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "channable", - propDefinitions: {}, + propDefinitions: { + stockUpdateId: { + type: "string", + label: "Stock Update ID", + description: "The ID of a stock update", + async options({ page }) { + const { offers } = await this.listStockUpdates({ + params: { + limit: 100, + offset: page * 100, + }, + }); + return offers?.map((offer) => ({ + label: offer.label, + value: offer.id, + })) || []; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.channable.com/v1"; + }, + _companyId() { + return this.$auth.company_id; + }, + _projectId() { + return this.$auth.project_id; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.api_token}`, + }, + ...opts, + }); + }, + listStockUpdates(opts = {}) { + return this._makeRequest({ + path: `/companies/${this._companyId()}/projects/${this._projectId()}/offers`, + ...opts, + }); + }, + updateStockUpdate(opts = {}) { + return this._makeRequest({ + path: `/companies/${this._companyId()}/projects/${this._projectId()}/stock_updates`, + method: "POST", + ...opts, + }); + }, + async *paginate({ + fn, args, resourceKey, max, + }) { + args = { + ...args, + params: { + ...args?.params, + limit: 100, + offset: 0, + }, + }; + let total, count = 0; + do { + const response = await fn(args); + const items = response[resourceKey]; + total = items?.length; + if (!total) { + return; + } + for (const item of items) { + yield item; + if (max && ++count >= max) { + return; + } + } + args.params.offset += args.params.limit; + } while (total === args.params.limit); + }, + async getPaginatedResources(opts) { + const resources = []; + for await (const resource of this.paginate(opts)) { + resources.push(resource); + } + return resources; }, }, }; diff --git a/components/channable/package.json b/components/channable/package.json index 13de9e940d8a3..c51037d1ff4ed 100644 --- a/components/channable/package.json +++ b/components/channable/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/channable", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Channable Components", "main": "channable.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0" } -} \ No newline at end of file +} diff --git a/components/channable/sources/common/base.mjs b/components/channable/sources/common/base.mjs new file mode 100644 index 0000000000000..974e0f1273fee --- /dev/null +++ b/components/channable/sources/common/base.mjs @@ -0,0 +1,84 @@ +import channable from "../../channable.app.mjs"; +import { + DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, ConfigurationError, +} from "@pipedream/platform"; + +export default { + props: { + channable, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs"); + }, + _setLastTs(ts) { + this.db.set("lastTs", ts); + }, + getResourceKey() { + return "offers"; + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + let maxTs = lastTs; + const tsField = this.getTsField(); + + const results = await this.channable.paginate({ + fn: this.getResourceFn(), + args: { + params: { + last_modified_after: lastTs, + }, + }, + resourceKey: this.getResourceKey(), + }); + + let items = []; + for await (const result of results) { + const ts = result[tsField]; + if (!maxTs || Date.parse(ts) > Date.parse(maxTs)) { + maxTs = ts; + } + items.push(result); + } + + if (!items.length) { + return; + } + + this._setLastTs(maxTs); + + if (max && items.length > max) { + items = items.slice(0, max); + } + + items.forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + getResourceFn() { + throw new ConfigurationError("getResourceFn must be implemented"); + }, + getTsField() { + throw new ConfigurationError("getTsField must be implemented"); + }, + generateMeta() { + throw new ConfigurationError("generateMeta must be implemented"); + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/channable/sources/new-stock-update-created/new-stock-update-created.mjs b/components/channable/sources/new-stock-update-created/new-stock-update-created.mjs new file mode 100644 index 0000000000000..2d8393d5526bc --- /dev/null +++ b/components/channable/sources/new-stock-update-created/new-stock-update-created.mjs @@ -0,0 +1,27 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "channable-new-stock-update-created", + name: "New Stock Update Created", + description: "Emit new event when a new stock update is created. [See the documentation](https://api.channable.com/v1/docs#tag/stock_updates/operation/get_stock_updates_companies__company_id__projects__project_id__offers_get)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.channable.listStockUpdates; + }, + getTsField() { + return "created"; + }, + generateMeta(item) { + return { + id: item.id, + summary: `New stock update created: ${item.id}`, + ts: Date.parse(item.created), + }; + }, + }, +}; diff --git a/components/channable/sources/stock-update-updated/stock-update-updated.mjs b/components/channable/sources/stock-update-updated/stock-update-updated.mjs new file mode 100644 index 0000000000000..9ec16dbcb4119 --- /dev/null +++ b/components/channable/sources/stock-update-updated/stock-update-updated.mjs @@ -0,0 +1,28 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "channable-stock-update-updated", + name: "Stock Update Updated", + description: "Emit new event when a stock update is updated. [See the documentation](https://api.channable.com/v1/docs#tag/stock_updates/operation/get_stock_updates_companies__company_id__projects__project_id__offers_get)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.channable.listStockUpdates; + }, + getTsField() { + return "modified"; + }, + generateMeta(item) { + const ts = Date.parse(item.modified); + return { + id: `${item.id}-${ts}`, + summary: `Stock update updated: ${item.id}`, + ts, + }; + }, + }, +}; diff --git a/components/memento_database/memento_database.app.mjs b/components/memento_database/memento_database.app.mjs index 663681c2cafd4..ca83e3d61f868 100644 --- a/components/memento_database/memento_database.app.mjs +++ b/components/memento_database/memento_database.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; \ No newline at end of file +}; diff --git a/components/robopost/robopost.app.mjs b/components/robopost/robopost.app.mjs index bea2c6c7af963..5281f98336489 100644 --- a/components/robopost/robopost.app.mjs +++ b/components/robopost/robopost.app.mjs @@ -8,4 +8,4 @@ export default { console.log(Object.keys(this.$auth)); }, }, -}; \ No newline at end of file +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae6984522be10..a51461c5c41ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2273,7 +2273,11 @@ importers: specifier: ^3.1.0 version: 3.1.0 - components/channable: {} + components/channable: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/channeladvisor: dependencies: @@ -8248,8 +8252,7 @@ importers: components/membervault: {} - components/memento_database: - specifiers: {} + components/memento_database: {} components/memix: {} @@ -11569,8 +11572,7 @@ importers: specifier: ^3.1.0 version: 3.1.0 - components/robopost: - specifiers: {} + components/robopost: {} components/rocket_chat: dependencies: