From 563ef5b609a8244ca8138850d0db6995e1ea1002 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 9 May 2025 17:25:33 -0300 Subject: [PATCH 1/4] guru init --- .../add-tag-to-card/add-tag-to-card.mjs | 34 +++++ .../guru/actions/create-card/create-card.mjs | 50 +++++++ .../export-folder-to-pdf.mjs | 29 ++++ components/guru/guru.app.mjs | 129 ++++++++++++++++++ .../announcement-read-instant.mjs | 64 +++++++++ .../card-updated-instant.mjs | 76 +++++++++++ .../new-card-instant/new-card-instant.mjs | 90 ++++++++++++ 7 files changed, 472 insertions(+) create mode 100644 components/guru/actions/add-tag-to-card/add-tag-to-card.mjs create mode 100644 components/guru/actions/create-card/create-card.mjs create mode 100644 components/guru/actions/export-folder-to-pdf/export-folder-to-pdf.mjs create mode 100644 components/guru/guru.app.mjs create mode 100644 components/guru/sources/announcement-read-instant/announcement-read-instant.mjs create mode 100644 components/guru/sources/card-updated-instant/card-updated-instant.mjs create mode 100644 components/guru/sources/new-card-instant/new-card-instant.mjs diff --git a/components/guru/actions/add-tag-to-card/add-tag-to-card.mjs b/components/guru/actions/add-tag-to-card/add-tag-to-card.mjs new file mode 100644 index 0000000000000..8857ea8ff1395 --- /dev/null +++ b/components/guru/actions/add-tag-to-card/add-tag-to-card.mjs @@ -0,0 +1,34 @@ +import guru from "../../guru.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "guru-add-tag-to-card", + name: "Add Tag to Card", + description: "Links an existing tag to a specified card in Guru. [See the documentation](https://developer.getguru.com/reference)", + version: "0.0.{{ts}}", + type: "action", + props: { + guru, + cardId: { + propDefinition: [ + guru, + "cardId", + ], + }, + tagId: { + propDefinition: [ + guru, + "tagId", + ], + }, + }, + async run({ $ }) { + const response = await this.guru.linkTagToCard({ + cardId: this.cardId, + tagId: this.tagId, + }); + + $.export("$summary", `Successfully linked tag ${this.tagId} to card ${this.cardId}`); + return response; + }, +}; diff --git a/components/guru/actions/create-card/create-card.mjs b/components/guru/actions/create-card/create-card.mjs new file mode 100644 index 0000000000000..0069763b1e035 --- /dev/null +++ b/components/guru/actions/create-card/create-card.mjs @@ -0,0 +1,50 @@ +import guru from "../../guru.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "guru-create-card", + name: "Create Card", + description: "Creates a new card on your Guru account. [See the documentation](https://developer.getguru.com/reference/postv1cardscreateextendedfact)", + version: "0.0.1", + type: "action", + props: { + guru, + cardTitle: { + propDefinition: [ + guru, + "cardTitle", + ], + }, + content: { + propDefinition: [ + guru, + "content", + ], + }, + groupId: { + propDefinition: [ + guru, + "groupId", + ], + optional: true, + }, + userId: { + propDefinition: [ + guru, + "userId", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.guru.createCard({ + cardTitle: this.cardTitle, + content: this.content, + groupId: this.groupId, + userId: this.userId, + }); + + $.export("$summary", `Created card "${this.cardTitle}" successfully`); + return response; + }, +}; diff --git a/components/guru/actions/export-folder-to-pdf/export-folder-to-pdf.mjs b/components/guru/actions/export-folder-to-pdf/export-folder-to-pdf.mjs new file mode 100644 index 0000000000000..ee76f21321cc6 --- /dev/null +++ b/components/guru/actions/export-folder-to-pdf/export-folder-to-pdf.mjs @@ -0,0 +1,29 @@ +import guru from "../../guru.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "guru-export-folder-to-pdf", + name: "Export Folder to PDF", + description: "Export a specific folder identified by its ID to a PDF file. [See the documentation](https://developer.getguru.com/reference/getv1foldersgetfolderaspdf)", + version: "0.0.{{ts}}", + type: "action", + props: { + guru, + folderId: { + propDefinition: [ + guru, + "folderId", + ], + }, + }, + async run({ $ }) { + const filePath = await this.guru.exportFolderToPdf({ + folderId: this.folderId, + }); + + $.export("$summary", `Successfully exported folder ID ${this.folderId} to PDF.`); + return { + filePath, + }; + }, +}; diff --git a/components/guru/guru.app.mjs b/components/guru/guru.app.mjs new file mode 100644 index 0000000000000..33c214a6e46b3 --- /dev/null +++ b/components/guru/guru.app.mjs @@ -0,0 +1,129 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "guru", + propDefinitions: { + cardTitle: { + type: "string", + label: "Card Title", + description: "The title of the card to create", + }, + content: { + type: "string", + label: "Content", + description: "The content of the card to create", + }, + groupId: { + type: "string", + label: "Group ID", + description: "The group ID for ownership of the card", + optional: true, + }, + userId: { + type: "string", + label: "User ID", + description: "The user ID for ownership of the card", + optional: true, + }, + cardId: { + type: "string", + label: "Card ID", + description: "The ID of the card", + }, + tagId: { + type: "string", + label: "Tag ID", + description: "The ID of the tag", + }, + folderId: { + type: "string", + label: "Folder ID", + description: "The ID of the folder to export to PDF", + }, + }, + methods: { + _baseUrl() { + return "https://api.getguru.com/api/v1"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, method = "GET", path, headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + async createCard({ + cardTitle, content, groupId, userId, ...opts + }) { + const data = { + content, + title: cardTitle, + }; + if (groupId) data.groupId = groupId; + if (userId) data.userId = userId; + + return this._makeRequest({ + method: "POST", + path: "/cards/extended", + data, + ...opts, + }); + }, + async linkTagToCard({ + cardId, tagId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/cards/${cardId}/tags/${tagId}`, + ...opts, + }); + }, + async exportFolderToPdf({ + folderId, ...opts + }) { + const response = await this._makeRequest({ + path: `/folders/${folderId}/pdf`, + responseType: "arraybuffer", + ...opts, + }); + const filePath = `/tmp/folder-${folderId}.pdf`; + require("fs").writeFileSync(filePath, response); + return filePath; + }, + async emitAlertReadEvent(alertId, ...opts) { + const eventType = "alert-read"; + // Emit the alert-read event logic here + return this._makeRequest({ + path: `/alerts/${alertId}/read`, + method: "POST", + ...opts, + }); + }, + async emitCardCreatedEvent(cardId, ...opts) { + const eventType = "card-created"; + // Emit the card-created event logic here + return this._makeRequest({ + path: `/cards/${cardId}`, + method: "GET", + ...opts, + }); + }, + async emitCardUpdatedEvent(cardId, ...opts) { + const eventType = "card-updated"; + // Emit the card-updated event logic here + return this._makeRequest({ + path: `/cards/${cardId}`, + method: "PATCH", + ...opts, + }); + }, + }, +}; diff --git a/components/guru/sources/announcement-read-instant/announcement-read-instant.mjs b/components/guru/sources/announcement-read-instant/announcement-read-instant.mjs new file mode 100644 index 0000000000000..1d9c36457f408 --- /dev/null +++ b/components/guru/sources/announcement-read-instant/announcement-read-instant.mjs @@ -0,0 +1,64 @@ +import { axios } from "@pipedream/platform"; +import guru from "../../guru.app.mjs"; + +export default { + key: "guru-announcement-read-instant", + name: "New Announcement Read", + description: "Emit an event each time an announcement is read. [See the documentation](https://developer.getguru.com/reference/mark-an-announcement-alert-as-readput)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + guru, + db: "$.service.db", + alertId: { + type: "string", + label: "Alert ID", + description: "The ID of the announcement alert to monitor for read events", + }, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + }, + hooks: { + async deploy() { + await this.checkForAlertRead(); + }, + async activate() { + // Setup logic for when the source is activated + }, + async deactivate() { + // Cleanup logic for when the source is deactivated + }, + }, + methods: { + async checkForAlertRead() { + try { + const readEvents = await this.guru.emitAlertReadEvent(this.alertId); + + const seenReadEvents = this.db.get("seenReadEvents") || []; + + for (const readEvent of readEvents) { + if (!seenReadEvents.includes(readEvent.alertId)) { + this.$emit(readEvent, { + id: null, + summary: `Announcement Read: ${readEvent.alertId}`, + ts: new Date().getTime(), + }); + seenReadEvents.push(readEvent.alertId); + } + } + + this.db.set("seenReadEvents", seenReadEvents); + } catch (error) { + console.error(`Error fetching alert read events: ${error}`); + } + }, + }, + async run() { + await this.checkForAlertRead(); + }, +}; diff --git a/components/guru/sources/card-updated-instant/card-updated-instant.mjs b/components/guru/sources/card-updated-instant/card-updated-instant.mjs new file mode 100644 index 0000000000000..9a40da15a266a --- /dev/null +++ b/components/guru/sources/card-updated-instant/card-updated-instant.mjs @@ -0,0 +1,76 @@ +import guru from "../../guru.app.mjs"; +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; + +export default { + key: "guru-card-updated-instant", + name: "New Card Update Event", + description: "Emit an event when a user makes an edit to a card. [See the documentation](https://developer.getguru.com/reference/authentication)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + guru, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + cardId: { + propDefinition: [ + guru, + "cardId", + ], + }, + }, + hooks: { + async deploy() { + const cardDetails = await this.guru.emitCardUpdatedEvent({ + cardId: this.cardId, + }); + + this.$emit(cardDetails, { + id: cardDetails.id, + summary: `Last Card Update: ${cardDetails.title}`, + ts: new Date(cardDetails.lastModified).getTime(), + }); + }, + async activate() { + const response = await this.guru._makeRequest({ + path: "/webhooks", + method: "POST", + data: { + deliveryMode: "BATCH", + targetUrl: "https://yourserver.com/webhook-handler", + status: "ENABLED", + filter: "card-updated", + }, + }); + this.webhookId = response.id; + }, + async deactivate() { + if (!this.webhookId) { + console.log("No webhook ID found, skipping deactivation"); + return; + } + await this.guru._makeRequest({ + path: `/webhooks/${this.webhookId}`, + method: "DELETE", + }); + }, + }, + async run() { + const cardDetails = await this.guru.emitCardUpdatedEvent({ + cardId: this.cardId, + }); + + this.$emit(cardDetails, { + id: cardDetails.id, + summary: `Card Updated: ${cardDetails.title}`, + ts: new Date(cardDetails.lastModified).getTime(), + }); + }, +}; diff --git a/components/guru/sources/new-card-instant/new-card-instant.mjs b/components/guru/sources/new-card-instant/new-card-instant.mjs new file mode 100644 index 0000000000000..ffedb84356bd5 --- /dev/null +++ b/components/guru/sources/new-card-instant/new-card-instant.mjs @@ -0,0 +1,90 @@ +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import guru from "../../guru.app.mjs"; + +export default { + key: "guru-new-card-instant", + name: "New Card Created", + description: "Emit new event when a new card is published. [See the documentation](https://developer.getguru.com/docs/creating-a-webhook)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + guru, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + hooks: { + async deploy() { + const cards = await this.fetchRecentCards(); + for (const card of cards.slice(0, 50)) { + this.$emit(card, { + id: card.id, + summary: `New Card: ${card.title}`, + ts: Date.parse(card.createdAt), + }); + } + }, + async activate() { + await this.createWebhook(); + }, + async deactivate() { + await this.deleteWebhook(); + }, + }, + methods: { + async createWebhook() { + const response = await axios(this, { + method: "POST", + url: `${this.guru._baseUrl()}/webhooks`, + headers: { + Authorization: `Bearer ${this.guru.$auth.oauth_access_token}`, + }, + data: { + deliveryMode: "BATCH", + targetUrl: "https://endpoint.m.pipedream.net", // Replace with your unique Pipedream Webhook URL + status: "ENABLED", + filter: "card-created", + }, + }); + this.db.set("webhookId", response.id); + }, + async deleteWebhook() { + const webhookId = this.db.get("webhookId"); + if (!webhookId) return; + + await axios(this, { + method: "DELETE", + url: `${this.guru._baseUrl()}/webhooks/${webhookId}`, + headers: { + Authorization: `Bearer ${this.guru.$auth.oauth_access_token}`, + }, + }); + this.db.set("webhookId", null); + }, + async fetchRecentCards() { + const allCards = await this.guru.emitCardCreatedEvent(); + return allCards.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + }, + }, + async run(event) { + const payload = event.body; + if (!payload) { + console.log("No payload found, skipping"); + return; + } + for (const card of payload) { + this.$emit(card, { + id: card.id, + summary: `New Card: ${card.title}`, + ts: Date.parse(card.dateCreated), + }); + } + }, +}; From 7487e78a9ef881096d58ebb85429f69baf763aeb Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 12 May 2025 13:06:57 -0300 Subject: [PATCH 2/4] [Components] guru #13217 Sources - Announcement Read (Instant) - New Card (Instant) - Card Updated (Instant) Actions - Create Card - Add Tag To Card - Export Card To PDF --- components/guru/.gitignore | 3 - .../add-tag-to-card/add-tag-to-card.mjs | 17 +- .../guru/actions/create-card/create-card.mjs | 54 +++-- .../export-card-to-pdf/export-card-to-pdf.mjs | 41 ++++ .../export-folder-to-pdf.mjs | 29 --- components/guru/app/guru.app.ts | 13 -- components/guru/common/constants.mjs | 4 + components/guru/common/utils.mjs | 24 +++ components/guru/guru.app.mjs | 197 ++++++++++++------ components/guru/package.json | 10 +- .../announcement-read-instant.mjs | 68 ++---- .../announcement-read-instant/test-event.mjs | 11 + .../card-updated-instant.mjs | 80 ++----- .../card-updated-instant/test-event.mjs | 11 + components/guru/sources/common/base.mjs | 47 +++++ .../new-card-instant/new-card-instant.mjs | 90 +------- .../sources/new-card-instant/test-event.mjs | 11 + 17 files changed, 367 insertions(+), 343 deletions(-) delete mode 100644 components/guru/.gitignore create mode 100644 components/guru/actions/export-card-to-pdf/export-card-to-pdf.mjs delete mode 100644 components/guru/actions/export-folder-to-pdf/export-folder-to-pdf.mjs delete mode 100644 components/guru/app/guru.app.ts create mode 100644 components/guru/common/constants.mjs create mode 100644 components/guru/common/utils.mjs create mode 100644 components/guru/sources/announcement-read-instant/test-event.mjs create mode 100644 components/guru/sources/card-updated-instant/test-event.mjs create mode 100644 components/guru/sources/common/base.mjs create mode 100644 components/guru/sources/new-card-instant/test-event.mjs diff --git a/components/guru/.gitignore b/components/guru/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/guru/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/guru/actions/add-tag-to-card/add-tag-to-card.mjs b/components/guru/actions/add-tag-to-card/add-tag-to-card.mjs index 8857ea8ff1395..42afb2333a610 100644 --- a/components/guru/actions/add-tag-to-card/add-tag-to-card.mjs +++ b/components/guru/actions/add-tag-to-card/add-tag-to-card.mjs @@ -1,11 +1,10 @@ import guru from "../../guru.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "guru-add-tag-to-card", name: "Add Tag to Card", - description: "Links an existing tag to a specified card in Guru. [See the documentation](https://developer.getguru.com/reference)", - version: "0.0.{{ts}}", + description: "Links an existing tag to a specified card in Guru. [See the documentation](https://developer.getguru.com/reference/getv1cardsgetextendedfact)", + version: "0.0.1", type: "action", props: { guru, @@ -15,20 +14,24 @@ export default { "cardId", ], }, - tagId: { + tags: { propDefinition: [ guru, - "tagId", + "tags", ], + type: "string", + label: "Tag", + description: "The ID of the tag to add to the card", }, }, async run({ $ }) { const response = await this.guru.linkTagToCard({ + $, cardId: this.cardId, - tagId: this.tagId, + tagId: this.tags, }); - $.export("$summary", `Successfully linked tag ${this.tagId} to card ${this.cardId}`); + $.export("$summary", `Successfully linked tag ${this.tags} to card ${this.cardId}`); return response; }, }; diff --git a/components/guru/actions/create-card/create-card.mjs b/components/guru/actions/create-card/create-card.mjs index 0069763b1e035..e59dfc47268bc 100644 --- a/components/guru/actions/create-card/create-card.mjs +++ b/components/guru/actions/create-card/create-card.mjs @@ -1,5 +1,6 @@ +import { SHARE_STATUS_OPTIONS } from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; import guru from "../../guru.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "guru-create-card", @@ -9,42 +10,61 @@ export default { type: "action", props: { guru, - cardTitle: { - propDefinition: [ - guru, - "cardTitle", - ], + title: { + type: "string", + label: "Card Title", + description: "The title of the card to create", }, content: { + type: "string", + label: "Content", + description: "The content of the card to create", + }, + shareStatus: { + type: "string", + label: "Share Status", + description: "The share status of the card.", + options: SHARE_STATUS_OPTIONS, + optional: true, + }, + collection: { propDefinition: [ guru, - "content", + "collection", ], }, - groupId: { + folderIds: { propDefinition: [ guru, - "groupId", + "folderIds", ], - optional: true, }, - userId: { + tags: { propDefinition: [ guru, - "userId", + "tags", ], optional: true, }, }, async run({ $ }) { const response = await this.guru.createCard({ - cardTitle: this.cardTitle, - content: this.content, - groupId: this.groupId, - userId: this.userId, + $, + data: { + preferredPhrase: this.title, + content: this.content, + shareStatus: this.shareStatus, + collection: { + id: this.collection, + }, + folderIds: parseObject(this.folderIds), + tags: parseObject(this.tags)?.map((item) => ({ + id: item, + })), + }, }); - $.export("$summary", `Created card "${this.cardTitle}" successfully`); + $.export("$summary", `Created card "${this.title}" successfully`); return response; }, }; diff --git a/components/guru/actions/export-card-to-pdf/export-card-to-pdf.mjs b/components/guru/actions/export-card-to-pdf/export-card-to-pdf.mjs new file mode 100644 index 0000000000000..ff8337b0abbe9 --- /dev/null +++ b/components/guru/actions/export-card-to-pdf/export-card-to-pdf.mjs @@ -0,0 +1,41 @@ +import fs from "fs"; +import stream from "stream"; +import { promisify } from "util"; +import guru from "../../guru.app.mjs"; + +export default { + key: "guru-export-card-to-pdf", + name: "Export Card to PDF", + description: "Export a specific card identified by its ID to a PDF file. [See the documentation](https://developer.getguru.com/docs/download-cards-to-pdf)", + version: "0.0.1", + type: "action", + props: { + guru, + cardId: { + propDefinition: [ + guru, + "cardId", + ], + }, + }, + async run({ $ }) { + const { + headers, data, + } = await this.guru.exportCardToPdf({ + $, + cardId: this.cardId, + returnFullResponse: true, + }); + + const fileName = headers["content-disposition"]?.split("filename=")[1]?.split("/")[1].slice(0, -1); + const filePath = `/tmp/${fileName}`; + + const pipeline = promisify(stream.pipeline); + await pipeline(data, fs.createWriteStream(filePath)); + + $.export("$summary", `Successfully exported card ID ${this.cardId} to PDF.`); + return { + filePath, + }; + }, +}; diff --git a/components/guru/actions/export-folder-to-pdf/export-folder-to-pdf.mjs b/components/guru/actions/export-folder-to-pdf/export-folder-to-pdf.mjs deleted file mode 100644 index ee76f21321cc6..0000000000000 --- a/components/guru/actions/export-folder-to-pdf/export-folder-to-pdf.mjs +++ /dev/null @@ -1,29 +0,0 @@ -import guru from "../../guru.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "guru-export-folder-to-pdf", - name: "Export Folder to PDF", - description: "Export a specific folder identified by its ID to a PDF file. [See the documentation](https://developer.getguru.com/reference/getv1foldersgetfolderaspdf)", - version: "0.0.{{ts}}", - type: "action", - props: { - guru, - folderId: { - propDefinition: [ - guru, - "folderId", - ], - }, - }, - async run({ $ }) { - const filePath = await this.guru.exportFolderToPdf({ - folderId: this.folderId, - }); - - $.export("$summary", `Successfully exported folder ID ${this.folderId} to PDF.`); - return { - filePath, - }; - }, -}; diff --git a/components/guru/app/guru.app.ts b/components/guru/app/guru.app.ts deleted file mode 100644 index 364108a439141..0000000000000 --- a/components/guru/app/guru.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "guru", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); diff --git a/components/guru/common/constants.mjs b/components/guru/common/constants.mjs new file mode 100644 index 0000000000000..0a065a9330795 --- /dev/null +++ b/components/guru/common/constants.mjs @@ -0,0 +1,4 @@ +export const SHARE_STATUS_OPTIONS = [ + "TEAM", + "PRIVATE", +]; diff --git a/components/guru/common/utils.mjs b/components/guru/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/guru/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/guru/guru.app.mjs b/components/guru/guru.app.mjs index 33c214a6e46b3..8dd6b6ab1377e 100644 --- a/components/guru/guru.app.mjs +++ b/components/guru/guru.app.mjs @@ -4,38 +4,88 @@ export default { type: "app", app: "guru", propDefinitions: { - cardTitle: { + collection: { type: "string", - label: "Card Title", - description: "The title of the card to create", - }, - content: { - type: "string", - label: "Content", - description: "The content of the card to create", + label: "Collection", + description: "The collection to create the card in", + async options() { + const data = await this.listCollections(); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, - groupId: { - type: "string", - label: "Group ID", - description: "The group ID for ownership of the card", - optional: true, + folderIds: { + type: "string[]", + label: "Folder Ids", + description: "The IDs of the folders to create the card in", + async options() { + const data = await this.listFolders(); + + return data.map(({ + id: value, title: label, + }) => ({ + label, + value, + })); + }, }, - userId: { - type: "string", - label: "User ID", - description: "The user ID for ownership of the card", - optional: true, + tags: { + type: "string[]", + label: "Tags", + description: "The IDs of the tags to add to the card", + async options() { + const { team: { id: teamId } } = await this.whoAmI(); + const data = await this.listTags({ + teamId, + }); + + return data[0]?.tags.map(({ + id: value, value: label, + }) => ({ + label, + value, + })); + }, }, cardId: { type: "string", label: "Card ID", description: "The ID of the card", + async options({ prevContext }) { + const { + data, headers, + } = await this.listCards({ + params: { + token: prevContext.token, + }, + }); + let token; + + if (headers.link) { + const link = headers.link.split(">")[0].slice(1); + const url = new URL(link); + const params = new URLSearchParams(url.search); + token = params.get("token"); + } + return { + options: data.map(({ + id: value, preferredPhrase: label, + }) => ({ + label, + value, + })), + context: { + token, + }, + }; + }, }, - tagId: { - type: "string", - label: "Tag ID", - description: "The ID of the tag", - }, + folderId: { type: "string", label: "Folder ID", @@ -46,84 +96,95 @@ export default { _baseUrl() { return "https://api.getguru.com/api/v1"; }, - async _makeRequest(opts = {}) { - const { - $ = this, method = "GET", path, headers, ...otherOpts - } = opts; + _auth() { + return { + username: `${this.$auth.username}`, + password: `${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - ...otherOpts, - method, url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.oauth_access_token}`, - }, + auth: this._auth(), + ...opts, }); }, - async createCard({ - cardTitle, content, groupId, userId, ...opts - }) { - const data = { - content, - title: cardTitle, - }; - if (groupId) data.groupId = groupId; - if (userId) data.userId = userId; - + whoAmI(opts = {}) { + return this._makeRequest({ + path: "/whoami", + ...opts, + }); + }, + createCard(opts = {}) { return this._makeRequest({ method: "POST", path: "/cards/extended", - data, ...opts, }); }, - async linkTagToCard({ + linkTagToCard({ cardId, tagId, ...opts }) { return this._makeRequest({ method: "PUT", path: `/cards/${cardId}/tags/${tagId}`, + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + }, ...opts, }); }, - async exportFolderToPdf({ - folderId, ...opts + exportCardToPdf({ + cardId, ...opts }) { - const response = await this._makeRequest({ - path: `/folders/${folderId}/pdf`, - responseType: "arraybuffer", + return this._makeRequest({ + path: `/cards/${cardId}/pdf`, + responseType: "stream", ...opts, }); - const filePath = `/tmp/folder-${folderId}.pdf`; - require("fs").writeFileSync(filePath, response); - return filePath; }, - async emitAlertReadEvent(alertId, ...opts) { - const eventType = "alert-read"; - // Emit the alert-read event logic here + listCollections(opts = {}) { return this._makeRequest({ - path: `/alerts/${alertId}/read`, - method: "POST", + path: "/collections", + ...opts, + }); + }, + listFolders(opts = {}) { + return this._makeRequest({ + path: "/folders", + ...opts, + }); + }, + listTags({ + teamId, ...opts + }) { + return this._makeRequest({ + path: `/teams/${teamId}/tagcategories`, ...opts, }); }, - async emitCardCreatedEvent(cardId, ...opts) { - const eventType = "card-created"; - // Emit the card-created event logic here + listCards(opts = {}) { return this._makeRequest({ - path: `/cards/${cardId}`, - method: "GET", + path: "/search/cardmgr", + returnFullResponse: true, ...opts, }); }, - async emitCardUpdatedEvent(cardId, ...opts) { - const eventType = "card-updated"; - // Emit the card-updated event logic here + createWebhook(opts = {}) { return this._makeRequest({ - path: `/cards/${cardId}`, - method: "PATCH", + method: "POST", + path: "/webhooks", ...opts, }); }, + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); + }, }, }; diff --git a/components/guru/package.json b/components/guru/package.json index c50c8c39acbbb..8fcf089f37ec8 100644 --- a/components/guru/package.json +++ b/components/guru/package.json @@ -1,18 +1,18 @@ { "name": "@pipedream/guru", - "version": "0.0.3", + "version": "0.1.0", "description": "Pipedream Guru Components", - "main": "dist/app/guru.app.mjs", + "main": "guru.app.mjs", "keywords": [ "pipedream", "guru" ], - "files": [ - "dist" - ], "homepage": "https://pipedream.com/apps/guru", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/guru/sources/announcement-read-instant/announcement-read-instant.mjs b/components/guru/sources/announcement-read-instant/announcement-read-instant.mjs index 1d9c36457f408..13929def4a12e 100644 --- a/components/guru/sources/announcement-read-instant/announcement-read-instant.mjs +++ b/components/guru/sources/announcement-read-instant/announcement-read-instant.mjs @@ -1,64 +1,22 @@ -import { axios } from "@pipedream/platform"; -import guru from "../../guru.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "guru-announcement-read-instant", - name: "New Announcement Read", - description: "Emit an event each time an announcement is read. [See the documentation](https://developer.getguru.com/reference/mark-an-announcement-alert-as-readput)", - version: "0.0.{{ts}}", + name: "New Announcement Read (Instant)", + description: "Emit new event when a user clicks on the \"I read it\" button in an Announcement. [See the documentation](https://developer.getguru.com/docs/creating-a-webhook)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - guru, - db: "$.service.db", - alertId: { - type: "string", - label: "Alert ID", - description: "The ID of the announcement alert to monitor for read events", - }, - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, - }, - }, - hooks: { - async deploy() { - await this.checkForAlertRead(); - }, - async activate() { - // Setup logic for when the source is activated - }, - async deactivate() { - // Cleanup logic for when the source is deactivated - }, - }, methods: { - async checkForAlertRead() { - try { - const readEvents = await this.guru.emitAlertReadEvent(this.alertId); - - const seenReadEvents = this.db.get("seenReadEvents") || []; - - for (const readEvent of readEvents) { - if (!seenReadEvents.includes(readEvent.alertId)) { - this.$emit(readEvent, { - id: null, - summary: `Announcement Read: ${readEvent.alertId}`, - ts: new Date().getTime(), - }); - seenReadEvents.push(readEvent.alertId); - } - } - - this.db.set("seenReadEvents", seenReadEvents); - } catch (error) { - console.error(`Error fetching alert read events: ${error}`); - } + ...common.methods, + getEventType() { + return "alert-read"; + }, + getSummary(body) { + return `Announcement Read: ${body.id}`; }, }, - async run() { - await this.checkForAlertRead(); - }, + sampleEmit, }; diff --git a/components/guru/sources/announcement-read-instant/test-event.mjs b/components/guru/sources/announcement-read-instant/test-event.mjs new file mode 100644 index 0000000000000..9566c050c32a7 --- /dev/null +++ b/components/guru/sources/announcement-read-instant/test-event.mjs @@ -0,0 +1,11 @@ +export default { + "id":"64753163-9817-4500-9651-96177c32e3d1", + "eventType":"alert-read", + "user":"bob@getguru.com", + "eventDate":"2021-04-13T13:53:00.000+0000", + "properties":{ + "cardId":"64753163-9817-4500-9651-96177c32e3d1", + "collectionId":"64753163-9817-4500-9651-96177c32e3d1", + "source": "UI" + } +} \ No newline at end of file diff --git a/components/guru/sources/card-updated-instant/card-updated-instant.mjs b/components/guru/sources/card-updated-instant/card-updated-instant.mjs index 9a40da15a266a..bc38c7143ab18 100644 --- a/components/guru/sources/card-updated-instant/card-updated-instant.mjs +++ b/components/guru/sources/card-updated-instant/card-updated-instant.mjs @@ -1,76 +1,22 @@ -import guru from "../../guru.app.mjs"; -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "guru-card-updated-instant", - name: "New Card Update Event", - description: "Emit an event when a user makes an edit to a card. [See the documentation](https://developer.getguru.com/reference/authentication)", - version: "0.0.{{ts}}", + name: "Card Updated (Instant)", + description: "Emit new event when a user makes an edit to a card. [See the documentation](https://developer.getguru.com/docs/creating-a-webhook)", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - guru, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, + methods: { + ...common.methods, + getEventType() { + return "card-updated"; }, - cardId: { - propDefinition: [ - guru, - "cardId", - ], + getSummary(body) { + return `Card Updated: ${body.properties.cardId}`; }, }, - hooks: { - async deploy() { - const cardDetails = await this.guru.emitCardUpdatedEvent({ - cardId: this.cardId, - }); - - this.$emit(cardDetails, { - id: cardDetails.id, - summary: `Last Card Update: ${cardDetails.title}`, - ts: new Date(cardDetails.lastModified).getTime(), - }); - }, - async activate() { - const response = await this.guru._makeRequest({ - path: "/webhooks", - method: "POST", - data: { - deliveryMode: "BATCH", - targetUrl: "https://yourserver.com/webhook-handler", - status: "ENABLED", - filter: "card-updated", - }, - }); - this.webhookId = response.id; - }, - async deactivate() { - if (!this.webhookId) { - console.log("No webhook ID found, skipping deactivation"); - return; - } - await this.guru._makeRequest({ - path: `/webhooks/${this.webhookId}`, - method: "DELETE", - }); - }, - }, - async run() { - const cardDetails = await this.guru.emitCardUpdatedEvent({ - cardId: this.cardId, - }); - - this.$emit(cardDetails, { - id: cardDetails.id, - summary: `Card Updated: ${cardDetails.title}`, - ts: new Date(cardDetails.lastModified).getTime(), - }); - }, + sampleEmit, }; diff --git a/components/guru/sources/card-updated-instant/test-event.mjs b/components/guru/sources/card-updated-instant/test-event.mjs new file mode 100644 index 0000000000000..fabb48c22966c --- /dev/null +++ b/components/guru/sources/card-updated-instant/test-event.mjs @@ -0,0 +1,11 @@ +export default { + "id":"64753163-9817-4500-9651-96177c32e3d1", + "eventType":"card-updated", + "user":"bob@getguru.com", + "eventDate":"2021-04-13T13:53:00.000+0000", + "properties":{ + "cardId":"64753163-9817-4500-9651-96177c32e3d1", + "collectionId":"64753163-9817-4500-9651-96177c32e3d1", + "source": "UI" + } +} \ No newline at end of file diff --git a/components/guru/sources/common/base.mjs b/components/guru/sources/common/base.mjs new file mode 100644 index 0000000000000..4911f98ce97ee --- /dev/null +++ b/components/guru/sources/common/base.mjs @@ -0,0 +1,47 @@ +import guru from "../../guru.app.mjs"; + +export default { + props: { + guru, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + methods: { + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getExtraData() { + return {}; + }, + }, + hooks: { + async activate() { + const response = await this.guru.createWebhook({ + data: { + targetUrl: this.http.endpoint, + deliveryMode: "SINGLE", + status: "ENABLED", + filter: this.getEventType(), + }, + }); + this._setHookId(response.id); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.guru.deleteWebhook(webhookId); + }, + }, + async run({ body }) { + this.$emit(body, { + id: body.id, + summary: this.getSummary(body), + ts: Date.parse(body.eventDate), + }); + }, +}; diff --git a/components/guru/sources/new-card-instant/new-card-instant.mjs b/components/guru/sources/new-card-instant/new-card-instant.mjs index ffedb84356bd5..58898cfffd35a 100644 --- a/components/guru/sources/new-card-instant/new-card-instant.mjs +++ b/components/guru/sources/new-card-instant/new-card-instant.mjs @@ -1,90 +1,22 @@ -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; -import guru from "../../guru.app.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; export default { + ...common, key: "guru-new-card-instant", - name: "New Card Created", + name: "New Card Created (Instant)", description: "Emit new event when a new card is published. [See the documentation](https://developer.getguru.com/docs/creating-a-webhook)", - version: "0.0.{{ts}}", + version: "0.0.1", type: "source", dedupe: "unique", - props: { - guru, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - }, - hooks: { - async deploy() { - const cards = await this.fetchRecentCards(); - for (const card of cards.slice(0, 50)) { - this.$emit(card, { - id: card.id, - summary: `New Card: ${card.title}`, - ts: Date.parse(card.createdAt), - }); - } - }, - async activate() { - await this.createWebhook(); - }, - async deactivate() { - await this.deleteWebhook(); - }, - }, methods: { - async createWebhook() { - const response = await axios(this, { - method: "POST", - url: `${this.guru._baseUrl()}/webhooks`, - headers: { - Authorization: `Bearer ${this.guru.$auth.oauth_access_token}`, - }, - data: { - deliveryMode: "BATCH", - targetUrl: "https://endpoint.m.pipedream.net", // Replace with your unique Pipedream Webhook URL - status: "ENABLED", - filter: "card-created", - }, - }); - this.db.set("webhookId", response.id); - }, - async deleteWebhook() { - const webhookId = this.db.get("webhookId"); - if (!webhookId) return; - - await axios(this, { - method: "DELETE", - url: `${this.guru._baseUrl()}/webhooks/${webhookId}`, - headers: { - Authorization: `Bearer ${this.guru.$auth.oauth_access_token}`, - }, - }); - this.db.set("webhookId", null); + ...common.methods, + getEventType() { + return "card-created"; }, - async fetchRecentCards() { - const allCards = await this.guru.emitCardCreatedEvent(); - return allCards.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + getSummary(body) { + return `New Card: ${body.properties.cardId}`; }, }, - async run(event) { - const payload = event.body; - if (!payload) { - console.log("No payload found, skipping"); - return; - } - for (const card of payload) { - this.$emit(card, { - id: card.id, - summary: `New Card: ${card.title}`, - ts: Date.parse(card.dateCreated), - }); - } - }, + sampleEmit, }; diff --git a/components/guru/sources/new-card-instant/test-event.mjs b/components/guru/sources/new-card-instant/test-event.mjs new file mode 100644 index 0000000000000..93956d75958fe --- /dev/null +++ b/components/guru/sources/new-card-instant/test-event.mjs @@ -0,0 +1,11 @@ +export default { + "id":"64753163-9817-4500-9651-96177c32e3d1", + "eventType":"card-created", + "user":"bob@getguru.com", + "eventDate":"2021-04-13T13:53:00.000+0000", + "properties":{ + "cardId":"64753163-9817-4500-9651-96177c32e3d1", + "collectionId":"64753163-9817-4500-9651-96177c32e3d1", + "source": "UI" + } +} \ No newline at end of file From 5c57d9a5391e381feb972d5fb48300a7e15298d9 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 12 May 2025 13:08:11 -0300 Subject: [PATCH 3/4] pnpm update --- pnpm-lock.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5ecaa8caadd1..853a28d965062 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5815,7 +5815,11 @@ importers: components/gupshup: {} - components/guru: {} + components/guru: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/h_supertools_analytics_tool: dependencies: From 64eb713991b6e82330e21f9ea71db4a957aa0fc0 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 13 May 2025 09:19:02 -0300 Subject: [PATCH 4/4] Update components/guru/sources/common/base.mjs Co-authored-by: Jorge Cortes --- components/guru/sources/common/base.mjs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/guru/sources/common/base.mjs b/components/guru/sources/common/base.mjs index 4911f98ce97ee..547db9a268add 100644 --- a/components/guru/sources/common/base.mjs +++ b/components/guru/sources/common/base.mjs @@ -3,10 +3,7 @@ import guru from "../../guru.app.mjs"; export default { props: { guru, - http: { - type: "$.interface.http", - customResponse: true, - }, + http: "$.interface.http", db: "$.service.db", }, methods: {