diff --git a/components/mural/actions/create-mural/create-mural.mjs b/components/mural/actions/create-mural/create-mural.mjs new file mode 100644 index 0000000000000..f61afb50a29b2 --- /dev/null +++ b/components/mural/actions/create-mural/create-mural.mjs @@ -0,0 +1,95 @@ +import mural from "../../mural.app.mjs"; + +export default { + key: "mural-create-mural", + name: "Create Mural", + description: "Create a new mural within a specified workspace. [See the documentation](https://developers.mural.co/public/reference/createmural)", + version: "0.0.1", + type: "action", + props: { + mural, + workspaceId: { + propDefinition: [ + mural, + "workspaceId", + ], + }, + roomId: { + propDefinition: [ + mural, + "roomId", + (c) => ({ + workspaceId: c.workspaceId, + }), + ], + }, + title: { + type: "string", + label: "Title", + description: "The title of the Mural.", + }, + backgroundColor: { + type: "string", + label: "Background Color", + description: "The background color of the mural. Example: `#FAFAFAFF`", + optional: true, + }, + height: { + type: "integer", + label: "Height", + description: "The height of the mural in px", + optional: true, + }, + width: { + type: "integer", + label: "Width", + description: "The width of the mural in px", + optional: true, + }, + infinite: { + type: "boolean", + label: "Infinite", + description: "When `true`, this indicates that the mural canvas is borderless and grows as you add widgets to it.", + optional: true, + }, + timerSoundTheme: { + type: "string", + label: "Timer Sound Theme", + description: "The timer sound theme for the mural", + options: [ + "airplane", + "cello", + "cuckoo", + ], + optional: true, + }, + visitorAvatarTheme: { + type: "string", + label: "Visitor Avatar Theme", + description: "The visitor avatar theme for the mural", + options: [ + "animals", + "music", + "travel", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.mural.createMural({ + $, + data: { + roomId: this.roomId, + title: this.title, + backgroundColor: this.backgroundColor, + height: this.height, + width: this.width, + infinite: this.infinite, + timerSoundTheme: this.timerSoundTheme, + visitorAvatarTheme: this.visitorAvatarTheme, + }, + }); + $.export("$summary", `Successfully created mural "${this.title}"`); + return response; + }, +}; diff --git a/components/mural/actions/create-sticky/create-sticky.mjs b/components/mural/actions/create-sticky/create-sticky.mjs new file mode 100644 index 0000000000000..ae273cb02d290 --- /dev/null +++ b/components/mural/actions/create-sticky/create-sticky.mjs @@ -0,0 +1,117 @@ +import mural from "../../mural.app.mjs"; + +export default { + key: "mural-create-sticky", + name: "Create Sticky", + description: "Create a new sticky note within a given mural. [See the documentation](https://developers.mural.co/public/reference/createstickynote)", + version: "0.0.1", + type: "action", + props: { + mural, + workspaceId: { + propDefinition: [ + mural, + "workspaceId", + ], + }, + muralId: { + propDefinition: [ + mural, + "muralId", + (c) => ({ + workspaceId: c.workspaceId, + }), + ], + }, + shape: { + type: "string", + label: "Shape", + description: "The shape of the sticky note widget", + options: [ + "circle", + "rectangle", + ], + }, + xPosition: { + type: "integer", + label: "X Position", + description: "The horizontal position of the widget in px. This is the distance from the left of the parent widget, such as an area. If the widget has no parent widget, this is the distance from the left of the mural.", + }, + yPosition: { + type: "integer", + label: "Y Position", + description: "The vertical position of the widget in px. This is the distance from the top of the parent widget, such as an area. If the widget has no parent widget, this is the distance from the top of the mural.", + }, + text: { + type: "string", + label: "Text", + description: "The text in the widget", + }, + title: { + type: "string", + label: "Title", + description: "The title of the widget in the outline", + optional: true, + }, + height: { + type: "integer", + label: "Height", + description: "The height of the widget in px", + optional: true, + }, + width: { + type: "integer", + label: "Width", + description: "The width of the widget in px", + optional: true, + }, + hidden: { + type: "boolean", + label: "Hidden", + description: "If `true`, the widget is hidden from non-facilitators. Applies only when the widget is in the outline", + optional: true, + }, + tagIds: { + propDefinition: [ + mural, + "tagIds", + (c) => ({ + muralId: c.muralId, + }), + ], + }, + parentId: { + propDefinition: [ + mural, + "widgetId", + (c) => ({ + muralId: c.muralId, + type: "areas", + }), + ], + label: "Parent ID", + description: "The ID of the area widget that contains the widget", + }, + }, + async run({ $ }) { + const response = await this.mural.createSticky({ + $, + muralId: this.muralId, + data: [ + { + shape: this.shape, + x: this.xPosition, + y: this.yPosition, + text: this.text, + title: this.title, + height: this.height, + width: this.width, + hidden: this.hidden, + parentId: this.parentId, + }, + ], + }); + $.export("$summary", `Successfully created sticky note with ID: ${response.value[0].id}`); + return response; + }, +}; diff --git a/components/mural/mural.app.mjs b/components/mural/mural.app.mjs index c4859e6998b0d..79bfc8c0a77e6 100644 --- a/components/mural/mural.app.mjs +++ b/components/mural/mural.app.mjs @@ -1,11 +1,247 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "mural", - propDefinitions: {}, + propDefinitions: { + workspaceId: { + type: "string", + label: "Workspace ID", + description: "The ID of the Workspace.", + async options({ prevContext }) { + const { + value, next: nextToken, + } = await this.listWorkspaces({ + params: { + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + muralId: { + type: "string", + label: "Mural ID", + description: "The ID of the Mural.", + async options({ + workspaceId, prevContext, + }) { + const { + value, next: nextToken, + } = await this.listMurals({ + workspaceId, + params: { + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, title: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + roomId: { + type: "string", + label: "Room ID", + description: "The ID of the Room.", + async options({ + workspaceId, prevContext, + }) { + const { + value, next: nextToken, + } = await this.listRooms({ + workspaceId, + params: { + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + tagIds: { + type: "string[]", + label: "Tag IDs", + description: "Unique identifiers of the tags in the widget", + optional: true, + async options({ + muralId, prevContext, + }) { + const { + value, next: nextToken, + } = await this.listTags({ + muralId, + params: { + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, text: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + widgetId: { + type: "string", + label: "Widget ID", + description: "Unique identifiers of the widget", + optional: true, + async options({ + muralId, type, prevContext, + }) { + const { + value, next: nextToken, + } = await this.listWidgets({ + muralId, + params: { + type, + next: prevContext?.next, + }, + }); + return { + options: value?.map(({ + id: value, title: label, + }) => ({ + value, + label, + })) || [], + context: { + next: nextToken, + }, + }; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://app.mural.co/api/public/v1"; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl()}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + listWorkspaces(opts = {}) { + return this._makeRequest({ + path: "/workspaces", + ...opts, + }); + }, + listMurals({ + workspaceId, ...opts + }) { + return this._makeRequest({ + path: `/workspaces/${workspaceId}/murals`, + ...opts, + }); + }, + listRooms({ + workspaceId, ...opts + }) { + return this._makeRequest({ + path: `/workspaces/${workspaceId}/rooms`, + ...opts, + }); + }, + listTags({ + muralId, ...opts + }) { + return this._makeRequest({ + path: `/murals/${muralId}/tags`, + ...opts, + }); + }, + listWidgets({ + muralId, ...opts + }) { + return this._makeRequest({ + path: `/murals/${muralId}/widgets`, + ...opts, + }); + }, + createMural(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/murals", + ...opts, + }); + }, + createSticky({ + muralId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/murals/${muralId}/widgets/sticky-note`, + ...opts, + }); + }, + async *paginate({ + fn, + args, + max, + }) { + args = { + ...args, + params: { + ...args?.params, + limit: 100, + }, + }; + let count = 0; + do { + const { + value, next, + } = await fn(args); + for (const item of value) { + yield item; + if (max && ++count >= max) { + return; + } + } + args.params.next = next; + } while (args.params.next); }, }, -}; \ No newline at end of file +}; diff --git a/components/mural/package.json b/components/mural/package.json index 6da8a3e0b5df3..227fca215ed5d 100644 --- a/components/mural/package.json +++ b/components/mural/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/mural", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Mural Components", "main": "mural.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/mural/sources/common/base.mjs b/components/mural/sources/common/base.mjs new file mode 100644 index 0000000000000..7fada52e4d214 --- /dev/null +++ b/components/mural/sources/common/base.mjs @@ -0,0 +1,85 @@ +import mural from "../../mural.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + mural, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + workspaceId: { + propDefinition: [ + mural, + "workspaceId", + ], + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs") || 0; + }, + _setLastTs(lastTs) { + this.db.set("lastTs", lastTs); + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + getArgs() { + return {}; + }, + getTsField() { + return "createdOn"; + }, + generateMeta(item) { + return { + id: item.id, + summary: this.getSummary(item), + ts: item[this.getTsField()], + }; + }, + getSummary() { + throw new Error("getSummary is not implemented"); + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + let maxTs = lastTs; + const fn = this.getResourceFn(); + const args = this.getArgs(); + const tsField = this.getTsField(); + + const results = this.mural.paginate({ + fn, + args, + max, + }); + + const items = []; + for await (const item of results) { + const ts = item[tsField]; + if (ts > lastTs) { + items.push(item); + maxTs = Math.max(ts, maxTs); + } + } + + this._setLastTs(maxTs); + + items.forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/mural/sources/new-area/new-area.mjs b/components/mural/sources/new-area/new-area.mjs new file mode 100644 index 0000000000000..d97417079f6f4 --- /dev/null +++ b/components/mural/sources/new-area/new-area.mjs @@ -0,0 +1,40 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "mural-new-area", + name: "New Area Created", + description: "Emit new event when a new area is created in the user's mural", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + muralId: { + propDefinition: [ + common.props.mural, + "muralId", + (c) => ({ + workspaceId: c.workspaceId, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.mural.listWidgets; + }, + getArgs() { + return { + muralId: this.muralId, + params: { + type: "areas", + }, + }; + }, + getSummary(item) { + return `New Area Widget ID: ${item.id}`; + }, + }, +}; diff --git a/components/mural/sources/new-mural/new-mural.mjs b/components/mural/sources/new-mural/new-mural.mjs new file mode 100644 index 0000000000000..49abc7fd03fda --- /dev/null +++ b/components/mural/sources/new-mural/new-mural.mjs @@ -0,0 +1,28 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "mural-new-mural", + name: "New Mural Created", + description: "Emit new event when a new mural is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.mural.listMurals; + }, + getArgs() { + return { + workspaceId: this.workspaceId, + params: { + sortBy: "lastCreated", + }, + }; + }, + getSummary(item) { + return `New Mural ID: ${item.id}`; + }, + }, +}; diff --git a/components/mural/sources/new-sticky/new-sticky.mjs b/components/mural/sources/new-sticky/new-sticky.mjs new file mode 100644 index 0000000000000..6316a2dc51261 --- /dev/null +++ b/components/mural/sources/new-sticky/new-sticky.mjs @@ -0,0 +1,40 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "mural-new-sticky", + name: "New Sticky Note Created", + description: "Emit new event each time a new sticky note is created in a specified mural", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + muralId: { + propDefinition: [ + common.props.mural, + "muralId", + (c) => ({ + workspaceId: c.workspaceId, + }), + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.mural.listWidgets; + }, + getArgs() { + return { + muralId: this.muralId, + params: { + type: "sticky notes", + }, + }; + }, + getSummary(item) { + return `New Sticky Note ID: ${item.id}`; + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ca5248787320..3d89fb7ed1b2e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6501,7 +6501,10 @@ importers: specifiers: {} components/mural: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 components/murlist: specifiers: {}