wuffle 0.73.1 → 0.73.3

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.
@@ -15,13 +15,21 @@ const CHANGES_REQUESTED = 'changes_requested';
15
15
  * @param {import('./webhook-events/WebhookEvents.js').default} webhookEvents
16
16
  * @param {import('./github-issues/GithubIssues.js').default} githubIssues
17
17
  * @param {import('../columns.js').default} columns
18
+ * @param {import('./issue-filter/IssueFilter.js').default} issueFilter
19
+ * @param {import('../types.js').Logger } logger
18
20
  */
19
- export default function(webhookEvents, githubIssues, columns) {
21
+ export default function(webhookEvents, githubIssues, columns, issueFilter, logger) {
22
+
23
+ const log = logger.child({
24
+ name: 'wuffle:automatic-dev-flow'
25
+ });
26
+
27
+ const ifEnabled = issueFilter.createWebhookFilter(log);
20
28
 
21
29
  webhookEvents.on([
22
30
  'issues.closed',
23
31
  'pull_request.closed'
24
- ], async (context) => {
32
+ ], ifEnabled(async (context) => {
25
33
 
26
34
  const {
27
35
  pull_request,
@@ -31,9 +39,9 @@ export default function(webhookEvents, githubIssues, columns) {
31
39
  const column = columns.getByState(DONE);
32
40
 
33
41
  await githubIssues.moveIssue(context, issue || pull_request, column);
34
- });
42
+ }));
35
43
 
36
- webhookEvents.on('pull_request.converted_to_draft', async (context) => {
44
+ webhookEvents.on('pull_request.converted_to_draft', ifEnabled(async (context) => {
37
45
 
38
46
  const {
39
47
  pull_request
@@ -47,12 +55,12 @@ export default function(webhookEvents, githubIssues, columns) {
47
55
  githubIssues.moveIssue(context, pull_request, column),
48
56
  githubIssues.moveReferencedIssues(context, pull_request, column)
49
57
  ]);
50
- });
58
+ }));
51
59
 
52
60
  webhookEvents.on([
53
61
  'pull_request.ready_for_review',
54
62
  'pull_request.review_requested'
55
- ], async (context) => {
63
+ ], ifEnabled(async (context) => {
56
64
 
57
65
  const {
58
66
  pull_request,
@@ -73,12 +81,12 @@ export default function(webhookEvents, githubIssues, columns) {
73
81
  githubIssues.moveIssue(context, pull_request, column),
74
82
  githubIssues.moveReferencedIssues(context, pull_request, column)
75
83
  ]);
76
- });
84
+ }));
77
85
 
78
86
  webhookEvents.on([
79
87
  'pull_request.opened',
80
88
  'pull_request.reopened'
81
- ], async (context) => {
89
+ ], ifEnabled(async (context) => {
82
90
 
83
91
  const {
84
92
  pull_request
@@ -105,9 +113,9 @@ export default function(webhookEvents, githubIssues, columns) {
105
113
  githubIssues.moveIssue(context, pull_request, column, newAssignee),
106
114
  githubIssues.moveReferencedIssues(context, pull_request, column, newAssignee)
107
115
  ]);
108
- });
116
+ }));
109
117
 
