wuffle 0.53.1 → 0.54.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/README.md +2 -2
- package/lib/apps/automatic-dev-flow.js +12 -2
- package/lib/apps/board-api-routes/board-api-routes.js +4 -8
- package/lib/apps/search/Search.js +67 -43
- package/lib/util/search.js +14 -2
- package/package.json +4 -4
- package/public/bundle.js +1 -1
- package/public/bundle.js.map +1 -1
- package/public/global.css +1 -1
- package/wuffle.config.example.js +7 -1
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@ Refer to the [project documentation](https://github.com/nikku/wuffle#readme) for
|
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
|
-
Run your own [Wuffle
|
|
16
|
+
Run your own [Wuffle](https://github.com/nikku/wuffle) instance via `npx`:
|
|
17
17
|
|
|
18
|
-
```
|
|
18
|
+
```sh
|
|
19
19
|
npx wuffle
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -49,12 +49,22 @@ module.exports = function(webhookEvents, githubIssues, columns) {
|
|
|
49
49
|
]);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
webhookEvents.on(
|
|
52
|
+
webhookEvents.on([
|
|
53
|
+
'pull_request.ready_for_review',
|
|
54
|
+
'pull_request.review_requested'
|
|
55
|
+
], async (context) => {
|
|
53
56
|
|
|
54
57
|
const {
|
|
55
|
-
pull_request
|
|
58
|
+
pull_request,
|
|
59
|
+
action
|
|
56
60
|
} = context.payload;
|
|
57
61
|
|
|
62
|
+
// do not forcefully move draft PRs into review,
|
|
63
|
+
// these shall be marked as _ready for review_ first
|
|
64
|
+
if (action === 'review_requested' && pull_request.draft) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
const state = isExternal(pull_request) ? EXTERNAL_CONTRIBUTION : IN_REVIEW;
|
|
59
69
|
|
|
60
70
|
const column = columns.getByState(state);
|
|
@@ -62,11 +62,7 @@ module.exports = async (
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function getIssueSearchFilter(req) {
|
|
65
|
-
const s = req.query.s;
|
|
66
|
-
|
|
67
|
-
if (!s) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
65
|
+
const s = req.query.s || null;
|
|
70
66
|
|
|
71
67
|
const user = authRoutes.getGitHubUser(req);
|
|
72
68
|
|
|
@@ -99,7 +95,7 @@ module.exports = async (
|
|
|
99
95
|
};
|
|
100
96
|
});
|
|
101
97
|
|
|
102
|
-
const searchFiltered =
|
|
98
|
+
const searchFiltered = accessFiltered.filter(searchFilter);
|
|
103
99
|
|
|
104
100
|
filteredItems[columnKey] = searchFiltered.map(filterIssueOrPull);
|
|
105
101
|
|
|
@@ -134,7 +130,7 @@ module.exports = async (
|
|
|
134
130
|
};
|
|
135
131
|
});
|
|
136
132
|
|
|
137
|
-
const searchFiltered =
|
|
133
|
+
const searchFiltered = accessFiltered.map(update => {
|
|
138
134
|
|
|
139
135
|
if (searchFilter(update.issue)) {
|
|
140
136
|
return update;
|
|
@@ -146,7 +142,7 @@ module.exports = async (
|
|
|
146
142
|
...update,
|
|
147
143
|
type: 'remove'
|
|
148
144
|
};
|
|
149
|
-
})
|
|
145
|
+
});
|
|
150
146
|
|
|
151
147
|
return searchFiltered.map(update => {
|
|
152
148
|
|
|
@@ -16,16 +16,20 @@ const CHILD_LINK_TYPES = {
|
|
|
16
16
|
[ LinkTypes.CLOSES ]: true
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @typedef { { defaultFilter?: string } } SearchConfig
|
|
21
|
+
*/
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* This app allows you to create a search filter from a given term.
|
|
22
25
|
*
|
|
23
26
|
* @constructor
|
|
24
27
|
*
|
|
28
|
+
* @param {SearchConfig} config
|
|
25
29
|
* @param {import('../../types').Logger} logger
|
|
26
30
|
* @param {import('../../store')} store
|
|
27
31
|
*/
|
|
28
|
-
function Search(logger, store) {
|
|
32
|
+
function Search(config, logger, store) {
|
|
29
33
|
|
|
30
34
|
const log = logger.child({
|
|
31
35
|
name: 'wuffle:search'
|
|
@@ -47,19 +51,25 @@ function Search(logger, store) {
|
|
|
47
51
|
return filterReject;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
/**
|
|
55
|
+
* @param { string } actual
|
|
56
|
+
* @param { string } pattern
|
|
57
|
+
* @param { boolean } [exact=false]
|
|
58
|
+
*
|
|
59
|
+
* @return { boolean }
|
|
60
|
+
*/
|
|
61
|
+
function includes(actual, pattern, exact) {
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
if (exact) {
|
|
64
|
+
return pattern && actual === pattern;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return pattern && actual.toLowerCase().includes(pattern.toLowerCase());
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
const filters = {
|
|
59
71
|
|
|
60
|
-
text: function textFilter(text) {
|
|
61
|
-
|
|
62
|
-
text = text.toLowerCase();
|
|
72
|
+
text: function textFilter(text, exact) {
|
|
63
73
|
|
|
64
74
|
return function filterText(issue) {
|
|
65
75
|
const issueText = `#${issue.number} ${issue.title}\n\n${issue.body}`;
|
|
@@ -129,26 +139,26 @@ function Search(logger, store) {
|
|
|
129
139
|
}
|
|
130
140
|
},
|
|
131
141
|
|
|
132
|
-
label: function labelFilter(name) {
|
|
142
|
+
label: function labelFilter(name, exact) {
|
|
133
143
|
return function filterLabel(issue) {
|
|
134
144
|
|
|
135
145
|
const { labels } = issue;
|
|
136
146
|
|
|
137
|
-
return (labels || []).some(label => includes(label.name, name));
|
|
147
|
+
return (labels || []).some(label => includes(label.name, name, exact));
|
|
138
148
|
};
|
|
139
149
|
},
|
|
140
150
|
|
|
141
|
-
repo: function repoFilter(name) {
|
|
151
|
+
repo: function repoFilter(name, exact) {
|
|
142
152
|
|
|
143
153
|
return function filterRepoAndOwner(issue) {
|
|
144
154
|
|
|
145
155
|
const { repository } = issue;
|
|
146
156
|
|
|
147
|
-
return includes(`${repository.owner.login}/${repository.name}`, name);
|
|
157
|
+
return includes(`${repository.owner.login}/${repository.name}`, name, exact);
|
|
148
158
|
};
|
|
149
159
|
},
|
|
150
160
|
|
|
151
|
-
milestone: function milestoneFilter(name) {
|
|
161
|
+
milestone: function milestoneFilter(name, exact) {
|
|
152
162
|
|
|
153
163
|
return function filterMilestone(issue) {
|
|
154
164
|
|
|
@@ -156,11 +166,11 @@ function Search(logger, store) {
|
|
|
156
166
|
milestone
|
|
157
167
|
} = issue;
|
|
158
168
|
|
|
159
|
-
return milestone &&
|
|
169
|
+
return milestone && includes(milestone.title, name, exact);
|
|
160
170
|
};
|
|
161
171
|
},
|
|
162
172
|
|
|
163
|
-
author: function authorFilter(name) {
|
|
173
|
+
author: function authorFilter(name, exact) {
|
|
164
174
|
|
|
165
175
|
return function filterAuthor(issue) {
|
|
166
176
|
|
|
@@ -168,11 +178,11 @@ function Search(logger, store) {
|
|
|
168
178
|
user
|
|
169
179
|
} = issue;
|
|
170
180
|
|
|
171
|
-
return user &&
|
|
181
|
+
return user && includes(user.login, name, exact);
|
|
172
182
|
};
|
|
173
183
|
},
|
|
174
184
|
|
|
175
|
-
assignee: function assigneeFilter(name) {
|
|
185
|
+
assignee: function assigneeFilter(name, exact) {
|
|
176
186
|
|
|
177
187
|
return function filterAssignee(issue) {
|
|
178
188
|
|
|
@@ -180,11 +190,11 @@ function Search(logger, store) {
|
|
|
180
190
|
assignees
|
|
181
191
|
} = issue;
|
|
182
192
|
|
|
183
|
-
return (assignees || []).some(assignee =>
|
|
193
|
+
return (assignees || []).some(assignee => includes(assignee.login, name, exact));
|
|
184
194
|
};
|
|
185
195
|
},
|
|
186
196
|
|
|
187
|
-
reviewer: function reviewerFilter(name) {
|
|
197
|
+
reviewer: function reviewerFilter(name, exact) {
|
|
188
198
|
|
|
189
199
|
return function filterReviewer(issue) {
|
|
190
200
|
|
|
@@ -199,14 +209,14 @@ function Search(logger, store) {
|
|
|
199
209
|
}
|
|
200
210
|
|
|
201
211
|
return (
|
|
202
|
-
requested_reviewers.some(reviewer =>
|
|
212
|
+
requested_reviewers.some(reviewer => includes(reviewer.login, name, exact))
|
|
203
213
|
) || (
|
|
204
|
-
(reviews || []).some(review =>
|
|
214
|
+
(reviews || []).some(review => includes(review.user.login, name, exact))
|
|
205
215
|
);
|
|
206
216
|
};
|
|
207
217
|
},
|
|
208
218
|
|
|
209
|
-
commented: function commentedFilter(name) {
|
|
219
|
+
commented: function commentedFilter(name, exact) {
|
|
210
220
|
|
|
211
221
|
return function filterCommented(issue) {
|
|
212
222
|
|
|
@@ -220,17 +230,17 @@ function Search(logger, store) {
|
|
|
220
230
|
}
|
|
221
231
|
|
|
222
232
|
return (
|
|
223
|
-
comments.some(comment =>
|
|
233
|
+
comments.some(comment => includes(comment.user.login, name))
|
|
224
234
|
);
|
|
225
235
|
};
|
|
226
236
|
},
|
|
227
237
|
|
|
228
|
-
involves: function involvesFilter(name) {
|
|
238
|
+
involves: function involvesFilter(name, exact) {
|
|
229
239
|
|
|
230
|
-
const isAssigned = filters.assignee(name);
|
|
231
|
-
const isAuthor = filters.author(name);
|
|
232
|
-
const isReviewer = filters.reviewer(name);
|
|
233
|
-
const hasCommented = filters.commented(name);
|
|
240
|
+
const isAssigned = filters.assignee(name, exact);
|
|
241
|
+
const isAuthor = filters.author(name, exact);
|
|
242
|
+
const isReviewer = filters.reviewer(name, exact);
|
|
243
|
+
const hasCommented = filters.commented(name, exact);
|
|
234
244
|
|
|
235
245
|
return function filterInvolves(issue) {
|
|
236
246
|
return (
|
|
@@ -289,23 +299,16 @@ function Search(logger, store) {
|
|
|
289
299
|
};
|
|
290
300
|
}
|
|
291
301
|
|
|
292
|
-
|
|
293
|
-
* Retrieve a filter function from the given search string.
|
|
294
|
-
*
|
|
295
|
-
* @param {string} search
|
|
296
|
-
* @param {import('../../types').GitHubUser} [user]
|
|
297
|
-
*
|
|
298
|
-
* @return {Function}
|
|
299
|
-
*/
|
|
300
|
-
function getSearchFilter(search, user) {
|
|
302
|
+
function buildFilterFns(search, user) {
|
|
301
303
|
|
|
302
|
-
const terms = parseSearch(search);
|
|
304
|
+
const terms = search ? parseSearch(search) : [];
|
|
303
305
|
|
|
304
|
-
|
|
306
|
+
return terms.map(term => {
|
|
305
307
|
let {
|
|
306
308
|
qualifier,
|
|
307
309
|
value,
|
|
308
|
-
negated
|
|
310
|
+
negated,
|
|
311
|
+
exact
|
|
309
312
|
} = term;
|
|
310
313
|
|
|
311
314
|
if (!value) {
|
|
@@ -318,6 +321,7 @@ function Search(logger, store) {
|
|
|
318
321
|
}
|
|
319
322
|
|
|
320
323
|
value = user.login;
|
|
324
|
+
exact = true;
|
|
321
325
|
}
|
|
322
326
|
|
|
323
327
|
const factoryFn = filters[qualifier];
|
|
@@ -326,7 +330,7 @@ function Search(logger, store) {
|
|
|
326
330
|
return noopFilter();
|
|
327
331
|
}
|
|
328
332
|
|
|
329
|
-
const fn = factoryFn(value);
|
|
333
|
+
const fn = factoryFn(value, exact);
|
|
330
334
|
|
|
331
335
|
if (negated) {
|
|
332
336
|
return function(arg) {
|
|
@@ -336,10 +340,30 @@ function Search(logger, store) {
|
|
|
336
340
|
|
|
337
341
|
return fn;
|
|
338
342
|
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Retrieve a filter function from the given search string.
|
|
347
|
+
*
|
|
348
|
+
* @param {string} search
|
|
349
|
+
* @param {import('../../types').GitHubUser} [user]
|
|
350
|
+
*
|
|
351
|
+
* @return {Function}
|
|
352
|
+
*/
|
|
353
|
+
function getSearchFilter(search, user) {
|
|
354
|
+
|
|
355
|
+
const filterFns = buildFilterFns(search, user);
|
|
356
|
+
|
|
357
|
+
const ignoreFilterFns = buildFilterFns(config.defaultFilter, user);
|
|
339
358
|
|
|
340
359
|
return function(issue) {
|
|
341
360
|
try {
|
|
342
|
-
|
|
361
|
+
if (filterFns.length) {
|
|
362
|
+
return filterFns.every(fn => fn(issue));
|
|
363
|
+
} else {
|
|
364
|
+
|
|
365
|
+
return ignoreFilterFns.every(fn => fn(issue));
|
|
366
|
+
}
|
|
343
367
|
} catch (err) {
|
|
344
368
|
log.warn({ issue: issueIdent(issue), err }, 'filter failed');
|
|
345
369
|
|
package/lib/util/search.js
CHANGED
|
@@ -57,6 +57,16 @@ function startOfDay(time) {
|
|
|
57
57
|
return date.getTime();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} str
|
|
62
|
+
*
|
|
63
|
+
* @return { {
|
|
64
|
+
* qualifier: string,
|
|
65
|
+
* value: string|undefined,
|
|
66
|
+
* exact: boolean,
|
|
67
|
+
* negated?: boolean
|
|
68
|
+
* }[] }
|
|
69
|
+
*/
|
|
60
70
|
function parseSearch(str) {
|
|
61
71
|
|
|
62
72
|
const regexp = /(?:([\w#/&]+)|"([\w#/&\s-.]+)"|([-!]?)([\w]+):(?:([\w-#/&@<>=.]+)|"([\w-#/&@:.,; ]+)")?)(?:\s|$)/g;
|
|
@@ -83,7 +93,8 @@ function parseSearch(str) {
|
|
|
83
93
|
if (textValue) {
|
|
84
94
|
terms.push({
|
|
85
95
|
qualifier: 'text',
|
|
86
|
-
value: textValue
|
|
96
|
+
value: textValue,
|
|
97
|
+
exact: !!textEscaped
|
|
87
98
|
});
|
|
88
99
|
}
|
|
89
100
|
|
|
@@ -93,7 +104,8 @@ function parseSearch(str) {
|
|
|
93
104
|
terms.push({
|
|
94
105
|
qualifier,
|
|
95
106
|
value: qualifierValue,
|
|
96
|
-
negated: !!negated
|
|
107
|
+
negated: !!negated,
|
|
108
|
+
exact: !!qualifierTextEscaped
|
|
97
109
|
});
|
|
98
110
|
}
|
|
99
111
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wuffle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"description": "A multi-repository task board for GitHub issues",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Nico Rehwaldt",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"smee-client": "^1.2.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@graphql-eslint/eslint-plugin": "^3.
|
|
54
|
+
"@graphql-eslint/eslint-plugin": "^3.20.1",
|
|
55
55
|
"@octokit/graphql-schema": "^12.10.0",
|
|
56
56
|
"@types/compression": "^1.7.2",
|
|
57
57
|
"@types/express-session": "^1.17.4",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"npm-run-all": "^4.1.5",
|
|
65
65
|
"sinon": "^12.0.1",
|
|
66
66
|
"sinon-chai": "^3.7.0",
|
|
67
|
-
"typescript": "^
|
|
67
|
+
"typescript": "^5.1.6"
|
|
68
68
|
},
|
|
69
69
|
"engines": {
|
|
70
70
|
"node": "14.x || 16.x"
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"index.js",
|
|
93
93
|
"wuffle.config.example.js"
|
|
94
94
|
],
|
|
95
|
-
"gitHead": "
|
|
95
|
+
"gitHead": "466ef6ec95b811c8f62a55fb7c9e5b6928d866a8"
|
|
96
96
|
}
|