wuffle 0.75.0 → 0.76.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.
package/app.yml CHANGED
@@ -46,6 +46,7 @@ default_events:
46
46
  # - team
47
47
  # - team_add
48
48
  # - watch
49
+ - sub_issues
49
50
 
50
51
  # The set of permissions needed by the GitHub App. The format of the object uses
51
52
  # the permission name for the key (for example, issues) and the access type for
@@ -172,19 +172,19 @@ export default async function BoardApiRoutes(
172
172
  const items = store.getBoard();
173
173
  const cursor = store.getUpdateCursor();
174
174
 
175
- filterBoardItems(req, items).then(filteredItems => {
175
+ return filterBoardItems(req, items).then(filteredItems => {
176
176
 
177
- return res.type('json').json({
177
+ res.type('json').json({
178
178
  items: filteredItems,
179
179
  cursor
180
- }) && null;
180
+ });
181
181
  }).catch(err => {
182
182
  log.error({
183
183
  err,
184
184
  cursor
185
185
  }, 'failed to retrieve cards');
186
186
 
187
- return res.status(500).json({ error : true }) && null;
187
+ res.status(500).json({ error : true });
188
188
  });
189
189
  });
190
190
 
@@ -206,7 +206,7 @@ export default async function BoardApiRoutes(
206
206
  };
207
207
  }),
208
208
  name: name || 'Wuffle Board'
209
- }) && null;
209
+ });
210
210
 
211
211
  });
212
212
 
@@ -216,18 +216,16 @@ export default async function BoardApiRoutes(
216
216
 
217
217
  const updates = cursor ? store.getUpdates(cursor) : [];
218
218
 
219
- return (
220
- filterUpdates(req, updates).then(filteredUpdates => {
221
- res.type('json').json(filteredUpdates);
222
- }).catch(err => {
223
- log.error({
224
- err,
225
- cursor
226
- }, 'failed to retrieve card updates');
227
-
228
- res.status(500).json({ error : true });
229
- })
230
- );
219
+ return filterUpdates(req, updates).then(filteredUpdates => {
220
+ res.type('json').json(filteredUpdates);
221
+ }).catch(err => {
222
+ log.error({
223
+ err,
224
+ cursor
225
+ }, 'failed to retrieve card updates');
226
+
227
+ res.status(500).json({ error : true });
228
+ });
231
229
  });
232
230
 
233
231
 
@@ -288,7 +286,7 @@ export default async function BoardApiRoutes(
288
286
  }
289
287
  };
290
288
 
