diff --git a/components/linear/package.json b/components/linear/package.json index e29596222b378..132895821faf7 100644 --- a/components/linear/package.json +++ b/components/linear/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/linear", - "version": "0.5.6", + "version": "0.5.7", "description": "Pipedream Linear Components", "main": "linear.app.mjs", "keywords": [ diff --git a/components/linear/sources/comment-created-instant/comment-created-instant.mjs b/components/linear/sources/comment-created-instant/comment-created-instant.mjs index d361edcf7eb5a..0ec7547828bc4 100644 --- a/components/linear/sources/comment-created-instant/comment-created-instant.mjs +++ b/components/linear/sources/comment-created-instant/comment-created-instant.mjs @@ -9,6 +9,6 @@ export default { ...commentCreatedInstant, ...utils.getAppProps(commentCreatedInstant), key: "linear-comment-created-instant", - description: "Emit new event when a new comment is created (OAuth). See the docs [here](https://developers.linear.app/docs/graphql/webhooks)", - version: "0.1.6", + description: "Emit new event when a new comment is created (OAuth). [See the documentation](https://developers.linear.app/docs/graphql/webhooks)", + version: "0.1.7", }; diff --git a/components/linear/sources/issue-created-instant/issue-created-instant.mjs b/components/linear/sources/issue-created-instant/issue-created-instant.mjs index 3287572d70695..0b5358e6fe01f 100644 --- a/components/linear/sources/issue-created-instant/issue-created-instant.mjs +++ b/components/linear/sources/issue-created-instant/issue-created-instant.mjs @@ -9,6 +9,6 @@ export default { ...issueCreatedInstant, ...utils.getAppProps(issueCreatedInstant), key: "linear-issue-created-instant", - description: "Emit new event when a new issue is created (OAuth). See the docs [here](https://developers.linear.app/docs/graphql/webhooks)", - version: "0.3.6", + description: "Emit new event when a new issue is created (OAuth). [See the documentation](https://developers.linear.app/docs/graphql/webhooks)", + version: "0.3.7", }; diff --git a/components/linear/sources/issue-updated-instant/issue-updated-instant.mjs b/components/linear/sources/issue-updated-instant/issue-updated-instant.mjs index 7ef63f97e301f..ed7086cb8305c 100644 --- a/components/linear/sources/issue-updated-instant/issue-updated-instant.mjs +++ b/components/linear/sources/issue-updated-instant/issue-updated-instant.mjs @@ -9,6 +9,6 @@ export default { ...issueUpdatedInstant, ...utils.getAppProps(issueUpdatedInstant), key: "linear-issue-updated-instant", - description: "Emit new event when an issue is updated (OAuth). See the docs [here](https://developers.linear.app/docs/graphql/webhooks)", - version: "0.3.6", + description: "Emit new event when an issue is updated (OAuth). See the documentation](https://developers.linear.app/docs/graphql/webhooks)", + version: "0.3.7", }; diff --git a/components/linear/sources/new-issue-status-updated/new-issue-status-updated.mjs b/components/linear/sources/new-issue-status-updated/new-issue-status-updated.mjs index c6da4f42d6283..58db0b2f5d3f5 100644 --- a/components/linear/sources/new-issue-status-updated/new-issue-status-updated.mjs +++ b/components/linear/sources/new-issue-status-updated/new-issue-status-updated.mjs @@ -9,6 +9,6 @@ export default { ...newIssueStatusUpdated, ...utils.getAppProps(newIssueStatusUpdated), key: "linear-new-issue-status-updated", - description: "Emit new event when the status of an issue is updated (OAuth). See the docs [here](https://developers.linear.app/docs/graphql/webhooks)", - version: "0.1.6", + description: "Emit new event when the status of an issue is updated (OAuth). [See the documentation](https://developers.linear.app/docs/graphql/webhooks)", + version: "0.1.7", }; diff --git a/components/linear_app/package.json b/components/linear_app/package.json index c8c3f683ebfb6..51971257eccab 100644 --- a/components/linear_app/package.json +++ b/components/linear_app/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/linear_app", - "version": "0.5.6", + "version": "0.5.7", "description": "Pipedream Linear_app Components", "main": "linear_app.app.mjs", "keywords": [ diff --git a/components/linear_app/sources/comment-created-instant/comment-created-instant.mjs b/components/linear_app/sources/comment-created-instant/comment-created-instant.mjs index 7af2125de05cf..e4524b05b3ac7 100644 --- a/components/linear_app/sources/comment-created-instant/comment-created-instant.mjs +++ b/components/linear_app/sources/comment-created-instant/comment-created-instant.mjs @@ -5,9 +5,9 @@ export default { ...common, key: "linear_app-comment-created-instant", name: "New Created Comment (Instant)", - description: "Emit new event when a new comment is created. See the docs [here](https://developers.linear.app/docs/graphql/webhooks)", + description: "Emit new event when a new comment is created. [See the documentation](https://developers.linear.app/docs/graphql/webhooks)", type: "source", - version: "0.1.6", + version: "0.1.7", dedupe: "unique", methods: { ...common.methods, @@ -50,6 +50,11 @@ export default { }, }; }, + getResource(comment) { + return this.linearApp.getComment({ + commentId: comment.id, + }); + }, getMetadata(resource) { const { delivery, diff --git a/components/linear_app/sources/common/webhook.mjs b/components/linear_app/sources/common/webhook.mjs index 67d31aca1c64d..155b7baf0f211 100644 --- a/components/linear_app/sources/common/webhook.mjs +++ b/components/linear_app/sources/common/webhook.mjs @@ -1,7 +1,7 @@ import linearApp from "../../linear_app.app.mjs"; import constants from "../../common/constants.mjs"; import utils from "../../common/utils.mjs"; -import { ConfigurationError } from "@pipedream/platform"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; export default { props: { @@ -21,18 +21,28 @@ export default { "projectId", ], }, - http: "$.interface.http", db: "$.service.db", }, async additionalProps() { const props = {}; - if (!(await this.isAdmin())) { - props.alert = { - type: "alert", - alertType: "error", - content: "You must have an admin role to create or manage webhooks. See the Linear [documentation](https://linear.app/docs/api-and-webhooks#webhooks) for details.", + let msg; + if (await this.isAdmin()) { + msg = "Admin role detected. Trigger will be set up as a webhook."; + props.http = "$.interface.http"; + } else { + msg = "No admin role detected. Trigger will set up to use polling."; + props.timer = { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, }; } + props.alert = { + type: "alert", + alertType: "info", + content: `${msg} See the Linear [documentation](https://linear.app/docs/api-and-webhooks#webhooks) for details.`, + }; return props; }, methods: { @@ -54,6 +64,9 @@ export default { useGraphQl() { return true; }, + getResource() { + throw new Error("getResource is not implemented"); + }, getResourceTypes() { throw new Error("getResourceTypes is not implemented"); }, @@ -85,15 +98,7 @@ export default { }); return data?.user?.admin; }, - }, - hooks: { - async deploy() { - if (!(await this.isAdmin())) { - throw new ConfigurationError("You must have an admin role to create or manage webhooks. See the Linear [documentation](https://linear.app/docs/api-and-webhooks#webhooks) for details."); - } - - // Retrieve historical events - console.log("Retrieving historical events..."); + async emitPolledResources() { const stream = this.linearApp.paginateResources({ resourcesFn: this.getResourcesFn(), resourcesFnArgs: this.getResourcesFnArgs(), @@ -107,73 +112,89 @@ export default { this.$emit(resource, this.getMetadata(resource)); }); }, + }, + hooks: { + async deploy() { + // Retrieve historical events + console.log("Retrieving historical events..."); + await this.emitPolledResources(); + }, async activate() { - const args = { - resourceTypes: this.getResourceTypes(), - url: this.http.endpoint, - label: this.getWebhookLabel(), - }; - if (!this.teamIds && !this.teamId) { - args.allPublicTeams = true; - const { _webhook: webhook } = await this.linearApp.createWebhook(args); - this.setWebhookId("1", webhook.id); - return; - } - const teamIds = this.teamIds || [ - this.teamId, - ]; - for (const teamId of teamIds) { - const { _webhook: webhook } = - await this.linearApp.createWebhook({ - teamId, - ...args, - }); - this.setWebhookId(teamId, webhook.id); + if (await this.isAdmin()) { + const args = { + resourceTypes: this.getResourceTypes(), + url: this.http.endpoint, + label: this.getWebhookLabel(), + }; + if (!this.teamIds && !this.teamId) { + args.allPublicTeams = true; + const { _webhook: webhook } = await this.linearApp.createWebhook(args); + this.setWebhookId("1", webhook.id); + return; + } + const teamIds = this.teamIds || [ + this.teamId, + ]; + for (const teamId of teamIds) { + const { _webhook: webhook } = + await this.linearApp.createWebhook({ + teamId, + ...args, + }); + this.setWebhookId(teamId, webhook.id); + } } }, async deactivate() { - if (!this.teamIds && !this.teamId) { - const webhookId = this.getWebhookId("1"); - if (webhookId) { - await this.linearApp.deleteWebhook(webhookId); + if (await this.isAdmin()) { + if (!this.teamIds && !this.teamId) { + const webhookId = this.getWebhookId("1"); + if (webhookId) { + await this.linearApp.deleteWebhook(webhookId); + } + return; } - return; - } - const teamIds = this.teamIds || [ - this.teamId, - ]; - for (const teamId of teamIds) { - const webhookId = this.getWebhookId(teamId); - if (webhookId) { - await this.linearApp.deleteWebhook(webhookId); + const teamIds = this.teamIds || [ + this.teamId, + ]; + for (const teamId of teamIds) { + const webhookId = this.getWebhookId(teamId); + if (webhookId) { + await this.linearApp.deleteWebhook(webhookId); + } } } }, }, async run(event) { - const { - client_ip: clientIp, - body, - headers, - } = event; + if (!(await this.isAdmin())) { + await this.emitPolledResources(); + } else { + const { + client_ip: clientIp, + body, + headers, + } = event; - const { [constants.LINEAR_DELIVERY_HEADER]: delivery } = headers; + const { [constants.LINEAR_DELIVERY_HEADER]: delivery } = headers; - const resource = { - ...body, - delivery, - }; + const resource = { + ...body, + delivery, + }; - if (!this.isWebhookValid(clientIp)) { - console.log("Webhook is not valid"); - return; - } + if (!this.isWebhookValid(clientIp)) { + console.log("Webhook is not valid"); + return; + } - if (!(await this.isFromProject(body)) || !this.isRelevant(body)) { - return; - } + if (!(await this.isFromProject(body)) || !this.isRelevant(body)) { + return; + } - const meta = this.getMetadata(resource); - this.$emit(body, meta); + const meta = this.getMetadata(resource); + const item = await this.getResource(body.data); + this.$emit(item, meta); + } }, }; diff --git a/components/linear_app/sources/issue-created-instant/issue-created-instant.mjs b/components/linear_app/sources/issue-created-instant/issue-created-instant.mjs index 01dc46e253f7d..ba52f23a8e47d 100644 --- a/components/linear_app/sources/issue-created-instant/issue-created-instant.mjs +++ b/components/linear_app/sources/issue-created-instant/issue-created-instant.mjs @@ -5,9 +5,9 @@ export default { ...common, key: "linear_app-issue-created-instant", name: "New Created Issue (Instant)", - description: "Emit new event when a new issue is created. See the docs [here](https://developers.linear.app/docs/graphql/webhooks)", + description: "Emit new event when a new issue is created. [See the documentation](https://developers.linear.app/docs/graphql/webhooks)", type: "source", - version: "0.3.6", + version: "0.3.7", dedupe: "unique", methods: { ...common.methods, @@ -42,6 +42,11 @@ export default { isRelevant(body) { return body?.action === "create"; }, + getResource(issue) { + return this.linearApp.getIssue({ + issueId: issue.id, + }); + }, getMetadata(resource) { const { delivery, diff --git a/components/linear_app/sources/issue-updated-instant/issue-updated-instant.mjs b/components/linear_app/sources/issue-updated-instant/issue-updated-instant.mjs index 5f68aadbd1f79..3dcdbb81325d3 100644 --- a/components/linear_app/sources/issue-updated-instant/issue-updated-instant.mjs +++ b/components/linear_app/sources/issue-updated-instant/issue-updated-instant.mjs @@ -5,9 +5,9 @@ export default { ...common, key: "linear_app-issue-updated-instant", name: "New Updated Issue (Instant)", - description: "Emit new event when an issue is updated. See the docs [here](https://developers.linear.app/docs/graphql/webhooks)", + description: "Emit new event when an issue is updated. [See the documentation](https://developers.linear.app/docs/graphql/webhooks)", type: "source", - version: "0.3.6", + version: "0.3.7", dedupe: "unique", methods: { ...common.methods, @@ -39,17 +39,22 @@ export default { }, }; }, + getResource(issue) { + return this.linearApp.getIssue({ + issueId: issue.id, + }); + }, getMetadata(resource) { const { - delivery, title, data, updatedAt, } = resource; + const ts = Date.parse(data?.updatedAt || updatedAt); return { - id: delivery || resource.id, + id: `${resource.id}-${ts}`, summary: `Issue Updated: ${data?.title || title}`, - ts: Date.parse(updatedAt), + ts, }; }, }, diff --git a/components/linear_app/sources/new-issue-status-updated/new-issue-status-updated.mjs b/components/linear_app/sources/new-issue-status-updated/new-issue-status-updated.mjs index 78fa6fb227c19..59b00fbb2c517 100644 --- a/components/linear_app/sources/new-issue-status-updated/new-issue-status-updated.mjs +++ b/components/linear_app/sources/new-issue-status-updated/new-issue-status-updated.mjs @@ -1,18 +1,18 @@ import common from "../common/webhook.mjs"; import constants from "../../common/constants.mjs"; +import utils from "../../common/utils.mjs"; export default { ...common, key: "linear_app-new-issue-status-updated", name: "New Issue Status Updated (Instant)", - description: "Emit new event when the status of an issue is updated. See the docs [here](https://developers.linear.app/docs/graphql/webhooks)", + description: "Emit new event when the status of an issue is updated. [See the documentation](https://developers.linear.app/docs/graphql/webhooks)", type: "source", - version: "0.1.6", + version: "0.1.7", dedupe: "unique", props: { linearApp: common.props.linearApp, - http: common.props.http, - db: common.props.db, + db: "$.service.db", teamId: { label: "Team ID", type: "string", @@ -20,7 +20,7 @@ export default { common.props.linearApp, "teamId", ], - optional: true, + reloadProps: true, }, projectId: { propDefinition: [ @@ -41,6 +41,12 @@ export default { }, methods: { ...common.methods, + _getPreviousStatuses() { + return this.db.get("previousStatuses") || {}; + }, + _setPreviousStatuses(previousStatuses) { + this.db.set("previousStatuses", previousStatuses); + }, getResourceTypes() { return [ constants.RESOURCE_TYPE.ISSUE, @@ -79,6 +85,11 @@ export default { isRelevant(body) { return body?.updatedFrom?.stateId && (!this.stateId || body.data.stateId === this.stateId); }, + getResource(issue) { + return this.linearApp.getIssue({ + issueId: issue.id, + }); + }, getMetadata(resource) { const { delivery, @@ -86,11 +97,43 @@ export default { data, updatedAt, } = resource; + const ts = Date.parse(updatedAt); return { - id: delivery || resource.id, + id: delivery || `${resource.id}-${ts}`, summary: `Issue status updated: ${data?.title || title}`, - ts: Date.parse(updatedAt), + ts, }; }, + async emitPolledResources() { + const previousStatuses = this._getPreviousStatuses(); + const newStatuses = {}; + + const stream = this.linearApp.paginateResources({ + resourcesFn: this.getResourcesFn(), + resourcesFnArgs: this.getResourcesFnArgs(), + useGraphQl: this.useGraphQl(), + max: 1000, + }); + const resources = await utils.streamIterator(stream); + + const updatedResources = []; + for (const issue of resources) { + newStatuses[issue.id] = issue.state.id; + if (issue.createdAt === issue.updatedAt) { + continue; + } + if (previousStatuses[issue.id] !== issue.state.id) { + updatedResources.push(issue); + } + } + + this._setPreviousStatuses(newStatuses); + + updatedResources + .reverse() + .forEach((resource) => { + this.$emit(resource, this.getMetadata(resource)); + }); + }, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfd11476845c8..351f394949037 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24480,22 +24480,22 @@ packages: superagent@3.8.1: resolution: {integrity: sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==} engines: {node: '>= 4.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@4.1.0: resolution: {integrity: sha512-FT3QLMasz0YyCd4uIi5HNe+3t/onxMyEho7C3PSqmti3Twgy2rXT4fmkTz6wRL6bTF4uzPcfkUCa8u4JWHw8Ag==} engines: {node: '>= 6.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@5.3.1: resolution: {integrity: sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==} engines: {node: '>= 7.0.0'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at . superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} - deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + deprecated: Please downgrade to v7.1.5 if you need IE/ActiveXObject support OR upgrade to v8.0.0 as we no longer support IE and published an incorrect patch version (see https://github.com/visionmedia/superagent/issues/1731) supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}