X Tutup
Skip to content

Commit a65c125

Browse files
committed
Fix slackapi#80 by making app.action/options more flexible
1 parent ba14b1f commit a65c125

File tree

6 files changed

+287
-34
lines changed

6 files changed

+287
-34
lines changed

samples/dialogs_app.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ def test_command(body, client, ack, logger):
5151
logger.info(res)
5252

5353

54-
@app.action({"type": "dialog_submission", "callback_id": "dialog-callback-id"})
55-
def dialog_submission(ack: Ack, body: dict):
54+
@app.action("dialog-callback-id")
55+
def dialog_submission_or_cancellation(ack: Ack, body: dict):
56+
if body["type"] == "dialog_cancellation":
57+
# This can be sent only when notify_on_cancel is True
58+
ack()
59+
return
60+
5661
errors = []
5762
submission = body["submission"]
5863
if len(submission["loc_origin"]) <= 3:
@@ -69,7 +74,30 @@ def dialog_submission(ack: Ack, body: dict):
6974
ack()
7075

7176

72-
@app.options({"type": "dialog_suggestion", "callback_id": "dialog-callback-id"})
77+
# @app.action({"type": "dialog_submission", "callback_id": "dialog-callback-id"})
78+
# def dialog_submission_or_cancellation(ack: Ack, body: dict):
79+
# errors = []
80+
# submission = body["submission"]
81+
# if len(submission["loc_origin"]) <= 3:
82+
# errors = [
83+
# {
84+
# "name": "loc_origin",
85+
# "error": "Pickup Location must be longer than 3 characters"
86+
# }
87+
# ]
88+
# if len(errors) > 0:
89+
# # or ack({"errors": errors})
90+
# ack(errors=errors)
91+
# else:
92+
# ack()
93+
#
94+
# @app.action({"type": "dialog_cancellation", "callback_id": "dialog-callback-id"})
95+
# def dialog_cancellation(ack):
96+
# ack()
97+
98+
99+
# @app.options({"type": "dialog_suggestion", "callback_id": "dialog-callback-id"})
100+
@app.options("dialog-callback-id")
73101
def dialog_suggestion(ack):
74102
ack(
75103
{
@@ -88,10 +116,6 @@ def dialog_suggestion(ack):
88116
)
89117

90118

91-
@app.action({"type": "dialog_cancellation", "callback_id": "dialog-callback-id"})
92-
def dialog_cancellation(ack):
93-
ack()
94-
95119

96120
if __name__ == "__main__":
97121
app.start(3000)

slack_bolt/listener_matcher/builtins.py

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,18 @@ def action(
179179
asyncio: bool = False,
180180
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
181181
if isinstance(constraints, (str, Pattern)):
182-
return block_action(constraints, asyncio)
182+
183+
def func(body: Dict[str, Any]) -> bool:
184+
return (
185+
_block_action(constraints, body)
186+
or _attachment_action(constraints, body)
187+
or _dialog_submission(constraints, body)
188+
or _dialog_cancellation(constraints, body)
189+
or _workflow_step_edit(constraints, body)
190+
)
191+
192+
return build_listener_matcher(func, asyncio)
193+
183194
elif "type" in constraints:
184195
action_type = constraints["type"]
185196
if action_type == "block_actions":
@@ -206,66 +217,89 @@ def action(
206217
)
207218

208219

220+
def _block_action(
221+
constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
222+
body: Dict[str, Any],
223+
) -> bool:
224+
if is_block_actions(body) is False:
225+
return False
226+
227+
action = to_action(body)
228+
if isinstance(constraints, (str, Pattern)):
229+
action_id = constraints
230+
return _matches(action_id, action["action_id"])
231+
elif isinstance(constraints, dict):
232+
# block_id matching is optional
233+
block_id: Optional[Union[str, Pattern]] = constraints.get("block_id")
234+
block_id_matched = block_id is None or _matches(
235+
block_id, action.get("block_id")
236+
)
237+
action_id_matched = _matches(constraints["action_id"], action["action_id"])
238+
return block_id_matched and action_id_matched
239+
240+
209241
def block_action(
210242
constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
211243
asyncio: bool = False,
212244
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
213245
def func(body: Dict[str, Any]) -> bool:
214-
if is_block_actions(body) is False:
215-
return False
216-
217-
action = to_action(body)
218-
if isinstance(constraints, (str, Pattern)):
219-
action_id = constraints
220-
return _matches(action_id, action["action_id"])
221-
elif isinstance(constraints, dict):
222-
# block_id matching is optional
223-
block_id: Optional[Union[str, Pattern]] = constraints.get("block_id")
224-
block_id_matched = block_id is None or _matches(
225-
block_id, action.get("block_id")
226-
)
227-
action_id_matched = _matches(constraints["action_id"], action["action_id"])
228-
return block_id_matched and action_id_matched
246+
return _block_action(constraints, body)
229247

230248
return build_listener_matcher(func, asyncio)
231249

232250

251+
def _attachment_action(callback_id: Union[str, Pattern], body: Dict[str, Any],) -> bool:
252+
return is_attachment_action(body) and _matches(callback_id, body["callback_id"])
253+
254+
233255
def attachment_action(
234256
callback_id: Union[str, Pattern], asyncio: bool = False,
235257
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
236258
def func(body: Dict[str, Any]) -> bool:
237-
return is_attachment_action(body) and _matches(callback_id, body["callback_id"])
259+
return _attachment_action(callback_id, body)
238260

239261
return build_listener_matcher(func, asyncio)
240262

241263

264+
def _dialog_submission(callback_id: Union[str, Pattern], body: Dict[str, Any],) -> bool:
265+
return is_dialog_submission(body) and _matches(callback_id, body["callback_id"])
266+
267+
242268
def dialog_submission(
243269
callback_id: Union[str, Pattern], asyncio: bool = False,
244270
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
245271
def func(body: Dict[str, Any]) -> bool:
246-
return is_dialog_submission(body) and _matches(callback_id, body["callback_id"])
272+
return _dialog_submission(callback_id, body)
247273

248274
return build_listener_matcher(func, asyncio)
249275

250276

277+
def _dialog_cancellation(
278+
callback_id: Union[str, Pattern], body: Dict[str, Any],
279+
) -> bool:
280+
return is_dialog_cancellation(body) and _matches(callback_id, body["callback_id"])
281+
282+
251283
def dialog_cancellation(
252284
callback_id: Union[str, Pattern], asyncio: bool = False,
253285
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
254286
def func(body: Dict[str, Any]) -> bool:
255-
return is_dialog_cancellation(body) and _matches(
256-
callback_id, body["callback_id"]
257-
)
287+
return _dialog_cancellation(callback_id, body)
258288

259289
return build_listener_matcher(func, asyncio)
260290

261291

292+
def _workflow_step_edit(
293+
callback_id: Union[str, Pattern], body: Dict[str, Any],
294+
) -> bool:
295+
return is_workflow_step_edit(body) and _matches(callback_id, body["callback_id"])
296+
297+
262298
def workflow_step_edit(
263299
callback_id: Union[str, Pattern], asyncio: bool = False,
264300
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
265301
def func(body: Dict[str, Any]) -> bool:
266-
return is_workflow_step_edit(body) and _matches(
267-
callback_id, body["callback_id"]
268-
)
302+
return _workflow_step_edit(callback_id, body)
269303

270304
return build_listener_matcher(func, asyncio)
271305

@@ -322,7 +356,14 @@ def options(
322356
asyncio: bool = False,
323357
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
324358
if isinstance(constraints, (str, Pattern)):
325-
return block_suggestion(constraints, asyncio)
359+
360+
def func(body: Dict[str, Any]) -> bool:
361+
return _block_suggestion(constraints, body) or _dialog_suggestion(
362+
constraints, body
363+
)
364+
365+
return build_listener_matcher(func, asyncio)
366+
326367
if "action_id" in constraints:
327368
return block_suggestion(constraints["action_id"], asyncio)
328369
if "callback_id" in constraints:
@@ -333,20 +374,28 @@ def options(
333374
)
334375

335376

377+
def _block_suggestion(action_id: Union[str, Pattern], body: Dict[str, Any],) -> bool:
378+
return is_block_suggestion(body) and _matches(action_id, body["action_id"])
379+
380+
336381
def block_suggestion(
337382
action_id: Union[str, Pattern], asyncio: bool = False,
338383
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
339384
def func(body: Dict[str, Any]) -> bool:
340-
return is_block_suggestion(body) and _matches(action_id, body["action_id"])
385+
return _block_suggestion(action_id, body)
341386

342387
return build_listener_matcher(func, asyncio)
343388

344389

390+
def _dialog_suggestion(callback_id: Union[str, Pattern], body: Dict[str, Any],) -> bool:
391+
return is_dialog_suggestion(body) and _matches(callback_id, body["callback_id"])
392+
393+
345394
def dialog_suggestion(
346395
callback_id: Union[str, Pattern], asyncio: bool = False,
347396
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
348397
def func(body: Dict[str, Any]) -> bool:
349-
return is_dialog_suggestion(body) and _matches(callback_id, body["callback_id"])
398+
return _dialog_suggestion(callback_id, body)
350399

351400
return build_listener_matcher(func, asyncio)
352401

tests/scenario_tests/test_attachment_actions.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ def test_mock_server_is_running(self):
5151
resp = self.web_client.api_test()
5252
assert resp != None
5353

54+
def test_success_without_type(self):
55+
app = App(client=self.web_client, signing_secret=self.signing_secret,)
56+
app.action("pick_channel_for_fun")(simple_listener)
57+
58+
request = self.build_valid_request()
59+
response = app.dispatch(request)
60+
assert response.status == 200
61+
assert self.mock_received_requests["/auth.test"] == 1
62+
5463
def test_success(self):
5564
app = App(client=self.web_client, signing_secret=self.signing_secret,)
5665
app.action(
@@ -86,6 +95,18 @@ def test_process_before_response(self):
8695
assert response.status == 200
8796
assert self.mock_received_requests["/auth.test"] == 1
8897

98+
def test_failure_without_type(self):
99+
app = App(client=self.web_client, signing_secret=self.signing_secret,)
100+
request = self.build_valid_request()
101+
response = app.dispatch(request)
102+
assert response.status == 404
103+
assert self.mock_received_requests["/auth.test"] == 1
104+
105+
app.action("unknown")(simple_listener)
106+
response = app.dispatch(request)
107+
assert response.status == 404
108+
assert self.mock_received_requests["/auth.test"] == 2
109+
89110
def test_failure(self):
90111
app = App(client=self.web_client, signing_secret=self.signing_secret,)
91112
request = self.build_valid_request()

tests/scenario_tests/test_dialogs.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,30 @@ def test_mock_server_is_running(self):
4949
resp = self.web_client.api_test()
5050
assert resp != None
5151

52+
def test_success_without_type(self):
53+
app = App(client=self.web_client, signing_secret=self.signing_secret,)
54+
app.options("dialog-callback-id")(handle_suggestion)
55+
app.action("dialog-callback-id")(handle_submission_cancellation)
56+
57+
request = self.build_valid_request(suggestion_raw_body)
58+
response = app.dispatch(request)
59+
assert response.status == 200
60+
assert response.body != ""
61+
assert response.headers["content-type"][0] == "application/json;charset=utf-8"
62+
assert self.mock_received_requests["/auth.test"] == 1
63+
64+
request = self.build_valid_request(submission_raw_body)
65+
response = app.dispatch(request)
66+
assert response.status == 200
67+
assert response.body == ""
68+
assert self.mock_received_requests["/auth.test"] == 2
69+
70+
request = self.build_valid_request(cancellation_raw_body)
71+
response = app.dispatch(request)
72+
assert response.status == 200
73+
assert response.body == ""
74+
assert self.mock_received_requests["/auth.test"] == 3
75+
5276
def test_success(self):
5377
app = App(client=self.web_client, signing_secret=self.signing_secret,)
5478
app.options({"type": "dialog_suggestion", "callback_id": "dialog-callback-id"})(
@@ -169,6 +193,18 @@ def test_process_before_response_2(self):
169193
assert response.body == ""
170194
assert self.mock_received_requests["/auth.test"] == 3
171195

196+
def test_suggestion_failure_without_type(self):
197+
app = App(client=self.web_client, signing_secret=self.signing_secret,)
198+
request = self.build_valid_request(suggestion_raw_body)
199+
response = app.dispatch(request)
200+
assert response.status == 404
201+
assert self.mock_received_requests["/auth.test"] == 1
202+
203+
app.options("dialog-callback-iddddd")(handle_suggestion)
204+
response = app.dispatch(request)
205+
assert response.status == 404
206+
assert self.mock_received_requests["/auth.test"] == 2
207+
172208
def test_suggestion_failure(self):
173209
app = App(client=self.web_client, signing_secret=self.signing_secret,)
174210
request = self.build_valid_request(suggestion_raw_body)
@@ -195,6 +231,18 @@ def test_suggestion_failure_2(self):
195231
assert response.status == 404
196232
assert self.mock_received_requests["/auth.test"] == 2
197233

234+
def test_submission_failure_without_type(self):
235+
app = App(client=self.web_client, signing_secret=self.signing_secret,)
236+
request = self.build_valid_request(suggestion_raw_body)
237+
response = app.dispatch(request)
238+
assert response.status == 404
239+
assert self.mock_received_requests["/auth.test"] == 1
240+
241+
app.action("dialog-callback-iddddd")(handle_submission)
242+
response = app.dispatch(request)
243+
assert response.status == 404
244+
assert self.mock_received_requests["/auth.test"] == 2
245+
198246
def test_submission_failure(self):
199247
app = App(client=self.web_client, signing_secret=self.signing_secret,)
200248
request = self.build_valid_request(suggestion_raw_body)
@@ -221,6 +269,18 @@ def test_submission_failure_2(self):
221269
assert response.status == 404
222270
assert self.mock_received_requests["/auth.test"] == 2
223271

272+
def test_cancellation_failure_without_type(self):
273+
app = App(client=self.web_client, signing_secret=self.signing_secret,)
274+
request = self.build_valid_request(suggestion_raw_body)
275+
response = app.dispatch(request)
276+
assert response.status == 404
277+
assert self.mock_received_requests["/auth.test"] == 1
278+
279+
app.action("dialog-callback-iddddd")(handle_cancellation)
280+
response = app.dispatch(request)
281+
assert response.status == 404
282+
assert self.mock_received_requests["/auth.test"] == 2
283+
224284
def test_cancellation_failure(self):
225285
app = App(client=self.web_client, signing_secret=self.signing_secret,)
226286
request = self.build_valid_request(suggestion_raw_body)
@@ -336,3 +396,9 @@ def handle_cancellation(ack, body, payload, action):
336396
assert body == action
337397
assert payload == action
338398
ack()
399+
400+
401+
def handle_submission_cancellation(ack, body, payload, action):
402+
assert body == action
403+
assert payload == action
404+
ack()

0 commit comments

Comments
 (0)
X Tutup