Skip to content

Commit c6ec722

Browse files
authored
Add missing listener suggestion to the default unhandled error message (#323)
* Add missing listener suggestion to the default unhandled error message * Adjust the warning log message
1 parent 1e18c0d commit c6ec722

File tree

6 files changed

+1048
-9
lines changed

6 files changed

+1048
-9
lines changed

scripts/run_tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ python_version=`python --version | awk '{print $2}'`
1212
if [[ $test_target != "" ]]
1313
then
1414
black slack_bolt/ tests/ && \
15-
pytest $1
15+
pytest -vv $1
1616
else
1717
if [ ${python_version:0:3} == "3.8" ]
1818
then
1919
# pytype's behavior can be different in older Python versions
2020
black slack_bolt/ tests/ \
21-
&& pytest \
21+
&& pytest -vv \
2222
&& pip install -e ".[adapter]" \
2323
&& pip install -U pip setuptools wheel \
2424
&& pip install -U pytype \

slack_bolt/app/app.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,6 @@ def middleware_next():
530530
def _handle_unmatched_requests(
531531
self, req: BoltRequest, resp: BoltResponse
532532
) -> BoltResponse:
533-
# TODO: provide more info like suggestion of listeners
534-
# e.g., You can handle this type of message with @app.event("app_mention")
535533
self._framework_logger.warning(warning_unhandled_request(req))
536534
return resp
537535

slack_bolt/app/async_app.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,6 @@ async def async_middleware_next():
586586
def _handle_unmatched_requests(
587587
self, req: AsyncBoltRequest, resp: BoltResponse
588588
) -> BoltResponse:
589-
# TODO: provide more info like suggestion of listeners
590-
# e.g., You can handle this type of message with @app.event("app_mention")
591589
self._framework_logger.warning(warning_unhandled_request(req))
592590
return resp
593591

slack_bolt/logger/messages.py

Lines changed: 188 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
import time
2-
from typing import Union
2+
from typing import Union, Dict, Any, Optional
33

44
from slack_sdk.web import SlackResponse
55

66
from slack_bolt.request import BoltRequest
7-
7+
from slack_bolt.request.payload_utils import (
8+
is_action,
9+
is_event,
10+
is_options,
11+
is_shortcut,
12+
is_slash_command,
13+
is_view,
14+
is_workflow_step_edit,
15+
is_workflow_step_save,
16+
is_workflow_step_execute,
17+
)
818

919
# -------------------------------
1020
# Error
@@ -94,10 +104,185 @@ def warning_unhandled_by_global_middleware( # type: ignore
94104
)
95105

96106

107+
_unhandled_request_suggestion_prefix = """
108+
---
109+
[Suggestion] You can handle this type of event with the following listener function:
110+
"""
111+
112+
113+
def _build_filtered_body(body: Optional[Dict[str, Any]]) -> dict:
114+
if body is None:
115+
return {}
116+
117+
payload_type = body.get("type")
118+
filtered_body = {"type": payload_type}
119+
120+
if "view" in body:
121+
view = body["view"]
122+
# view_submission, view_closed, workflow_step_save
123+
filtered_body["view"] = {
124+
"type": view.get("type"),
125+
"callback_id": view.get("callback_id"),
126+
}
127+
128+
if payload_type == "block_actions":
129+
# Block Kit Interactivity
130+
actions = body.get("actions", [])
131+
if len(actions) > 0 and actions[0] is not None:
132+
filtered_body["block_id"] = actions[0].get("block_id")
133+
filtered_body["action_id"] = actions[0].get("action_id")
134+
if payload_type == "block_suggestion":
135+
# Block Kit - external data source
136+
filtered_body["block_id"] = body.get("block_id")
137+
filtered_body["action_id"] = body.get("action_id")
138+
filtered_body["value"] = body.get("value")
139+
140+
if payload_type == "event_callback" and "event" in body:
141+
# Events API, workflow_step_execute
142+
event_payload = body.get("event", {})
143+
filtered_event = {"type": event_payload.get("type")}
144+
if "subtype" in body["event"]:
145+
filtered_event["subtype"] = event_payload.get("subtype")
146+
filtered_body["event"] = filtered_event
147+
148+
if "command" in body:
149+
# Slash Commands
150+
filtered_body["command"] = body.get("command")
151+
152+
if payload_type in ["workflow_step_edit", "shortcut", "message_action"]:
153+
# Workflow Steps, Global Shortcuts, Message Shortcuts
154+
filtered_body["callback_id"] = body.get("callback_id")
155+
156+
if payload_type == "interactive_message":
157+
# Actions in Attachments
158+
filtered_body["callback_id"] = body.get("callback_id")
159+
filtered_body["actions"] = body.get("actions")
160+
161+
if payload_type == "dialog_suggestion":
162+
# Dialogs - external data source
163+
filtered_body["callback_id"] = body.get("callback_id")
164+
filtered_body["value"] = body.get("value")
165+
if payload_type == "dialog_submission":
166+
# Dialogs - clicking submit button
167+
filtered_body["callback_id"] = body.get("callback_id")
168+
filtered_body["submission"] = body.get("submission")
169+
if payload_type == "dialog_cancellation":
170+
# Dialogs - clicking cancel button
171+
filtered_body["callback_id"] = body.get("callback_id")
172+
173+
return filtered_body
174+
175+
176+
def _build_unhandled_request_suggestion(default_message: str, code_snippet: str):
177+
return f"""{default_message}{_unhandled_request_suggestion_prefix}{code_snippet}"""
178+
179+
97180
def warning_unhandled_request( # type: ignore
98181
req: Union[BoltRequest, "AsyncBoltRequest"], # type: ignore
99182
) -> str: # type: ignore
100-
return f"Unhandled request ({req.body})"
183+
filtered_body = _build_filtered_body(req.body)
184+
default_message = f"Unhandled request ({filtered_body})"
185+
if (
186+
is_workflow_step_edit(req.body)
187+
or is_workflow_step_save(req.body)
188+
or is_workflow_step_execute(req.body)
189+
):
190+
# @app.step
191+
callback_id = (
192+
filtered_body.get("callback_id")
193+
or filtered_body.get("view", {}).get("callback_id") # type: ignore
194+
or "your-callback-id"
195+
)
196+
return _build_unhandled_request_suggestion(
197+
default_message,
198+
f"""
199+
from slack_bolt.workflows.step import WorkflowStep
200+
ws = WorkflowStep(
201+
callback_id="{callback_id}",
202+
edit=edit,
203+
save=save,
204+
execute=execute,
205+
)
206+
# Pass Step to set up listeners
207+
app.step(ws)
208+
""",
209+
)
210+
if is_action(req.body):
211+
# @app.action
212+
action_id_or_callback_id = req.body.get("callback_id")
213+
if req.body.get("type") == "block_actions":
214+
action_id_or_callback_id = req.body.get("actions")[0].get("action_id")
215+
return _build_unhandled_request_suggestion(
216+
default_message,
217+
f"""
218+
@app.action("{action_id_or_callback_id}")
219+
def handle_some_action(ack, body, logger):
220+
ack()
221+
logger.info(body)
222+
""",
223+
)
224+
if is_options(req.body):
225+
# @app.options
226+
constraints = '"action-id"'
227+
if req.body.get("action_id") is not None:
228+
constraints = '"' + req.body.get("action_id") + '"'
229+
elif req.body.get("type") == "dialog_suggestion":
230+
constraints = f"""{{"type": "dialog_suggestion", "callback_id": "{req.body.get('callback_id')}"}}"""
231+
return _build_unhandled_request_suggestion(
232+
default_message,
233+
f"""
234+
@app.options({constraints})
235+
def handle_some_options(ack):
236+
ack(options=[ ... ])
237+
""",
238+
)
239+
if is_shortcut(req.body):
240+
# @app.shortcut
241+
id = req.body.get("action_id") or req.body.get("callback_id")
242+
return _build_unhandled_request_suggestion(
243+
default_message,
244+
f"""
245+
@app.shortcut("{id}")
246+
def handle_shortcuts(ack, body, logger):
247+
ack()
248+
logger.info(body)
249+
""",
250+
)
251+
if is_view(req.body):
252+
# @app.view
253+
return _build_unhandled_request_suggestion(
254+
default_message,
255+
f"""
256+
@app.view("{req.body.get('view', {}).get('callback_id', 'modal-view-id')}")
257+
def handle_view_events(ack, body, logger):
258+
ack()
259+
logger.info(body)
260+
""",
261+
)
262+
if is_event(req.body):
263+
# @app.event
264+
event_type = req.body.get("event", {}).get("type")
265+
return _build_unhandled_request_suggestion(
266+
default_message,
267+
f"""
268+
@app.event("{event_type}")
269+
def handle_{event_type}_events(body, logger):
270+
logger.info(body)
271+
""",
272+
)
273+
if is_slash_command(req.body):
274+
# @app.command
275+
command = req.body.get("command", "/your-command")
276+
return _build_unhandled_request_suggestion(
277+
default_message,
278+
f"""
279+
@app.command("{command}")
280+
def handle_some_command(ack, body, logger):
281+
ack()
282+
logger.info(body)
283+
""",
284+
)
285+
return default_message
101286

102287

103288
def warning_did_not_call_ack(listener_name: str) -> str:

tests/slack_bolt/logger/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)