291
- moveIssue(context, issue, column, { before, after }).then(() => {
289
+ return moveIssue(context, issue, column, { before, after }).then(() => {
292
290
  res.type('json').json({});
293
291
  }).catch(err => {
294
292
  log.error(err, 'failed to move issue');
@@ -227,12 +227,29 @@ export default function EventsSync(webhookEvents, store, logger) {
227
227
  });
228
228
 
229
229
 
230
- // issues ///////////////////////////////
230
+ // sub-issues /////////////////////
231
231
 
232
- // https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#issues
233
232
 
234
- // issue transfer is mapped to the following GitHub events
233
+ // https://docs.github.com/en/webhooks/webhook-events-and-payloads#sub_issues
234
+ //
235
+ // update sub-issues on change - updating parent <> child relationship
236
+ webhookEvents.on(
237
+ /** @type {any} */ ([ 'sub_issues.sub_issue_added', 'sub_issues.sub_issue_removed' ]),
238
+ async ({ payload }) => {
239
+
240
+ const {
241
+ sub_issue,
242
+ repository
243
+ } = /** @type {any} */ (payload);
244
+
245
+ return store.updateIssue(filterIssue(sub_issue, repository));
246
+ }
247
+ );
248
+
249
+
250
+ // https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#issues
235
251
  //
252
+ // issue transfer is mapped to the following GitHub events:
236
253
  // -> issues.opened (new issue is being opened by GitHub)
237
254
  // -> issues.transferred (old issue was deleted by GitHub)
238
255
  //
@@ -19,11 +19,13 @@ const RequiredEvents = [
19
19
  'issues',
20
20
  'issue_comment',
21
21
  'label',
22
+ 'member',
22
23
  'milestone',
23
24
  'pull_request',
24
25
  'pull_request_review',
25
26
  'repository',
26
- 'status'
27
+ 'status',
28
+ 'sub_issues'
27
29
  ];
28
30
 
29
31
  /**
@@ -41,8 +43,9 @@ const RequiredEvents = [
41
43
  * @param {import('../../types.js').ProbotApp} app
42
44
  * @param {import('../../types.js').Logger} logger
43
45
  * @param {import('../../types.js').Injector} injector
46
+ * @param {import('../../events.js').default} events
44
47
  */
45
- export default function GithubApp(config, app, logger, injector) {
48
+ export default function GithubApp(config, app, logger, injector, events) {
46
49
 
47
50
  const log = logger.child({
48
51
  name: 'wuffle:github-app'
@@ -266,6 +269,31 @@ export default function GithubApp(config, app, logger, injector) {
266
269
  log.debug('validated installations');
267
270
  }
268
271
 
272
+ async function validateApp() {
273
+ const octokit = await getAppScopedClient();
274
+ const { data: app } = await octokit.rest.apps.getAuthenticated();
275
+
276
+ // app may not be configured yet
277
+ if (!app) {
278
+ return;
279
+ }
280
+
281
+ const missingEvents = RequiredEvents.filter(
282
+ event => !app.events.includes(event)
283
+ );
284
+
285
+ if (missingEvents.length) {
286
+ log.error({
287
+ missingEvents,
288
+ events: app.events
289
+ }, 'app is missing required event subscriptions; update app settings on GitHub');
290
+ }
291
+ }
292
+
293
+ events.once('wuffle.start', async function() {
294
+ await validateApp().catch(err => log.warn({ err }, 'failed to validate app configuration'));
295
+ });
296
+
269
297
  /**
270
298
  * Fetch active installations.
271
299
  *
package/lib/filters.js CHANGED
@@ -214,7 +214,8 @@ export function filterIssue(githubIssue, githubRepository) {
214
214
  milestone,
215
215
  pull_request,
216
216
  html_url,
217
- author_association
217
+ author_association,
218
+ parent_issue_url
218
219
  } = githubIssue;
219
220
 
220
221
  // stable ID that is independent from GitHubs internal issue/pr distinction
@@ -245,7 +246,8 @@ export function filterIssue(githubIssue, githubRepository) {
245
246
  repository: filterRepository(githubRepository),
246
247
  pull_request: !!pull_request,
247
248
  html_url,
248
- author_association
249
+ author_association,
250
+ parent_issue_url: parent_issue_url || null
249
251
  };
250
252
 
251
253
  }
package/lib/util/links.js CHANGED
@@ -145,6 +145,22 @@ export function findLinks(issue, types) {
145
145
  links.push(link);
146
146
  }
147
147
 
148
+ // add CHILD_OF link from parent_issue_url (GitHub sub-issues)
149
+ const { parent_issue_url } = issue;
150
+
151
+ if (parent_issue_url) {
152
+ const parentMatch = parent_issue_url.match(/\/repos\/([^/]+)\/([^/]+)\/issues\/(\d+)/);
153
+
154
+ if (parentMatch) {
155
+ links.push({
156
+ type: CHILD_OF,
157
+ owner: parentMatch[1],
158
+ repo: parentMatch[2],
159
+ number: parseInt(parentMatch[3], 10)
160
+ });
161
+ }
162
+ }
163
+
148
164
  if (typeof types !== 'undefined') {
149
165
  return filterLinks(links, types);
150
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wuffle",
3
- "version": "0.75.0",
3
+ "version": "0.76.0",
4
4
  "description": "A multi-repository task board for GitHub issues",
5
5
  "author": {
6
6
  "name": "Nico Rehwaldt",
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@aws-sdk/client-s3": "^3.1037.0",
46
46
  "async-didi": "^1.0.0",
47
- "body-parser": "^2.2.2",
47
+ "body-parser": "^2.3.0",
48
48
  "compression": "^1.8.1",
49
49
  "express-session": "^1.19.0",
50
50
  "fake-tag": "^5.0.0",
@@ -63,11 +63,11 @@
63
63
  "@types/mocha": "^10.0.10",
64
64
  "@types/sinon": "^21.0.1",
65
65
  "chai": "^6.2.2",
66
- "graphql": "^16.14.0",
67
- "mocha": "^11.7.5",
68
- "nock": "^14.0.13",
66
+ "graphql": "^17.0.0",
67
+ "mocha": "^11.7.6",
68
+ "nock": "^14.0.15",
69
69
  "nodemon": "^3.1.14",
70
- "npm-run-all2": "^8.0.4",
70
+ "npm-run-all2": "^9.0.2",
71
71
  "sinon": "^22.0.0",
72
72
  "sinon-chai": "^4.0.0",
73
73
  "typescript": "^5.9.3"
@@ -83,5 +83,5 @@
83
83
  "index.js",
84
84
  "wuffle.config.example.js"
85
85
  ],
86
- "gitHead": "132f68f8372f71a15c4e34b0fd9db837ccc02b6b"
86
+ "gitHead": "3a21cf84280395b89d369422bbaf16f2b63c20dd"
87
87
  }