Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #260 Enable to use respond utility in app.view listeners (only when response_urls exists) #288

Merged
merged 2 commits into from
Apr 16, 2021
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
23 changes: 16 additions & 7 deletions slack_bolt/request/async_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,34 @@
extract_team_id,
extract_user_id,
extract_channel_id,
debug_multiple_response_urls_detected,
)


def build_async_context(
context: AsyncBoltContext,
payload: Dict[str, Any],
body: Dict[str, Any],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for consistency with internals.py. This method name does not start with _ but this is part of internals source file. I think it's safe to change the name in the next minor release as no one relies on the argument name.

) -> AsyncBoltContext:
enterprise_id = extract_enterprise_id(payload)
enterprise_id = extract_enterprise_id(body)
if enterprise_id:
context["enterprise_id"] = enterprise_id
team_id = extract_team_id(payload)
team_id = extract_team_id(body)
if team_id:
context["team_id"] = team_id
user_id = extract_user_id(payload)
user_id = extract_user_id(body)
if user_id:
context["user_id"] = user_id
channel_id = extract_channel_id(payload)
channel_id = extract_channel_id(body)
if channel_id:
context["channel_id"] = channel_id
if "response_url" in payload:
context["response_url"] = payload["response_url"]
if "response_url" in body:
context["response_url"] = body["response_url"]
elif "response_urls" in body:
# In the case where response_url_enabled: true in a modal exists
response_urls = body["response_urls"]
if len(response_urls) >= 1:
if len(response_urls) > 1:
context.logger.debug(debug_multiple_response_urls_detected())
response_url = response_urls[0].get("response_url")
context["response_url"] = response_url
return context
16 changes: 16 additions & 0 deletions slack_bolt/request/internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,14 @@ def build_context(context: BoltContext, body: Dict[str, Any]) -> BoltContext:
context["channel_id"] = channel_id
if "response_url" in body:
context["response_url"] = body["response_url"]
elif "response_urls" in body:
# In the case where response_url_enabled: true in a modal exists
response_urls = body["response_urls"]
if len(response_urls) >= 1:
if len(response_urls) > 1:
context.logger.debug(debug_multiple_response_urls_detected())
response_url = response_urls[0].get("response_url")
context["response_url"] = response_url
return context


Expand Down Expand Up @@ -177,3 +185,11 @@ def error_message_raw_body_required_in_http_mode() -> str:

def error_message_unknown_request_body_type() -> str:
return "`body` must be either str or dict"


def debug_multiple_response_urls_detected() -> str:
return (
"`response_urls` in the body has multiple URLs in it. "
"If you would like to use non-primary one, "
"please manually extract the one from body['response_urls']."
)
6 changes: 6 additions & 0 deletions tests/mock_web_api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ def set_common_headers(self):
def _handle(self):
self.received_requests[self.path] = self.received_requests.get(self.path, 0) + 1
try:
if self.path == "/webhook":
self.send_response(200)
self.set_common_headers()
self.wfile.write("OK".encode("utf-8"))
return

if self.path == "/received_requests.json":
self.send_response(200)
self.set_common_headers()
Expand Down
217 changes: 142 additions & 75 deletions tests/scenario_tests/test_view_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,133 @@
from tests.utils import remove_os_env_temporarily, restore_os_env


body = {
"type": "view_submission",
"team": {
"id": "T111",
"domain": "workspace-domain",
"enterprise_id": "E111",
"enterprise_name": "Sandbox Org",
},
"user": {
"id": "W111",
"username": "primary-owner",
"name": "primary-owner",
"team_id": "T111",
},
"api_app_id": "A111",
"token": "verification_token",
"trigger_id": "111.222.valid",
"view": {
"id": "V111",
"team_id": "T111",
"type": "modal",
"blocks": [
{
"type": "input",
"block_id": "hspI",
"label": {
"type": "plain_text",
"text": "Label",
},
"optional": False,
"element": {"type": "plain_text_input", "action_id": "maBWU"},
}
],
"private_metadata": "This is for you!",
"callback_id": "view-id",
"state": {
"values": {"hspI": {"maBWU": {"type": "plain_text_input", "value": "test"}}}
},
"hash": "1596530361.3wRYuk3R",
"title": {
"type": "plain_text",
"text": "My App",
},
"clear_on_close": False,
"notify_on_close": False,
"close": {
"type": "plain_text",
"text": "Cancel",
},
"submit": {
"type": "plain_text",
"text": "Submit",
},
"previous_view_id": None,
"root_view_id": "V111",
"app_id": "A111",
"external_id": "",
"app_installed_team_id": "T111",
"bot_id": "B111",
},
"response_urls": [],
}

