@pipedream/linear_app 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/actions/create-issue/create-issue.mjs +5 -6
- package/actions/get-issue/get-issue.mjs +24 -0
- package/actions/get-teams/get-teams.mjs +2 -2
- package/actions/search-issues/search-issues.mjs +6 -4
- package/actions/update-issue/update-issue.mjs +5 -6
- package/linear_app.app.mjs +51 -8
- package/package.json +1 -1
- package/sources/comment-created-instant/comment-created-instant.mjs +47 -0
- package/sources/common/webhook.mjs +49 -34
- package/sources/issue-created-instant/issue-created-instant.mjs +8 -1
- package/sources/issue-updated-instant/issue-updated-instant.mjs +8 -1
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
key: "linear_app-create-issue",
|
|
6
6
|
name: "Create Issue",
|
|
7
7
|
description: "Create an issue (API Key). See the docs [here](https://developers.linear.app/docs/graphql/working-with-the-graphql-api#creating-and-editing-issues)",
|
|
8
|
-
version: "0.3.
|
|
8
|
+
version: "0.3.5",
|
|
9
9
|
props: {
|
|
10
10
|
linearApp,
|
|
11
11
|
teamId: {
|
|
@@ -49,11 +49,10 @@ export default {
|
|
|
49
49
|
assigneeId,
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
52
|
+
const summary = response.success
|
|
53
|
+
? `Created issue ${response._issue.id}`
|
|
54
|
+
: "Failed to create issue";
|
|
55
|
+
$.export("$summary", summary);
|
|
57
56
|
|
|
58
57
|
return response;
|
|
59
58
|
},
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import linearApp from "../../linear_app.app.mjs";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
key: "linear_app-get-issue",
|
|
5
|
+
name: "Get Issue",
|
|
6
|
+
description: "Get an issue by ID (API Key). See the docs [here](https://developers.linear.app/docs/graphql/working-with-the-graphql-api)",
|
|
7
|
+
version: "0.0.2",
|
|
8
|
+
type: "action",
|
|
9
|
+
props: {
|
|
10
|
+
linearApp,
|
|
11
|
+
issueId: {
|
|
12
|
+
propDefinition: [
|
|
13
|
+
linearApp,
|
|
14
|
+
"issueId",
|
|
15
|
+
],
|
|
16
|
+
description: "The issue ID",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
async run({ $ }) {
|
|
20
|
+
const issue = await this.linearApp.getIssue(this.issueId);
|
|
21
|
+
$.export("$summary", `Found issue with ID ${this.issueId}`);
|
|
22
|
+
return issue;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -4,7 +4,7 @@ export default {
|
|
|
4
4
|
key: "linear_app-get-teams",
|
|
5
5
|
name: "Get Teams",
|
|
6
6
|
description: "Get all the teams (API Key). See the docs [here](https://developers.linear.app/docs/graphql/working-with-the-graphql-api)",
|
|
7
|
-
version: "0.1.
|
|
7
|
+
version: "0.1.4",
|
|
8
8
|
type: "action",
|
|
9
9
|
props: {
|
|
10
10
|
linearApp,
|
|
@@ -12,7 +12,7 @@ export default {
|
|
|
12
12
|
async run({ $ }) {
|
|
13
13
|
const { nodes: teams } = await this.linearApp.listTeams();
|
|
14
14
|
|
|
15
|
-
$.export("summary", `Found ${teams.length} teams(s)`);
|
|
15
|
+
$.export("$summary", `Found ${teams.length} teams(s)`);
|
|
16
16
|
|
|
17
17
|
return teams;
|
|
18
18
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import constants from "../../common/constants.mjs";
|
|
1
2
|
import linearApp from "../../linear_app.app.mjs";
|
|
2
3
|
|
|
3
4
|
export default {
|
|
@@ -5,7 +6,7 @@ export default {
|
|
|
5
6
|
name: "Search Issues",
|
|
6
7
|
description: "Search issues (API Key). See the docs [here](https://developers.linear.app/docs/graphql/working-with-the-graphql-api)",
|
|
7
8
|
type: "action",
|
|
8
|
-
version: "0.1.
|
|
9
|
+
version: "0.1.3",
|
|
9
10
|
props: {
|
|
10
11
|
linearApp,
|
|
11
12
|
query: {
|
|
@@ -85,7 +86,7 @@ export default {
|
|
|
85
86
|
includeArchived,
|
|
86
87
|
} = this;
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
const issues = [];
|
|
89
90
|
let hasNextPage;
|
|
90
91
|
let after;
|
|
91
92
|
const filter = this.buildFilter();
|
|
@@ -101,15 +102,16 @@ export default {
|
|
|
101
102
|
orderBy,
|
|
102
103
|
after,
|
|
103
104
|
includeArchived,
|
|
105
|
+
first: constants.DEFAULT_LIMIT,
|
|
104
106
|
},
|
|
105
107
|
});
|
|
106
108
|
|
|
107
|
-
issues
|
|
109
|
+
issues.push(...nodes);
|
|
108
110
|
after = pageInfo.endCursor;
|
|
109
111
|
hasNextPage = pageInfo.hasNextPage;
|
|
110
112
|
} while (hasNextPage);
|
|
111
113
|
|
|
112
|
-
$.export("summary", `Found ${issues.length} issues`);
|
|
114
|
+
$.export("$summary", `Found ${issues.length} issues`);
|
|
113
115
|
|
|
114
116
|
return issues;
|
|
115
117
|
},
|
|
@@ -5,7 +5,7 @@ export default {
|
|
|
5
5
|
name: "Update Issue",
|
|
6
6
|
description: "Update an issue (API Key). See the docs [here](https://developers.linear.app/docs/graphql/working-with-the-graphql-api#creating-and-editing-issues)",
|
|
7
7
|
type: "action",
|
|
8
|
-
version: "0.0.
|
|
8
|
+
version: "0.0.4",
|
|
9
9
|
props: {
|
|
10
10
|
linearApp,
|
|
11
11
|
issueId: {
|
|
@@ -62,11 +62,10 @@ export default {
|
|
|
62
62
|
},
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
65
|
+
const summary = response.summary
|
|
66
|
+
? `Updated issue ${response._issue.id}`
|
|
67
|
+
: "Failed to update issue";
|
|
68
|
+
$.export("$summary", summary);
|
|
70
69
|
|
|
71
70
|
return response;
|
|
72
71
|
},
|
package/linear_app.app.mjs
CHANGED
|
@@ -25,7 +25,7 @@ export default {
|
|
|
25
25
|
teamId: {
|
|
26
26
|
type: "string",
|
|
27
27
|
label: "Team ID",
|
|
28
|
-
description: "The identifier or key of the team associated with the issue
|
|
28
|
+
description: "The identifier or key of the team associated with the issue",
|
|
29
29
|
async options({ prevContext }) {
|
|
30
30
|
return this.listResourcesOptions({
|
|
31
31
|
prevContext,
|
|
@@ -42,7 +42,7 @@ export default {
|
|
|
42
42
|
projectId: {
|
|
43
43
|
type: "string",
|
|
44
44
|
label: "Project ID",
|
|
45
|
-
description: "The identifier or key of the project associated with the issue
|
|
45
|
+
description: "The identifier or key of the project associated with the issue",
|
|
46
46
|
optional: true,
|
|
47
47
|
async options({ prevContext }) {
|
|
48
48
|
return this.listResourcesOptions({
|
|
@@ -65,7 +65,7 @@ export default {
|
|
|
65
65
|
assigneeId: {
|
|
66
66
|
type: "string",
|
|
67
67
|
label: "Assignee ID",
|
|
68
|
-
description: "The identifier of the user to assign the issue to
|
|
68
|
+
description: "The identifier of the user to assign the issue to",
|
|
69
69
|
optional: true,
|
|
70
70
|
async options({ prevContext }) {
|
|
71
71
|
return this.listResourcesOptions({
|
|
@@ -83,19 +83,19 @@ export default {
|
|
|
83
83
|
boardOrder: {
|
|
84
84
|
type: "string",
|
|
85
85
|
label: "Board order",
|
|
86
|
-
description: "The position of the issue in its column on the board view
|
|
86
|
+
description: "The position of the issue in its column on the board view",
|
|
87
87
|
optional: true,
|
|
88
88
|
},
|
|
89
89
|
issueDescription: {
|
|
90
90
|
type: "string",
|
|
91
91
|
label: "Description",
|
|
92
|
-
description: "The issue description in markdown format
|
|
92
|
+
description: "The issue description in markdown format",
|
|
93
93
|
optional: true,
|
|
94
94
|
},
|
|
95
95
|
issueLabels: {
|
|
96
96
|
type: "string[]",
|
|
97
97
|
label: "Issue Labels",
|
|
98
|
-
description: "The labels in the issue
|
|
98
|
+
description: "The labels in the issue",
|
|
99
99
|
optional: true,
|
|
100
100
|
async options({ prevContext }) {
|
|
101
101
|
return this.listResourcesOptions({
|
|
@@ -108,7 +108,7 @@ export default {
|
|
|
108
108
|
query: {
|
|
109
109
|
type: "string",
|
|
110
110
|
label: "Query",
|
|
111
|
-
description: "Search string to look for
|
|
111
|
+
description: "Search string to look for",
|
|
112
112
|
},
|
|
113
113
|
orderBy: {
|
|
114
114
|
type: "string",
|
|
@@ -120,7 +120,7 @@ export default {
|
|
|
120
120
|
includeArchived: {
|
|
121
121
|
type: "boolean",
|
|
122
122
|
label: "Include archived",
|
|
123
|
-
description: "Should archived resources be included (default: `false`)
|
|
123
|
+
description: "Should archived resources be included? (default: `false`)",
|
|
124
124
|
optional: true,
|
|
125
125
|
},
|
|
126
126
|
},
|
|
@@ -153,6 +153,9 @@ export default {
|
|
|
153
153
|
}) {
|
|
154
154
|
return this.client().issueSearch(query, variables);
|
|
155
155
|
},
|
|
156
|
+
async getIssue(id) {
|
|
157
|
+
return this.client().issue(id);
|
|
158
|
+
},
|
|
156
159
|
async listTeams(variables = {}) {
|
|
157
160
|
return this.client().teams(variables);
|
|
158
161
|
},
|
|
@@ -168,6 +171,9 @@ export default {
|
|
|
168
171
|
async listIssueLabels(variables = {}) {
|
|
169
172
|
return this.client().issueLabels(variables);
|
|
170
173
|
},
|
|
174
|
+
async listComments(variables = {}) {
|
|
175
|
+
return this.client().comments(variables);
|
|
176
|
+
},
|
|
171
177
|
async listResourcesOptions({
|
|
172
178
|
prevContext, resourcesFn, resouceMapper,
|
|
173
179
|
}) {
|
|
@@ -197,5 +203,42 @@ export default {
|
|
|
197
203
|
},
|
|
198
204
|
};
|
|
199
205
|
},
|
|
206
|
+
async *paginateResources({ resourcesFn }) {
|
|
207
|
+
const params = {
|
|
208
|
+
after: null,
|
|
209
|
+
first: constants.DEFAULT_LIMIT,
|
|
210
|
+
};
|
|
211
|
+
let hasNextPage = true;
|
|
212
|
+
do {
|
|
213
|
+
const {
|
|
214
|
+
nodes,
|
|
215
|
+
pageInfo,
|
|
216
|
+
} = await resourcesFn(params);
|
|
217
|
+
for (const d of nodes) {
|
|
218
|
+
yield d;
|
|
219
|
+
}
|
|
220
|
+
hasNextPage = pageInfo.hasNextPage;
|
|
221
|
+
if (hasNextPage) {
|
|
222
|
+
params.after = pageInfo.endCursor;
|
|
223
|
+
}
|
|
224
|
+
} while (hasNextPage);
|
|
225
|
+
},
|
|
226
|
+
isActionSet(body, actions) {
|
|
227
|
+
if (!actions.includes(body?.action)) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
},
|
|
232
|
+
async isProjectIdSet(body, projectId) {
|
|
233
|
+
if (projectId) {
|
|
234
|
+
if (!body.data?.projectId) {
|
|
235
|
+
const issue = body.data?.issue?.id && await this.getIssue(body.data?.issue?.id);
|
|
236
|
+
return issue?._project?.id === projectId;
|
|
237
|
+
} else {
|
|
238
|
+
return body.data.projectId === projectId;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
},
|
|
200
243
|
},
|
|
201
244
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import common from "../common/webhook.mjs";
|
|
2
|
+
import constants from "../../common/constants.mjs";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
...common,
|
|
6
|
+
key: "linear_app-comment-created-instant",
|
|
7
|
+
name: "New Created Comment (Instant)",
|
|
8
|
+
description: "Emit new event when a new comment is created. See the docs [here](https://developers.linear.app/docs/graphql/webhooks)",
|
|
9
|
+
type: "source",
|
|
10
|
+
version: "0.0.3",
|
|
11
|
+
dedupe: "unique",
|
|
12
|
+
methods: {
|
|
13
|
+
...common.methods,
|
|
14
|
+
getResourceTypes() {
|
|
15
|
+
return [
|
|
16
|
+
constants.RESOURCE_TYPE.COMMENT,
|
|
17
|
+
];
|
|
18
|
+
},
|
|
19
|
+
getWebhookLabel() {
|
|
20
|
+
return "Comment created";
|
|
21
|
+
},
|
|
22
|
+
getActions() {
|
|
23
|
+
return [
|
|
24
|
+
constants.ACTION.CREATE,
|
|
25
|
+
];
|
|
26
|
+
},
|
|
27
|
+
getResourcesFn() {
|
|
28
|
+
return this.linearApp.listComments;
|
|
29
|
+
},
|
|
30
|
+
async getLoadedProjectId(event) {
|
|
31
|
+
return event?._project?.id
|
|
32
|
+
|| (await this.linearApp.getIssue(event?._issue?.id))?._project?.id;
|
|
33
|
+
},
|
|
34
|
+
getMetadata(resource) {
|
|
35
|
+
const {
|
|
36
|
+
delivery,
|
|
37
|
+
data,
|
|
38
|
+
createdAt,
|
|
39
|
+
} = resource;
|
|
40
|
+
return {
|
|
41
|
+
id: delivery,
|
|
42
|
+
summary: `New comment event created: ${data.body}`,
|
|
43
|
+
ts: Date.parse(createdAt),
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -4,37 +4,24 @@ import constants from "../../common/constants.mjs";
|
|
|
4
4
|
export default {
|
|
5
5
|
props: {
|
|
6
6
|
linearApp,
|
|
7
|
-
|
|
7
|
+
teamIds: {
|
|
8
|
+
label: "Team IDs",
|
|
9
|
+
type: "string[]",
|
|
8
10
|
propDefinition: [
|
|
9
11
|
linearApp,
|
|
10
12
|
"teamId",
|
|
11
13
|
],
|
|
12
14
|
optional: true,
|
|
13
15
|
},
|
|
14
|
-
projectId: {
|
|
15
|
-
propDefinition: [
|
|
16
|
-
linearApp,
|
|
17
|
-
"projectId",
|
|
18
|
-
],
|
|
19
|
-
},
|
|
20
16
|
http: "$.interface.http",
|
|
21
17
|
db: "$.service.db",
|
|
22
18
|
},
|
|
23
19
|
methods: {
|
|
24
|
-
setWebhookId(id) {
|
|
25
|
-
this.db.set(
|
|
20
|
+
setWebhookId(teamId, id) {
|
|
21
|
+
this.db.set(`webhook-${teamId}`, id);
|
|
26
22
|
},
|
|
27
|
-
getWebhookId() {
|
|
28
|
-
return this.db.get(
|
|
29
|
-
},
|
|
30
|
-
isRelevant(body) {
|
|
31
|
-
if (!this.getActions().includes(body?.action)) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
if (this.projectId) {
|
|
35
|
-
return body.data.projectId === this.projectId;
|
|
36
|
-
}
|
|
37
|
-
return true;
|
|
23
|
+
getWebhookId(teamId) {
|
|
24
|
+
return this.db.get(`webhook-${teamId}`);
|
|
38
25
|
},
|
|
39
26
|
isWebhookValid(clientIp) {
|
|
40
27
|
return constants.CLIENT_IPS.includes(clientIp);
|
|
@@ -51,8 +38,39 @@ export default {
|
|
|
51
38
|
getMetadata() {
|
|
52
39
|
throw new Error("getMetadata is not implemented");
|
|
53
40
|
},
|
|
41
|
+
getResourcesFn() {
|
|
42
|
+
throw new Error("Get resource function not implemented");
|
|
43
|
+
},
|
|
44
|
+
getLoadedProjectId() {
|
|
45
|
+
throw new Error("Get loaded project ID not implemented");
|
|
46
|
+
},
|
|
54
47
|
},
|
|
55
48
|
hooks: {
|
|
49
|
+
async deploy() {
|
|
50
|
+
// Retrieve historical events
|
|
51
|
+
console.log("Retrieving historical events...");
|
|
52
|
+
const events = this.linearApp.paginateResources({
|
|
53
|
+
resourcesFn: this.getResourcesFn(),
|
|
54
|
+
});
|
|
55
|
+
for await (const event of events) {
|
|
56
|
+
const loadedProjectId = await this.getLoadedProjectId(event);
|
|
57
|
+
if (this.projectId && loadedProjectId !== this.projectId) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
event.projectId = loadedProjectId;
|
|
61
|
+
const [
|
|
62
|
+
action,
|
|
63
|
+
] = this.getActions();
|
|
64
|
+
const [
|
|
65
|
+
resourceType,
|
|
66
|
+
] = this.getResourceTypes();
|
|
67
|
+
this.$emit(event, {
|
|
68
|
+
id: event.id,
|
|
69
|
+
ts: Date.parse(event.updatedAt),
|
|
70
|
+
summary: `New ${action} ${resourceType} event: ${event.id}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
},
|
|
56
74
|
async activate() {
|
|
57
75
|
const params = {
|
|
58
76
|
resourceTypes: this.getResourceTypes(),
|
|
@@ -60,19 +78,18 @@ export default {
|
|
|
60
78
|
label: this.getWebhookLabel(),
|
|
61
79
|
};
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
params.teamId =
|
|
65
|
-
|
|
66
|
-
|
|
81
|
+
for (const teamId of this.teamIds) {
|
|
82
|
+
params.teamId = teamId;
|
|
83
|
+
const { _webhook: webhook } = await this.linearApp.createWebhook(params);
|
|
84
|
+
this.setWebhookId(teamId, webhook.id);
|
|
67
85
|
}
|
|
68
|
-
|
|
69
|
-
const { _webhook: webhook } = await this.linearApp.createWebhook(params);
|
|
70
|
-
this.setWebhookId(webhook.id);
|
|
71
86
|
},
|
|
72
87
|
async deactivate() {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
88
|
+
for (const teamId of this.teamIds) {
|
|
89
|
+
const webhookId = this.getWebhookId(teamId);
|
|
90
|
+
if (webhookId) {
|
|
91
|
+
await this.linearApp.deleteWebhook(webhookId);
|
|
92
|
+
}
|
|
76
93
|
}
|
|
77
94
|
},
|
|
78
95
|
},
|
|
@@ -95,9 +112,7 @@ export default {
|
|
|
95
112
|
return;
|
|
96
113
|
}
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.$emit(body, meta);
|
|
101
|
-
}
|
|
115
|
+
const meta = this.getMetadata(resource);
|
|
116
|
+
this.$emit(body, meta);
|
|
102
117
|
},
|
|
103
118
|
};
|
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
name: "New Created Issue (Instant)",
|
|
8
8
|
description: "Emit new event when a new issue is created. See the docs [here](https://developers.linear.app/docs/graphql/webhooks)",
|
|
9
9
|
type: "source",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.2.2",
|
|
11
11
|
dedupe: "unique",
|
|
12
12
|
methods: {
|
|
13
13
|
...common.methods,
|
|
@@ -24,6 +24,13 @@ export default {
|
|
|
24
24
|
constants.ACTION.CREATE,
|
|
25
25
|
];
|
|
26
26
|
},
|
|
27
|
+
getResourcesFn() {
|
|
28
|
+
return this.linearApp.listIssues;
|
|
29
|
+
},
|
|
30
|
+
async getLoadedProjectId(event) {
|
|
31
|
+
return event?._project?.id
|
|
32
|
+
|| (await this.linearApp.getIssue(event?.id))?._project?.id;
|
|
33
|
+
},
|
|
27
34
|
getMetadata(resource) {
|
|
28
35
|
const {
|
|
29
36
|
delivery,
|
|
@@ -7,7 +7,7 @@ export default {
|
|
|
7
7
|
name: "New Updated Issue (Instant)",
|
|
8
8
|
description: "Emit new event when an issue is updated. See the docs [here](https://developers.linear.app/docs/graphql/webhooks)",
|
|
9
9
|
type: "source",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.2.2",
|
|
11
11
|
dedupe: "unique",
|
|
12
12
|
methods: {
|
|
13
13
|
...common.methods,
|
|
@@ -24,6 +24,13 @@ export default {
|
|
|
24
24
|
constants.ACTION.UPDATE,
|
|
25
25
|
];
|
|
26
26
|
},
|
|
27
|
+
getResourcesFn() {
|
|
28
|
+
return this.linearApp.listIssues;
|
|
29
|
+
},
|
|
30
|
+
async getLoadedProjectId(event) {
|
|
31
|
+
return event?._project?.id
|
|
32
|
+
|| (await this.linearApp.getIssue(event?.id))?._project?.id;
|
|
33
|
+
},
|
|
27
34
|
getMetadata(resource) {
|
|
28
35
|
const {
|
|
29
36
|
delivery,
|