wuffle 0.41.5 → 0.43.0

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.
Files changed (41) hide show
  1. package/bin/run.js +1 -1
  2. package/lib/apps/auth-routes/AuthRoutes.js +5 -3
  3. package/lib/apps/automatic-dev-flow.js +2 -0
  4. package/lib/apps/background-sync/BackgroundSync.js +11 -9
  5. package/lib/apps/board-api-routes.js +3 -3
  6. package/lib/apps/board-routes.js +1 -1
  7. package/lib/apps/dump-store/local/DumpStoreLocal.js +5 -3
  8. package/lib/apps/dump-store/s3/DumpStoreS3.js +5 -3
  9. package/lib/apps/events-sync.js +28 -18
  10. package/lib/apps/github-app/GithubApp.js +19 -3
  11. package/lib/apps/github-checks/GithubChecks.js +6 -4
  12. package/lib/apps/github-client/GithubClient.js +33 -3
  13. package/lib/apps/github-comments/GithubComments.js +205 -0
  14. package/lib/apps/github-comments/index.js +6 -0
  15. package/lib/apps/github-issues/GithubIssues.js +77 -22
  16. package/lib/apps/github-reviews/GithubReviews.js +7 -3
  17. package/lib/apps/github-statuses/GithubStatuses.js +2 -0
  18. package/lib/apps/log-events.js +11 -7
  19. package/lib/apps/reindex-store.js +14 -2
  20. package/lib/apps/route-compression.js +1 -1
  21. package/lib/apps/route-https.js +1 -1
  22. package/lib/apps/search/Search.js +32 -5
  23. package/lib/apps/security-context/SecurityContext.js +5 -0
  24. package/lib/apps/user-access/UserAccess.js +6 -4
  25. package/lib/apps/webhook-events/WebhookEvents.js +33 -9
  26. package/lib/columns.js +16 -3
  27. package/lib/filters.js +20 -16
  28. package/lib/index.js +1 -0
  29. package/lib/links.js +15 -5
  30. package/lib/probot/apps/setup.js +1 -1
  31. package/lib/probot/manifest-creation.js +1 -1
  32. package/lib/store.js +11 -0
  33. package/lib/types.d.ts +23 -6
  34. package/lib/util/index.js +1 -1
  35. package/lib/util/links.js +48 -26
  36. package/package.json +24 -19
  37. package/public/bundle.css +1 -1
  38. package/public/bundle.js +1 -1
  39. package/public/bundle.js.map +1 -1
  40. package/public/service-worker.js.map +1 -1
  41. package/CHANGELOG.md +0 -465
package/bin/run.js CHANGED
@@ -156,7 +156,7 @@ async function validate() {
156
156
  }
157
157
  }
158
158
 