raw_body = f"payload={quote(json.dumps(body))}"


def simple_listener(ack, body, payload, view):
assert body["trigger_id"] == "111.222.valid"
assert body["view"] == payload
assert payload == view
assert view["private_metadata"] == "This is for you!"
ack()


response_url_payload_body = {
"type": "view_submission",
"team": {"id": "T111", "domain": "test-test-test"},
"user": {
"id": "U111",
"username": "test-test-test",
"name": "test-test-test",
"team_id": "T111",
},
"api_app_id": "A111",
"token": "verification-token",
"trigger_id": "111.222.xxx",
"view": {
"id": "V111",
"team_id": "T111",
"type": "modal",
"blocks": [],
"callback_id": "view-id",
"state": {},
"title": {
"type": "plain_text",
"text": "My App",
},
"close": {
"type": "plain_text",
"text": "Cancel",
},
"submit": {
"type": "plain_text",
"text": "Submit",
},
"previous_view_id": None,
"root_view_id": "V111",
"app_id": "A111",
"external_id": "",
"app_installed_team_id": "T111",
"bot_id": "B111",
},
"response_urls": [
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the field this pull request takes care of. This array can have an element only when a modal has a conversations_select block element with response_url_enabled: true option.

{
"block_id": "b",
"action_id": "a",
"channel_id": "C111",
"response_url": "http://localhost:8888/webhook",
}
],
"is_enterprise_install": False,
}


raw_response_url_body = f"payload={quote(json.dumps(response_url_payload_body))}"


class TestViewSubmission:
signing_secret = "secret"
valid_token = "xoxb-valid"
Expand Down Expand Up @@ -46,11 +173,9 @@ def build_headers(self, timestamp: str, body: str):
"x-slack-request-timestamp": [timestamp],
}

def build_valid_request(self) -> BoltRequest:
def build_valid_request(self, body: str = raw_body) -> BoltRequest:
timestamp = str(int(time()))
return BoltRequest(
body=raw_body, headers=self.build_headers(timestamp, raw_body)
)
return BoltRequest(body=body, headers=self.build_headers(timestamp, body))

def test_mock_server_is_running(self):
resp = self.web_client.api_test()
Expand Down Expand Up @@ -123,76 +248,18 @@ def test_failure_2(self):
assert response.status == 404
assert_auth_test_count(self, 1)

def test_response_urls(self):
app = App(
client=self.web_client,
signing_secret=self.signing_secret,
)

body = {
"type": "view_submission",
"team": {
"id": "T111",
"domain": "workspace-domain",
"enterprise_id": "E111",
"enterprise_name": "Sandbox Org",
},
"user": {
"id": "W111",
"username": "primary-owner",
"name": "primary-owner",
"team_id": "T111",
},
"api_app_id": "A111",
"token": "verification_token",
"trigger_id": "111.222.valid",
"view": {
"id": "V111",
"team_id": "T111",
"type": "modal",
"blocks": [
{
"type": "input",
"block_id": "hspI",
"label": {
"type": "plain_text",
"text": "Label",
},
"optional": False,
"element": {"type": "plain_text_input", "action_id": "maBWU"},
}
],
"private_metadata": "This is for you!",
"callback_id": "view-id",
"state": {
"values": {"hspI": {"maBWU": {"type": "plain_text_input", "value": "test"}}}
},
"hash": "1596530361.3wRYuk3R",
"title": {
"type": "plain_text",
"text": "My App",
},
"clear_on_close": False,
"notify_on_close": False,
"close": {
"type": "plain_text",
"text": "Cancel",
},
"submit": {
"type": "plain_text",
"text": "Submit",
},
"previous_view_id": None,
"root_view_id": "V111",
"app_id": "A111",
"external_id": "",
"app_installed_team_id": "T111",
"bot_id": "B111",
},
"response_urls": [],
}

raw_body = f"payload={quote(json.dumps(body))}"

@app.view("view-id")
def check(ack, respond):
respond("Hi")
ack()

def simple_listener(ack, body, payload, view):
assert body["trigger_id"] == "111.222.valid"
assert body["view"] == payload
assert payload == view
assert view["private_metadata"] == "This is for you!"
ack()
request = self.build_valid_request(raw_response_url_body)
response = app.dispatch(request)
assert response.status == 200
assert_auth_test_count(self, 1)
Loading