110
- webhookEvents.on('pull_request.edited', async (context) => {
118
+ webhookEvents.on('pull_request.edited', ifEnabled(async (context) => {
111
119
 
112
120
  const {
113
121
  pull_request
@@ -116,9 +124,9 @@ export default function(webhookEvents, githubIssues, columns) {
116
124
  const column = columns.getIssueColumn(pull_request);
117
125
 
118
126
  await githubIssues.moveReferencedIssues(context, pull_request, column);
119
- });
127
+ }));
120
128
 
121
- webhookEvents.on('pull_request_review.submitted', async (context) => {
129
+ webhookEvents.on('pull_request_review.submitted', ifEnabled(async (context) => {
122
130
 
123
131
  const {
124
132
  pull_request,
@@ -141,7 +149,7 @@ export default function(webhookEvents, githubIssues, columns) {
141
149
  githubIssues.moveIssue(context, pull_request, column),
142
150
  githubIssues.moveReferencedIssues(context, pull_request, column)
143
151
  ]);
144
- });
152
+ }));
145
153
 
146
154
  webhookEvents.on('create', async (context) => {
147
155
 
@@ -217,25 +217,11 @@ We automatically synchronize all repositories you granted us access to via the G
217
217
 
218
218
  const update = filterIssueOrPull(issueOrPull, repository);
219
219
 
220
- const {
221
- id
222
- } = update;
220
+ foundIssues[update.id] = update;
223
221
 
224
- const existingIssue = await store.getIssueById(id);
225
-
226
- if (existingIssue && existingIssue.updated_at >= update.updated_at) {
227
- foundIssues[id] = null;
228
-
229
- log.debug({
230
- [type]: `${owner}/${repo}#${issueOrPull.number}`
231
- }, 'skipping, as up-to-date');
232
- } else {
233
- foundIssues[id] = update;
234
-
235
- log.debug({
236
- [type]: `${owner}/${repo}#${issueOrPull.number}`
237
- }, 'scheduled for update');
238
- }
222
+ log.debug({
223
+ [type]: `${owner}/${repo}#${issueOrPull.number}`
224
+ }, 'scheduled for update');
239
225
  } catch (err) {
240
226
  log.error({
241
227
  err,
@@ -395,12 +381,21 @@ We automatically synchronize all repositories you granted us access to via the G
395
381
 
396
382
  // update changed issues
397
383
 
398
- await Promise.all(pendingUpdates.map(update => store.updateIssue(update)));
384
+ const updateTasks = pendingUpdates.map(
385
+ update => store.updateIssue(update)
386
+ );
387
+
388
+ // returns the update for updated issues or undefined
389
+ // where issues were not stored (filtered out) - we only filter
390
+ // for actual updated issues
391
+ const updatedIssues = await Promise.all(updateTasks).then(updates => {
392
+ return updates.filter(update => update);
393
+ });
399
394
 
400
- // emit background sync event for all found issues
395
+ // emit background sync event for all updated issues
401
396
 
402
397
  await syncDetails(
403
- Object.keys(foundIssues),
398
+ updatedIssues,
404
399
  getSyncClosedDetailsSince(),
405
400
  getSyncOpenDetailsSince()
406
401
  );
@@ -437,10 +432,12 @@ We automatically synchronize all repositories you granted us access to via the G
437
432
  );
438
433
  }
439
434
 
440
- function syncDetails(issueIds, syncClosedSince, syncOpenSince) {
435
+ function syncDetails(updatedIssues, syncClosedSince, syncOpenSince) {
436
+
437
+ const jobs = updatedIssues.map(async updatedIssue => {
441
438
 
442
- const jobs = issueIds.map(async id => {
443
- const issue = await store.getIssueById(id);
439
+ // ensure we fetch latest version of issue (to prevent de-sync)
440
+ const issue = await store.getIssueById(updatedIssue.id);
444
441
 
445
442
  if (!issue) {
446
443
  return;
@@ -12,8 +12,16 @@ import gql from 'fake-tag';
12
12
  * @param {import('../../events.js').default} events
13
13
  * @param {import('../github-client/GithubClient.js').default} githubClient
14
14
  * @param {import('../../store.js').default} store
15
+ * @param {import('../issue-filter/IssueFilter.js').default} issueFilter
16
+ * @param {import('../../types.js').Logger } logger
15
17
  */
16
- export default function GithubComments(webhookEvents, events, githubClient, store) {
18
+ export default function GithubComments(webhookEvents, events, githubClient, store, issueFilter, logger) {
19
+
20
+ const log = logger.child({
21
+ name: 'wuffle:github-comments'
22
+ });
23
+
24
+ const ifEnabled = issueFilter.createWebhookFilter(log);
17
25
 
18
26
  // issues /////////////////////
19
27
 
@@ -108,7 +116,7 @@ export default function GithubComments(webhookEvents, events, githubClient, stor
108
116
 
109
117
  webhookEvents.on([
110
118
  'issue_comment'
111
- ], async ({ payload }) => {
119
+ ], ifEnabled(async ({ payload }) => {
112
120
  const {
113
121
  action,
114
122
  comment: _comment,
@@ -169,7 +177,7 @@ export default function GithubComments(webhookEvents, events, githubClient, stor
169
177
  ...commentedIssue,
170
178
  comments
171
179
  });
172
- });
180
+ }));
173
181
 
174
182
  }
175
183
 
@@ -11,8 +11,16 @@ import { filterUser, filterPull } from '../../filters.js';
11
11
  * @param {import('../../events.js').default} events
12
12
  * @param {import('../github-client/GithubClient.js').default} githubClient
13
13
  * @param {import('../../store.js').default} store
14
+ * @param {import('../issue-filter/IssueFilter.js').default} issueFilter
15
+ * @param {import('../../types.js').Logger } logger
14
16
  */
15
- export default function GithubReviews(webhookEvents, events, githubClient, store) {
17
+ export default function GithubReviews(webhookEvents, events, githubClient, store, issueFilter, logger) {
18
+
19
+ const log = logger.child({
20
+ name: 'wuffle:github-reviews'
21
+ });
22
+
23
+ const ifEnabled = issueFilter.createWebhookFilter(log);
16
24
 
17
25
  // issues /////////////////////
18
26
 
@@ -54,7 +62,7 @@ export default function GithubReviews(webhookEvents, events, githubClient, store
54
62
 
55
63
  webhookEvents.on([
56
64
  'pull_request_review'
57
- ], async ({ payload }) => {
65
+ ], ifEnabled(async ({ payload }) => {
58
66
  const {
59
67
  action,
60
68
  review: _review,
@@ -102,7 +110,7 @@ export default function GithubReviews(webhookEvents, events, githubClient, store
102
110
  ...pull_request,
103
111
  reviews
104
112
  });
105
- });
113
+ }));
106
114
 
107
115
  }
108
116
 
@@ -11,8 +11,16 @@ import { filterPull } from '../../filters.js';
11
11
  * @param {import('../../events.js').default} events
12
12
  * @param {import('../github-client/GithubClient.js').default} githubClient
13
13
  * @param {import('../../store.js').default} store
14
+ * @param {import('../issue-filter/IssueFilter.js').default} issueFilter
15
+ * @param {import('../../types.js').Logger } logger
14
16
  */
15
- export default function GithubStates(webhookEvents, events, githubClient, store) {
17
+ export default function GithubStatuses(webhookEvents, events, githubClient, store, issueFilter, logger) {
18
+
19
+ const log = logger.child({
20
+ name: 'wuffle:github-statuses'
21
+ });
22
+
23
+ const ifEnabled = issueFilter.createWebhookFilter(log);
16
24
 
17
25
  // issues /////////////////////
18
26
 
@@ -89,7 +97,7 @@ export default function GithubStates(webhookEvents, events, githubClient, store)
89
97
  webhookEvents.on([
90
98
  'pull_request.opened',
91
99
  'pull_request.synchronize'
92
- ], async ({ payload }) => {
100
+ ], ifEnabled(async ({ payload }) => {
93
101
 
94
102
  const {
95
103
  pull_request: _pull_request,
@@ -108,7 +116,7 @@ export default function GithubStates(webhookEvents, events, githubClient, store)
108
116
  id,
109
117
  statuses
110
118
  });
111
- });
119
+ }));
112
120
 
113
121
  webhookEvents.on([
114
122
  'status'
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @typedef { { ignoreFilter?: string } } StoreFilterConfig
3
+ */
4
+
5
+ import { filterIssueOrPull } from '../../filters.js';
6
+ import { issueIdent } from '../../util/meta.js';
7
+
8
+ /**
9
+ * An component that configures the store to filter certain elements,
10
+ * effectively making them invisible to the board and its users.
11
+ *
12
+ * @param { StoreFilterConfig } config
13
+ * @param { import('../../store.js').default } store
14
+ * @param { import('../search/Search.js').default } search
15
+ * @param { import('../../types.js').Logger } logger
16
+ */
17
+ export default function IssueFilter(config, store, search, logger) {
18
+
19
+ const log = logger.child({
20
+ name: 'wuffle:issue-filter'
21
+ });
22
+
23
+ /**
24
+ * @type { import('../search/Search.js').FilterFn }
25
+ */
26
+ let isIgnored = (issue) => false;
27
+
28
+
29
+ if ('ignoreFilter' in config) {
30
+ const ignoreFilterFn = search.buildFilterFn(config.ignoreFilter);
31
+
32
+ if (ignoreFilterFn) {
33
+ isIgnored = ignoreFilterFn;
34
+
35
+ store.setIgnoreFilter(isIgnored);
36
+ } else {
37
+ log.warn('unparseable <ignoreFilter> - please correct your board configuration');
38
+ }
39
+ }
40
+
41
+ /**
42
+ * @param {import('../../types.js').Logger } log
43
+ *
44
+ * @return { (filterFn) => (any) => any }
45
+ */
46
+ function createWebhookFilter(log) {
47
+
48
+ return function ifEnabled(webhookHandlerFn) {
49
+
50
+ return (context) => {
51
+
52
+ const payload = context.payload;
53
+
54
+ const issueOrPull = filterIssueOrPull(
55
+ payload.issue || payload.pull_request,
56
+ payload.repository
57
+ );
58
+
59
+ if (isIgnored(issueOrPull)) {
60
+ log.debug({ issue: issueIdent(issueOrPull) }, 'issue matching ignore filter');
61
+
62
+ return;
63
+ }
64
+
65
+ return webhookHandlerFn(context);
66
+ };
67
+ };
68
+ };
69
+
70
+ /**
71
+ * Figure whether the issue shall be ignored (by ignore filter rules)
72
+ *
73
+ * @param {any} issue
74
+ *
75
+ * @return {boolean} true, if issue shall be ignored
76
+ */
77
+ this.isIgnored = isIgnored;
78
+
79
+ /**
80
+ * @param {import('../../types.js').Logger } log
81
+ *
82
+ * @return { (filterFn) => (any) => any }
83
+ */
84
+ this.createWebhookFilter = createWebhookFilter;
85
+ }
@@ -0,0 +1,5 @@
1
+ import IssueFilter from './IssueFilter.js';
2
+
3
+ export default {
4
+ issueFilter: [ 'type', IssueFilter ]
5
+ };
package/lib/index.js CHANGED
@@ -19,6 +19,7 @@ const appModules = [
19
19
  import('./apps/github-checks/index.js'),
20
20
  import('./apps/github-reviews/index.js'),
21
21
  import('./apps/github-statuses/index.js'),
22
+ import('./apps/issue-filter/index.js'),
22
23
  import('./apps/security-context/index.js'),
23
24
  import('./apps/user-access/index.js'),
24
25
  import('./apps/search/index.js'),
@@ -27,8 +28,7 @@ const appModules = [
27
28
  import('./apps/auth-routes/index.js'),
28
29
  import('./apps/board-api-routes/index.js'),
29
30
  import('./apps/board-routes.js'),
30
- import('./apps/reindex-store.js'),
31
- import('./apps/store-filter.js')
31
+ import('./apps/reindex-store.js')
32
32
  ];
33
33
 
34
34
  import loadConfig from './load-config.js';
package/lib/store.js CHANGED
@@ -44,6 +44,8 @@ export default class Store {
44
44
 
45
45
  /**
46
46
  * @type { FilterFn }
47
+ *
48
+ * @private
47
49
  */
48
50
  this.ignoreFilter = (issue) => false;
49
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wuffle",
3
- "version": "0.73.1",
3
+ "version": "0.73.3",
4
4
  "description": "A multi-repository task board for GitHub issues",
5
5
  "author": {
6
6
  "name": "Nico Rehwaldt",
@@ -82,5 +82,5 @@
82
82
  "index.js",
83
83
  "wuffle.config.example.js"
84
84
  ],
85
- "gitHead": "914903ed50d90df9a312a96c47ac66bf002604a9"
85
+ "gitHead": "1d7cdf09524102d300d6230b840959cdd1479e3e"
86
86
  }
@@ -1,31 +0,0 @@
1
- /**
2
- * @typedef { { ignoreFilter?: string } } StoreFilterConfig
3
- */
4
-
5
- /**
6
- * An component that configures the store to filter certain elements,
7
- * effectively making them invisible to the board and its users.
8
- *
9
- * @param { StoreFilterConfig } config
10
- *
11
- * @param { import('../store.js').default } store
12
- * @param { import('./search/Search.js').default } search
13
- * @param { import('../types.js').Logger } logger
14
- */
15
- export default function StoreFilter(config, store, search, logger) {
16
-
17
- const log = logger.child({
18
- name: 'wuffle:store-filter'
19
- });
20
-
21
- if ('ignoreFilter' in config) {
22
- const ignoreFilterFn = search.buildFilterFn(config.ignoreFilter);
23
-
24
- if (ignoreFilterFn) {
25
- store.setIgnoreFilter(ignoreFilterFn);
26
- } else {
27
- log.warn('unparseable <ignoreFilter> - please correct your board configuration');
28
- }
29
- }
30
-
31
- }