159
- function checkEnv(key, isError=false) {
159
+ function checkEnv(key, isError = false) {
160
160
 
161
161
  if (!process.env[key]) {
162
162
  return (isError ? error : warning)(
@@ -23,9 +23,11 @@ const {
23
23
  * Under the hood, it uses GitHub's APIs to perform user authentication
24
24
  * via OAuth.
25
25
  *
26
- * @param {import("../../types").Logger} logger
27
- * @param {import("../../types").Router} router
28
- * @param {import("../security-context/SecurityContext")} securityContext
26
+ * @constructor
27
+ *
28
+ * @param {import('../../types').Logger} logger
29
+ * @param {import('../../types').Router} router
30
+ * @param {import('../security-context/SecurityContext')} securityContext
29
31
  */
30
32
  function AuthRoutes(logger, router, securityContext) {
31
33
 
@@ -9,6 +9,8 @@ const IN_REVIEW = 'IN_REVIEW';
9
9
  * across the board, as long as the user adheres to a specified
10
10
  * dev flow.
11
11
  *
12
+ * @constructor
13
+ *
12
14
  * @param {import('./webhook-events/WebhookEvents')} webhookEvents
13
15
  * @param {import('./github-issues/GithubIssues')} githubIssues
14
16
  * @param {import('../columns')} columns
@@ -15,12 +15,14 @@ function isInternalError(error) {
15
15
  /**
16
16
  * This component performs a periodic background sync of a project.
17
17
  *
18
- * @param {import("../../types").Logger} logger
18
+ * @constructor
19
+ *
20
+ * @param {import('../../types').Logger} logger
19
21
  * @param {Object} config
20
- * @param {import("../../store")} store
21
- * @param {import("../github-client/GithubClient")} githubClient
22
- * @param {import("../github-app/GithubApp")} githubApp
23
- * @param {import("../../events")} events
22
+ * @param {import('../../store')} store
23
+ * @param {import('../github-client/GithubClient')} githubClient
24
+ * @param {import('../github-app/GithubApp')} githubApp
25
+ * @param {import('../../events')} events
24
26
  */
25
27
  function BackgroundSync(logger, config, store, githubClient, githubApp, events) {
26
28
 
@@ -140,8 +142,8 @@ We automatically synchronize all repositories you granted us access to via the G
140
142
  }, 'processing');
141
143
 
142
144
  const params = {
143
- sort: 'updated',
144
- direction: 'desc',
145
+ sort: /** @type { 'updated' } */ ('updated'),
146
+ direction: /** @type { 'desc' } */ ('desc'),
145
147
  per_page: 100,
146
148
  owner,
147
149
  repo
@@ -161,7 +163,7 @@ We automatically synchronize all repositories you granted us access to via the G
161
163
  ...params,
162
164
  state: 'open'
163
165
  },
164
- response => response.data.filter(issue => !issue.pull_request)
166
+ response => response.data.filter(issue => !('pull_request' in issue))
165
167
  ),
166
168
 
167
169
  // closed issues, updated last 30 days
@@ -172,7 +174,7 @@ We automatically synchronize all repositories you granted us access to via the G
172
174
  state: 'closed',
173
175
  since: new Date(syncClosedSince).toISOString()
174
176
  },
175
- response => response.data.filter(issue => !issue.pull_request)
177
+ response => response.data.filter(issue => !('pull_request' in issue))
176
178
  ),
177
179
 
178
180
  // open pulls
@@ -13,9 +13,9 @@ const {
13
13
  * This component provides the board API routes.
14
14
  *
15
15
  * @param {Object} config
16
- * @param {import("../store")} store
17
- * @param {import("../types").Router} router
18
- * @param {import("../types").Logger} logger
16
+ * @param {import('../store')} store
17
+ * @param {import('../types').Router} router
18
+ * @param {import('../types').Logger} logger
19
19
  * @param {import('./github-client/GithubClient')} githubClient
20
20
  * @param {import('./auth-routes/AuthRoutes')} authRoutes
21
21
  * @param {import('./user-access/UserAccess')} userAccess
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  /**
7
7
  * This component provides the publicly accessible board routes.
8
8
  *
9
- * @param {import("express").Router} router
9
+ * @param {import('express').Router} router
10
10
  */
11
11
  module.exports = async (router) => {
12
12
 
@@ -8,9 +8,11 @@ const mkdirp = require('mkdirp');
8
8
  * This component restores a store dump on startup and periodically
9
9
  * persists the store to disc.
10
10
  *
11
- * @param {import("../../../types").Logger} logger
12
- * @param {import("../../../store")} store
13
- * @param {import("../../../events")} events
11
+ * @constructor
12
+ *
13
+ * @param {import('../../../types').Logger} logger
14
+ * @param {import('../../../store')} store
15
+ * @param {import('../../../events')} events
14
16
  */
15
17
  function DumpStoreLocal(logger, store, events) {
16
18
 
@@ -5,9 +5,11 @@ const S3 = require('./S3');
5
5
  * This component restores a store dump on startup and periodically
6
6
  * persists the store to disc.
7
7
  *
8
- * @param {import("../../../types").Logger} logger
9
- * @param {import("../../../store")} store
10
- * @param {import("../../../events")} events
8
+ * @constructor
9
+ *
10
+ * @param {import('../../../types').Logger} logger
11
+ * @param {import('../../../store')} store
12
+ * @param {import('../../../events')} events
11
13
  */
12
14
  function DumpStoreS3(logger, store, events) {
13
15
 
@@ -11,11 +11,13 @@ const {
11
11
  /**
12
12
  * This component updates the stored issues based on GitHub events.
13
13
  *
14
- * @param {import("./webhook-events/WebhookEvents")} webhookEvents
15
- * @param {import("../store")} store
16
- * @param {import("../types").Logger} logger
14
+ * @constructor
15
+ *
16
+ * @param {import('./webhook-events/WebhookEvents')} webhookEvents
17
+ * @param {import('../store')} store
18
+ * @param {import('../types').Logger} logger
17
19
  */
18
- module.exports = function EventsSync(webhookEvents, store, logger) {
20
+ function EventsSync(webhookEvents, store, logger) {
19
21
 
20
22
  const log = logger.child({
21
23
  name: 'wuffle:user-access'
@@ -71,6 +73,25 @@ module.exports = function EventsSync(webhookEvents, store, logger) {
71
73
  return store.removeIssueById(id);
72
74
  });
73
75
 
76
+ // issue_comment ///////////////////////
77
+
78
+ // https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#issue_comment
79
+
80
+ webhookEvents.on([
81
+ 'issue_comment.created',
82
+ 'issue_comment.edited'
83
+ ], async ({ payload }) => {
84
+
85
+ const {
86
+ issue,
87
+ repository
88
+ } = payload;
89
+
90
+ // necro bump issue on comment
91
+ return store.updateIssue(filterIssueOrPull(issue, repository));
92
+ });
93
+
94
+
74
95
  // pull requests //////////////////
75
96
 
76
97
  webhookEvents.on([
@@ -203,19 +224,6 @@ module.exports = function EventsSync(webhookEvents, store, logger) {
203
224
  });
204
225
 
205
226
 
206
- // issue_comment ///////////////////////
207
-
208
- // https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#issue_comment
209
-
210
- webhookEvents.on([
211
- 'issue_comment.created',
212
- 'issue_comment.edited'
213
- ], async ({ payload }) => {
214
-
215
- // necro bump issue
216
- });
217
-
218
-
219
227
  // issues ///////////////////////////////
220
228
 
221
229
  // https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#issues
@@ -265,4 +273,6 @@ module.exports = function EventsSync(webhookEvents, store, logger) {
265
273
  return store.removeIssueById(storedIssue.id);
266
274
  });
267
275
 
268
- };
276
+ }
277
+
278
+ module.exports = EventsSync;
@@ -34,10 +34,12 @@ const RequiredEvents = [
34
34
  * This component validates and exposes
35
35
  * installations of the GitHub app.
36
36
  *
37
+ * @constructor
38
+ *
37
39
  * @param {Object} config
38
- * @param {import("../../types").ProbotApp} app
39
- * @param {import("../../types").Logger} logger
40
- * @param {import("../../types").Injector} injector
40
+ * @param {import('../../types').ProbotApp} app
41
+ * @param {import('../../types').Logger} logger
42
+ * @param {import('../../types').Injector} injector
41
43
  */
42
44
  function GithubApp(config, app, logger, injector) {
43
45
 
@@ -68,6 +70,9 @@ function GithubApp(config, app, logger, injector) {
68
70
 
69
71
  // functionality /////////////////
70
72
 
73
+ /**
74
+ * @return {Promise<import('../../types').Octokit>}
75
+ */
71
76
  function getAppScopedClient() {
72
77
  return app.auth();
73
78
  }
@@ -159,6 +164,11 @@ function GithubApp(config, app, logger, injector) {
159
164
  return getInstallationById(installation_id).then(installation => !!installation);
160
165
  }
161
166
 
167
+ /**
168
+ * @param {string} login
169
+ *
170
+ * @return {boolean}
171
+ */
162
172
  function isLoginEnabled(login) {
163
173
  if (allowedOrgs) {
164
174
  return allowedOrgs.some(org => org === login);
@@ -167,6 +177,12 @@ function GithubApp(config, app, logger, injector) {
167
177
  return true;
168
178
  }
169
179
 
180
+ /**
181
+ * @param {string} requested
182
+ * @param {string} actual
183
+ *
184
+ * @return {boolean}
185
+ */
170
186
  function isRequiredLevel(requested, actual) {
171
187
  return PermissionLevels[requested] <= PermissionLevels[actual || 'none'];
172
188
  }
@@ -6,10 +6,12 @@ const {
6
6
  /**
7
7
  * This component updates the stored issues based on GitHub events.
8
8
  *
9
- * @param {import("../webhook-events/WebhookEvents")} webhookEvents
10
- * @param {import("../../events")} events
11
- * @param {import("../github-client/GithubClient")} githubClient
12
- * @param {import("../../store")} store
9
+ * @constructor
10
+ *
11
+ * @param {import('../webhook-events/WebhookEvents')} webhookEvents
12
+ * @param {import('../../events')} events
13
+ * @param {import('../github-client/GithubClient')} githubClient
14
+ * @param {import('../../store')} store
13
15
  */
14
16
  module.exports = function GithubChecks(webhookEvents, events, githubClient, store) {
15
17
 
@@ -10,7 +10,20 @@ const {
10
10
  // 15 minutes
11
11
  const TTL = 1000 * 60 * 15;
12
12
 
13
-
13
+ /**
14
+ * @typedef { import('../../types').Octokit } Octokit
15
+ * @typedef { { [x: string]: Promise<Octokit> } } LoginCache
16
+ */
17
+
18
+ /**
19
+ * @constructor
20
+ *
21
+ * @param {import('../../types').ProbotApp} app
22
+ * @param {import('../webhook-events/WebhookEvents')} webhookEvents
23
+ * @param {import('../../types').Logger} logger
24
+ * @param {import('../github-app/GithubApp')} githubApp
25
+ * @param {import('../../events')} events
26
+ */
14
27
  function GitHubClient(app, webhookEvents, logger, githubApp, events) {
15
28
 
16
29
  const log = logger.child({
@@ -21,7 +34,7 @@ function GitHubClient(app, webhookEvents, logger, githubApp, events) {
21
34
 
22
35
  // cached data ///////////////////
23
36
 
24
- let authByLogin = {};
37
+ let authByLogin = /** @type { LoginCache } */ ({});
25
38
 
26
39
 
27
40
  // reactivity ////////////////////
@@ -33,10 +46,20 @@ function GitHubClient(app, webhookEvents, logger, githubApp, events) {
33
46
 
34
47
  // functionality /////////////////
35
48
 
49
+ /**
50
+ * @param {number} id
51
+ *
52
+ * @return {Promise<Octokit>}
53
+ */
36
54
  function getInstallationScoped(id) {
37
55
  return app.auth(id);
38
56
  }
39
57
 
58
+ /**
59
+ * @param {string} login
60
+ *
61
+ * @return {Promise<Octokit>}
62
+ */
40
63
  function getOrgScoped(login) {
41
64
 
42
65
  let auth = authByLogin[login];
@@ -50,7 +73,7 @@ function GitHubClient(app, webhookEvents, logger, githubApp, events) {
50
73
  auth = authByLogin[login] =
51
74
  githubApp.getInstallationByLogin(login)
52
75
  .then(
53
- installation => this.getInstallationScoped(installation.id),
76
+ installation => getInstallationScoped(installation.id),
54
77
  err => {
55
78
  log.error({
56
79
  err,
@@ -64,6 +87,13 @@ function GitHubClient(app, webhookEvents, logger, githubApp, events) {
64
87
  return auth;
65
88
  }
66
89
 
90
+ /**
91
+ * Return user scoped octokit instance.
92
+ *
93
+ * @param {string|{ access_token: string }} user
94
+ *
95
+ * @return {Promise<Octokit>}
96
+ */
67
97
  function getUserScoped(user) {
68
98
 
69
99
  const access_token = typeof user === 'string' ? user : user.access_token;
@@ -0,0 +1,205 @@
1
+ const {
2
+ repoAndOwner
3
+ } = require('../../util');
4
+
5
+ const {
6
+ filterUser,
7
+ filterIssue
8
+ } = require('../../filters');
9
+
10
+ const gql = require('fake-tag');
11
+
12
+
13
+ /**
14
+ * This component updates the stored issues based on GitHub events.
15
+ *
16
+ * @constructor
17
+ *
18
+ * @param {import('../webhook-events/WebhookEvents')} webhookEvents
19
+ * @param {import('../../events')} events
20
+ * @param {import('../github-client/GithubClient')} githubClient
21
+ * @param {import('../../store')} store
22
+ */
23
+ function GithubComments(webhookEvents, events, githubClient, store) {
24
+
25
+ // issues /////////////////////
26
+
27
+ events.on('backgroundSync.sync', async (event) => {
28
+
29
+ const {
30
+ issue
31
+ } = event;
32
+
33
+ const {
34
+ id,
35
+ number
36
+ } = issue;
37
+
38
+ const {
39
+ repo,
40
+ owner
41
+ } = repoAndOwner(issue);
42
+
43
+ const github = await githubClient.getOrgScoped(owner);
44
+
45
+ const result = await github.graphql(gql`
46
+
47
+ fragment CommentInfo on IssueComment {
48
+ id: databaseId
49
+ node_id: id
50
+ body: bodyText
51
+ created_at: publishedAt
52
+ authorAssociation,
53
+ html_url: url,
54
+ user: author {
55
+ login
56
+ avatar_url: avatarUrl,
57
+ html_url: url
58
+ }
59
+ }
60
+
61
+ query FetchComments(
62
+ $repo: String!,
63
+ $owner: String!,
64
+ $issue_number: Int!,
65
+ $after: String
66
+ ) {
67
+ repository(name: $repo, owner: $owner) {
68
+ issueOrPullRequest(number: $issue_number) {
69
+ ...on Issue {
70
+ comments(first: 100, after: $after) {
71
+ edges {
72
+ node {
73
+ ...CommentInfo
74
+ }
75
+ }
76
+ pageInfo {
77
+ endCursor
78
+ hasNextPage
79
+ }
80
+ totalCount
81
+ }
82
+ }
83
+ ...on PullRequest {
84
+ comments(first: 100, after: $after) {
85
+ edges {
86
+ node {
87
+ ...CommentInfo
88
+ }
89
+ }
90
+ pageInfo {
91
+ endCursor
92
+ hasNextPage
93
+ }
94
+ totalCount
95
+ }
96
+ }
97
+ }
98
+ }
99
+ }
100
+ `, {
101
+ owner,
102
+ repo,
103
+ issue_number: number
104
+ });
105
+
106
+ const comments = (
107
+ result.repository.issueOrPullRequest.comments.edges.map(e => e.node)
108
+ );
109
+
110
+ await store.queueUpdate({
111
+ id,
112
+ comments: comments.map(filterComment)
113
+ });
114
+ });
115
+
116
+ webhookEvents.on([
117
+ 'issue_comment'
118
+ ], async ({ payload }) => {
119
+ const {
120
+ action,
121
+ comment: _comment,
122
+ issue: _issue,
123
+ repository
124
+ } = payload;
125
+
126
+ const commentedIssue = filterIssue(_issue, repository);
127
+ const comment = filterComment(_comment);
128
+
129
+ const {
130
+ id
131
+ } = commentedIssue;
132
+
133
+ const issue = await store.getIssueById(id);
134
+
135
+ let comments = Array.isArray(issue.comments)
136
+ ? issue.comments
137
+ : [];
138
+
139
+ if (action === 'created') {
140
+ comments = [
141
+ ...comments,
142
+ comment
143
+ ];
144
+ }
145
+
146
+ if (action === 'deleted') {
147
+ const index = comments.findIndex(c => c.id === comment.id);
148
+
149
+ if (index !== -1) {
150
+ comments = [
151
+ ...comments.slice(0, index),
152
+ ...comments.slice(index + 1)
153
+ ];
154
+ }
155
+ }
156
+
157
+ if (action === 'edited') {
158
+
159
+ const index = comments.findIndex(c => c.id === comment.id);
160
+
161
+ if (index !== -1) {
162
+ comments = [
163
+ ...comments.slice(0, index),
164
+ comment,
165
+ ...comments.slice(index + 1)
166
+ ];
167
+ } else {
168
+ comments = [
169
+ ...comments,
170
+ comment
171
+ ];
172
+ }
173
+ }
174
+
175
+ await store.updateIssue({
176
+ ...commentedIssue,
177
+ comments
178
+ });
179
+ });
180
+
181
+ }
182
+
183
+ module.exports = GithubComments;
184
+
185
+
186
+ function filterComment(comment) {
187
+
188
+ const {
189
+ id,
190
+ node_id,
191
+ body,
192
+ created_at,
193
+ html_url,
194
+ user
195
+ } = comment;
196
+
197
+ return {
198
+ id,
199
+ node_id,
200
+ body,
201
+ created_at,
202
+ html_url,
203
+ user: filterUser(user)
204
+ };
205
+ }
@@ -0,0 +1,6 @@
1
+ const GithubComments = require('./GithubComments');
2
+
3
+ module.exports = {
4
+ __init__: [ 'githubComments' ],
5
+ githubComments: [ 'type', GithubComments ]
6
+ };