Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions scripts/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ python_version=`python --version | awk '{print $2}'`
if [[ $test_target != "" ]]
then
black slack_bolt/ tests/ && \
pytest $1
pytest -vv $1
else
if [ ${python_version:0:3} == "3.8" ]
then
# pytype's behavior can be different in older Python versions
black slack_bolt/ tests/ \
&& pytest \
&& pytest -vv \
&& pip install -e ".[adapter]" \
&& pip install -U pip setuptools wheel \
&& pip install -U pytype \
Expand Down
2 changes: 0 additions & 2 deletions slack_bolt/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,8 +530,6 @@ def middleware_next():
def _handle_unmatched_requests(
self, req: BoltRequest, resp: BoltResponse
) -> BoltResponse:
# TODO: provide more info like suggestion of listeners
# e.g., You can handle this type of message with @app.event("app_mention")
self._framework_logger.warning(warning_unhandled_request(req))
return resp

Expand Down
2 changes: 0 additions & 2 deletions slack_bolt/app/async_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,8 +586,6 @@ async def async_middleware_next():
def _handle_unmatched_requests(
self, req: AsyncBoltRequest, resp: BoltResponse
) -> BoltResponse:
# TODO: provide more info like suggestion of listeners
# e.g., You can handle this type of message with @app.event("app_mention")
self._framework_logger.warning(warning_unhandled_request(req))
return resp

Expand Down
191 changes: 188 additions & 3 deletions slack_bolt/logger/messages.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import time
from typing import Union
from typing import Union, Dict, Any, Optional

from slack_sdk.web import SlackResponse

from slack_bolt.request import BoltRequest

from slack_bolt.request.payload_utils import (
is_action,
is_event,
is_options,
is_shortcut,
is_slash_command,
is_view,
is_workflow_step_edit,
is_workflow_step_save,
is_workflow_step_execute,
)

# -------------------------------
# Error
Expand Down Expand Up @@ -94,10 +104,185 @@ def warning_unhandled_by_global_middleware( # type: ignore
)


_unhandled_request_suggestion_prefix = """
---
[Suggestion] You can handle this type of event with the following listener function:
"""


def _build_filtered_body(body: Optional[Dict[str, Any]]) -> dict:
if body is None:
return {}

payload_type = body.get("type")
filtered_body = {"type": payload_type}

if "view" in body:
view = body["view"]
# view_submission, view_closed, workflow_step_save
filtered_body["view"] = {
"type": view.get("type"),
"callback_id": view.get("callback_id"),
}

if payload_type == "block_actions":
# Block Kit Interactivity
actions = body.get("actions", [])
if len(actions) > 0 and actions[0] is not None:
filtered_body["block_id"] = actions[0].get("block_id")
filtered_body["action_id"] = actions[0].get("action_id")
if payload_type == "block_suggestion":
# Block Kit - external data source
filtered_body["block_id"] = body.get("block_id")
filtered_body["action_id"] = body.get("action_id")
filtered_body["value"] = body.get("value")

if payload_type == "event_callback" and "event" in body:
# Events API, workflow_step_execute
event_payload = body.get("event", {})
filtered_event = {"type": event_payload.get("type")}
if "subtype" in body["event"]:
filtered_event["subtype"] = event_payload.get("subtype")
filtered_body["event"] = filtered_event

if "command" in body:
# Slash Commands
filtered_body["command"] = body.get("command")

if payload_type in ["workflow_step_edit", "shortcut", "message_action"]:
# Workflow Steps, Global Shortcuts, Message Shortcuts
filtered_body["callback_id"] = body.get("callback_id")

if payload_type == "interactive_message":
# Actions in Attachments
filtered_body["callback_id"] = body.get("callback_id")
filtered_body["actions"] = body.get("actions")

if payload_type == "dialog_suggestion":
# Dialogs - external data source
filtered_body["callback_id"] = body.get("callback_id")
filtered_body["value"] = body.get("value")
if payload_type == "dialog_submission":
# Dialogs - clicking submit button
filtered_body["callback_id"] = body.get("callback_id")
filtered_body["submission"] = body.get("submission")
if payload_type == "dialog_cancellation":
# Dialogs - clicking cancel button
filtered_body["callback_id"] = body.get("callback_id")

return filtered_body


def _build_unhandled_request_suggestion(default_message: str, code_snippet: str):
return f"""{default_message}{_unhandled_request_suggestion_prefix}{code_snippet}"""


def warning_unhandled_request( # type: ignore
req: Union[BoltRequest, "AsyncBoltRequest"], # type: ignore
) -> str: # type: ignore
return f"Unhandled request ({req.body})"
filtered_body = _build_filtered_body(req.body)
default_message = f"Unhandled request ({filtered_body})"
if (
is_workflow_step_edit(req.body)
or is_workflow_step_save(req.body)
or is_workflow_step_execute(req.body)
):
# @app.step
callback_id = (
filtered_body.get("callback_id")
or filtered_body.get("view", {}).get("callback_id") # type: ignore
or "your-callback-id"
)
return _build_unhandled_request_suggestion(
default_message,
f"""
from slack_bolt.workflows.step import WorkflowStep
ws = WorkflowStep(
callback_id="{callback_id}",
edit=edit,
save=save,
execute=execute,
)
# Pass Step to set up listeners
app.step(ws)
""",
)
if is_action(req.body):
# @app.action
action_id_or_callback_id = req.body.get("callback_id")
if req.body.get("type") == "block_actions":
action_id_or_callback_id = req.body.get("actions")[0].get("action_id")
return _build_unhandled_request_suggestion(
default_message,
f"""
@app.action("{action_id_or_callback_id}")
def handle_some_action(ack, body, logger):
ack()
logger.info(body)
""",
)
if is_options(req.body):
# @app.options
constraints = '"action-id"'
if req.body.get("action_id") is not None:
constraints = '"' + req.body.get("action_id") + '"'
elif req.body.get("type") == "dialog_suggestion":
constraints = f"""{{"type": "dialog_suggestion", "callback_id": "{req.body.get('callback_id')}"}}"""
return _build_unhandled_request_suggestion(
default_message,
f"""
@app.options({constraints})
def handle_some_options(ack):
ack(options=[ ... ])
""",
)
if is_shortcut(req.body):
# @app.shortcut
id = req.body.get("action_id") or req.body.get("callback_id")
return _build_unhandled_request_suggestion(
default_message,
f"""
@app.shortcut("{id}")
def handle_shortcuts(ack, body, logger):
ack()
logger.info(body)
""",
)
if is_view(req.body):
# @app.view
return _build_unhandled_request_suggestion(
default_message,
f"""
@app.view("{req.body.get('view', {}).get('callback_id', 'modal-view-id')}")
def handle_view_events(ack, body, logger):
ack()
logger.info(body)
""",
)
if is_event(req.body):
# @app.event
event_type = req.body.get("event", {}).get("type")
return _build_unhandled_request_suggestion(
default_message,
f"""
@app.event("{event_type}")
def handle_{event_type}_events(body, logger):
logger.info(body)
""",
)
if is_slash_command(req.body):
# @app.command
command = req.body.get("command", "/your-command")
return _build_unhandled_request_suggestion(
default_message,
f"""
@app.command("{command}")
def handle_some_command(ack, body, logger):
ack()
logger.info(body)
""",
)
return default_message


def warning_did_not_call_ack(listener_name: str) -> str:
Expand Down
Empty file.
Loading