Skip to content

Commit 14caa46

Browse files
authored
feat: add support for objects in arrays in the payload filter (#589)
* feat: add support for objects in arrays in the payload filter * chore: restore old package lock file * chore: add changelog entry and usage documentation * chore: throw error if key required but not provided
1 parent bb5034a commit 14caa46

File tree

5 files changed

+219
-8
lines changed

5 files changed

+219
-8
lines changed

__tests__/unit/filters/payload.test.js

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ test('that checks are done against correct field', async () => {
4949
expect(filter.status).toBe('fail')
5050
})
5151

52-
test('that proper errors are returned if the field does not exit', async () => {
52+
test('that proper errors are returned if the field does not exist', async () => {
5353
const payload = new Payload()
5454

5555
const settings = {
@@ -77,3 +77,173 @@ test('that proper errors are returned if the field does not exit', async () => {
7777
const filter = await payload.processFilter(context, settings)
7878
expect(filter.status).toBe('error')
7979
})
80+
81+
test('that checks work against objects in an array with must_include', async () => {
82+
const payload = new Payload()
83+
84+
const settings = {
85+
do: 'payload',
86+
pull_request: {
87+
labels: {
88+
must_include: {
89+
regex: 'foo',
90+
key: 'name'
91+
}
92+
}
93+
}
94+
}
95+
96+
let context = {
97+
payload: {
98+
repository: {
99+
full_name: 'test-repo'
100+
},
101+
pull_request: {
102+
labels: [
103+
{
104+
name: 'foo'
105+
}
106+
]
107+
}
108+
}
109+
}
110+
111+
let filter = await payload.processFilter(context, settings)
112+
expect(filter.status).toBe('pass')
113+
114+
context = {
115+
payload: {
116+
repository: {
117+
full_name: 'test-repo'
118+
},
119+
pull_request: {
120+
labels: [
121+
{
122+
name: 'bar'
123+
}
124+
]
125+
}
126+
}
127+
}
128+
129+
filter = await payload.processFilter(context, settings)
130+
expect(filter.status).toBe('fail')
131+
})
132+
133+
test('that checks work against objects in an array with must_exclude', async () => {
134+
const payload = new Payload()
135+
136+
const settings = {
137+
do: 'payload',
138+
pull_request: {
139+
labels: {
140+
must_exclude: {
141+
regex: 'foo',
142+
key: 'name'
143+
}
144+
}
145+
}
146+
}
147+
148+
let context = {
149+
payload: {
150+
repository: {
151+
full_name: 'test-repo'
152+
},
153+
pull_request: {
154+
labels: [
155+
{
156+
name: 'bar'
157+
}
158+
]
159+
}
160+
}
161+
}
162+
163+
let filter = await payload.processFilter(context, settings)
164+
expect(filter.status).toBe('pass')
165+
166+
context = {
167+
payload: {
168+
repository: {
169+
full_name: 'test-repo'
170+
},
171+
pull_request: {
172+
labels: [
173+
{
174+
name: 'foo'
175+
}
176+
]
177+
}
178+
}
179+
}
180+
181+
filter = await payload.processFilter(context, settings)
182+
expect(filter.status).toBe('fail')
183+
})
184+
185+
test('that proper errors are returned if a key is required but not provided in must_include', async () => {
186+
const payload = new Payload()
187+
188+
const settings = {
189+
do: 'payload',
190+
pull_request: {
191+
labels: {
192+
must_include: {
193+
regex: 'foo'
194+
}
195+
}
196+
}
197+
}
198+
199+
const context = {
200+
payload: {
201+
repository: {
202+
full_name: 'test-repo'
203+
},
204+
pull_request: {
205+
labels: [
206+
{
207+
name: 'foo'
208+
}
209+
]
210+
}
211+
}
212+
}
213+
214+
const filter = await payload.processFilter(context, settings)
215+
expect(filter.status).toBe('error')
216+
})
217+
218+
test('that proper errors are returned if a key is required but not provided in must_exclude', async () => {
219+
const payload = new Payload()
220+
221+
const settings = {
222+
do: 'payload',
223+
pull_request: {
224+
labels: {
225+
must_exclude: {
226+
regex: 'foo'
227+
}
228+
}
229+
}
230+
}
231+
232+
const context = {
233+
payload: {
234+
repository: {
235+
full_name: 'test-repo'
236+
},
237+
pull_request: {
238+
labels: [
239+
{
240+
name: 'bar'
241+
}
242+
]
243+
}
244+
}
245+
}
246+
247+
const filter = await payload.processFilter(context, settings)
248+
expect(filter.status).toBe('error')
249+
})

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
CHANGELOG
22
=====================================
3+
| September 25, 2021: feat: Support for objects in arrays in the payload filter `#589 <https://github.com/mergeability/mergeable/pull/589>`_
34
| September 20, 2021 : fix: Check that getRouter is defined before attempting to access it `#587 <https://github.com/mergeability/mergeable/pull/587>`_
45
| August 26, 2021 : fix: Await GithubAPI topics response `#585 <https://github.com/mergeability/mergeable/pull/585>`_
56
| August 10, 2021 : feat: New labels API `#577 <https://github.com/mergeability/mergeable/pull/577>`_

docs/filters/payload.rst

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Payload
22
^^^^^^^^^^^^^^
33

4-
Check against any available fields within the payload, each event can have different field, please refer to `github API documentation for<https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads>`_ available fields.
4+
Check against any available fields within the payload, each event can have different field, please refer to `github API documentation for <https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads>`_ for available fields.
55

66
An example to check if a pull_request_review event has `state` of `changes_requested`
77

8-
.. codeblock:: yml
8+
::
99

1010
- do: payload
1111
review:
@@ -14,16 +14,30 @@ An example to check if a pull_request_review event has `state` of `changes_reque
1414
regex: 'changes_requested'
1515

1616

17+
An example to check if a pull_request event has a `label` named `foo`
18+
19+
::
20+
21+
- do: payload
22+
pull_request:
23+
labels:
24+
must_include:
25+
regex: 'foo'
26+
key: 'name'
27+
28+
1729
Each field must be checked using one of the following options
1830

19-
.. codeblock:: yml
31+
::
2032

2133
must_include:
2234
regex: 'This text must be included'
2335
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
36+
key: 'name' # Optional. If checking an array of objects, this specifies the key to check.
2437
must_exclude:
2538
regex: 'Text to exclude'
2639
regex_flag: 'none' # Optional. Specify the flag for Regex. default is 'i', to disable default use 'none'
40+
key: 'name' # Optional. If checking an array of objects, this specifies the key to check.
2741

2842

2943
Supported Events:

lib/validators/options_processor/options/must_exclude.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
const REGEX_NOT_FOUND_ERROR = 'Failed to run the test because \'regex\' is not provided for \'must_exclude\' option. Please check README for more information about configuration'
22
const UNKNOWN_INPUT_TYPE_ERROR = 'Input type invalid, expected either string or array of string as input'
3+
const KEY_NOT_FOUND_ERROR = 'Input type is an object and requires providing a \'key\' for the \'must_exclude\' option.'
34

45
class MustExclude {
56
static process (validatorContext, input, rule) {
67
const filter = rule.must_exclude
78

89
const regex = filter.regex
10+
const key = filter.key
911
let description = filter.message
1012
if (!regex) {
1113
throw new Error(REGEX_NOT_FOUND_ERROR)
@@ -34,9 +36,23 @@ class MustExclude {
3436
return !regexObj.test(input)
3537
} else if (Array.isArray(input)) {
3638
if (filter.all) {
37-
return input.every(label => !regexObj.test(label))
39+
return input.every(label => {
40+
if (typeof label === 'string') {
41+
return !regexObj.test(label)
42+
} else if (key) {
43+
return !regexObj.test(label[key])
44+
}
45+
throw new Error(KEY_NOT_FOUND_ERROR)
46+
})
3847
} else {
39-
return !input.some(label => regexObj.test(label))
48+
return !input.some(label => {
49+
if (typeof label === 'string') {
50+
return regexObj.test(label)
51+
} else if (key) {
52+
return regexObj.test(label[key])
53+
}
54+
throw new Error(KEY_NOT_FOUND_ERROR)
55+
})
4056
}
4157
} else {
4258
throw new Error(UNKNOWN_INPUT_TYPE_ERROR)

lib/validators/options_processor/options/must_include.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
const REGEX_NOT_FOUND_ERROR = 'Failed to run the test because \'regex\' is not provided for \'must_include\' option. Please check README for more information about configuration'
22
const UNKNOWN_INPUT_TYPE_ERROR = 'Input type invalid, expected either string or array of string as input'
3+
const KEY_NOT_FOUND_ERROR = 'Input type is an object and requires providing a \'key\' for the \'must_include\' option.'
34

45
class MustInclude {
56
static process (validatorContext, input, rule) {
67
const filter = rule.must_include
78

89
const regex = filter.regex
10+
const key = filter.key
911
let description = filter.message
1012
if (!regex) {
1113
throw new Error(REGEX_NOT_FOUND_ERROR)
@@ -33,10 +35,18 @@ class MustInclude {
3335
if (typeof input === 'string') {
3436
return regexObj.test(input)
3537
} else if (Array.isArray(input)) {
38+
const arrayHandler = label => {
39+
if (typeof label === 'string') {
40+
return regexObj.test(label)
41+
} else if (key) {
42+
return regexObj.test(label[key])
43+
}
44+
throw new Error(KEY_NOT_FOUND_ERROR)
45+
}
3646
if (filter.all) {
37-
return input.every(label => regexObj.test(label))
47+
return input.every(arrayHandler)
3848
} else {
39-
return input.some(label => regexObj.test(label))
49+
return input.some(arrayHandler)
4050
}
4151
} else {
4252
throw new Error(UNKNOWN_INPUT_TYPE_ERROR)

0 commit comments

Comments
 (0)