X Tutup
Skip to content

Commit efc28ac

Browse files
committed
Add full support for Ack args
1 parent d6c66ce commit efc28ac

File tree

5 files changed

+227
-17
lines changed

5 files changed

+227
-17
lines changed

slack_bolt/context/ack/ack.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from typing import List, Optional, Union
1+
from typing import List, Optional, Union, Dict
22

33
from slack_sdk.models.attachments import Attachment
44
from slack_sdk.models.blocks import Block, Option, OptionGroup
5+
from slack_sdk.models.views import View
56

67
from slack_bolt.context.ack.internals import _set_response
78
from slack_bolt.response.response import BoltResponse
@@ -18,14 +19,24 @@ def __call__(
1819
text: Union[str, dict] = "", # text: str or whole_response: dict
1920
blocks: Optional[List[Union[dict, Block]]] = None,
2021
attachments: Optional[List[Union[dict, Attachment]]] = None,
22+
response_type: Optional[str] = None, # in_channel / ephemeral
23+
# block_suggestion / dialog_suggestion
2124
options: Optional[List[Union[dict, Option]]] = None,
2225
option_groups: Optional[List[Union[dict, OptionGroup]]] = None,
26+
# view_submission
27+
response_action: Optional[str] = None, # errors / update / push / clear
28+
errors: Optional[Dict[str, str]] = None,
29+
view: Optional[Union[dict, View]] = None,
2330
) -> BoltResponse:
2431
return _set_response(
2532
self,
2633
text_or_whole_response=text,
2734
blocks=blocks,
2835
attachments=attachments,
36+
response_type=response_type,
2937
options=options,
3038
option_groups=option_groups,
39+
response_action=response_action,
40+
errors=errors,
41+
view=view,
3142
)

slack_bolt/context/ack/async_ack.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from typing import List, Optional, Union
1+
from typing import List, Optional, Union, Dict
22

33
from slack_sdk.models.attachments import Attachment
44
from slack_sdk.models.blocks import Block, Option, OptionGroup
5+
from slack_sdk.models.views import View
56

67
from slack_bolt.context.ack.internals import _set_response
78
from slack_bolt.response.response import BoltResponse
@@ -18,14 +19,24 @@ async def __call__(
1819
text: Union[str, dict] = "", # text: str or whole_response: dict
1920
blocks: Optional[List[Union[dict, Block]]] = None,
2021
attachments: Optional[List[Union[dict, Attachment]]] = None,
22+
response_type: Optional[str] = None, # in_channel / ephemeral
23+
# block_suggestion / dialog_suggestion
2124
options: Optional[List[Union[dict, Option]]] = None,
2225
option_groups: Optional[List[Union[dict, OptionGroup]]] = None,
26+
# view_submission
27+
response_action: Optional[str] = None, # errors / update / push / clear
28+
errors: Optional[Dict[str, str]] = None,
29+
view: Optional[Union[dict, View]] = None,
2330
) -> BoltResponse:
2431
return _set_response(
2532
self,
2633
text_or_whole_response=text,
2734
blocks=blocks,
2835
attachments=attachments,
36+
response_type=response_type,
2937
options=options,
3038
option_groups=option_groups,
39+
response_action=response_action,
40+
errors=errors,
41+
view=view,
3142
)

slack_bolt/context/ack/internals.py

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,72 @@
1-
from typing import Optional, List, Union, Any
1+
from typing import Optional, List, Union, Any, Dict
22

33
from slack_sdk.models.attachments import Attachment
44
from slack_sdk.models.blocks import Block, Option, OptionGroup
5+
from slack_sdk.models.views import View
56

67
from slack_bolt.error import BoltError
78
from slack_bolt.response import BoltResponse
8-
from slack_bolt.util.utils import convert_to_dict_list
9+
from slack_bolt.util.utils import convert_to_dict_list, _to_dict
910

1011

1112
def _set_response(
1213
self: Any,
1314
text_or_whole_response: Union[str, dict] = "",
1415
blocks: Optional[List[Union[dict, Block]]] = None,
1516
attachments: Optional[List[Union[dict, Attachment]]] = None,
17+
response_type: Optional[str] = None, # in_channel / ephemeral
18+
# block_suggestion / dialog_suggestion
1619
options: Optional[List[Union[dict, Option]]] = None,
1720
option_groups: Optional[List[Union[dict, OptionGroup]]] = None,
21+
# view_submission
22+
response_action: Optional[str] = None,
23+
errors: Optional[Dict[str, str]] = None,
24+
view: Optional[Union[dict, View]] = None,
1825
) -> BoltResponse:
1926
if isinstance(text_or_whole_response, str):
2027
text: str = text_or_whole_response
28+
body = {"text": text}
29+
if response_type:
30+
body["response_type"] = response_type
2131
if attachments and len(attachments) > 0:
22-
self.response = BoltResponse(
23-
status=200,
24-
body={"text": text, "attachments": convert_to_dict_list(attachments),},
32+
body.update(
33+
{"text": text, "attachments": convert_to_dict_list(attachments)}
2534
)
35+
self.response = BoltResponse(status=200, body=body)
2636
elif blocks and len(blocks) > 0:
27-
self.response = BoltResponse(
28-
status=200, body={"text": text, "blocks": convert_to_dict_list(blocks),}
29-
)
37+
body.update({"text": text, "blocks": convert_to_dict_list(blocks)})
38+
self.response = BoltResponse(status=200, body=body)
3039
elif options and len(options) > 0:
31-
self.response = BoltResponse(
32-
status=200, body={"options": convert_to_dict_list(options),}
33-
)
40+
body = {"options": convert_to_dict_list(options)}
41+
self.response = BoltResponse(status=200, body=body)
3442
elif option_groups and len(option_groups) > 0:
35-
self.response = BoltResponse(
36-
status=200, body={"option_groups": convert_to_dict_list(option_groups),}
37-
)
43+
body = {"option_groups": convert_to_dict_list(option_groups)}
44+
self.response = BoltResponse(status=200, body=body)
45+
elif response_action:
46+
# These patterns are in response to view_submission requests
47+
if response_action == "errors":
48+
if errors:
49+
self.response = BoltResponse(
50+
status=200,
51+
body={
52+
"response_action": response_action,
53+
"errors": _to_dict(errors),
54+
},
55+
)
56+
else:
57+
raise ValueError(
58+
f"errors field is required for response_action: errors"
59+
)
60+
else:
61+
body = {"response_action": response_action}
62+
if view:
63+
body["view"] = _to_dict(view)
64+
self.response = BoltResponse(status=200, body=body)
3865
else:
39-
self.response = BoltResponse(status=200, body=text)
66+
if len(body) == 1 and "text" in body:
67+
self.response = BoltResponse(status=200, body=body["text"])
68+
else:
69+
self.response = BoltResponse(status=200, body=body)
4070
return self.response
4171
elif isinstance(text_or_whole_response, dict):
4272
body = text_or_whole_response
@@ -48,6 +78,14 @@ def _set_response(
4878
body["options"] = convert_to_dict_list(body["options"])
4979
if "option_groups" in body:
5080
body["option_groups"] = convert_to_dict_list(body["option_groups"])
81+
if "response_type" in body:
82+
body["response_type"] = body["response_type"]
83+
if "response_action" in body:
84+
body["response_action"] = body["response_action"]
85+
if "errors" in body:
86+
body["errors"] = _to_dict(body["errors"])
87+
if "view" in body:
88+
body["view"] = _to_dict(body["view"])
5189

5290
self.response = BoltResponse(status=200, body=body)
5391
return self.response
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from slack_bolt import Ack, BoltResponse
2+
3+
4+
class TestAck:
5+
def setup_method(self):
6+
pass
7+
8+
def teardown_method(self):
9+
pass
10+
11+
def test_text(self):
12+
ack = Ack()
13+
response: BoltResponse = ack(text="foo")
14+
assert (response.status, response.body) == (200, "foo")
15+
16+
def test_blocks(self):
17+
ack = Ack()
18+
response: BoltResponse = ack(text="foo", blocks=[{"type": "divider"}])
19+
assert (response.status, response.body) == (
20+
200,
21+
'{"text": "foo", "blocks": [{"type": "divider"}]}',
22+
)
23+
24+
def test_response_type(self):
25+
ack = Ack()
26+
response: BoltResponse = ack(text="foo", response_type="in_channel")
27+
assert (response.status, response.body) == (
28+
200,
29+
'{"text": "foo", "response_type": "in_channel"}',
30+
)
31+
32+
def test_view_errors(self):
33+
ack = Ack()
34+
response: BoltResponse = ack(
35+
response_action="errors",
36+
errors={
37+
"block_title": "Title is required",
38+
"block_description": "Description must be longer than 10 characters",
39+
},
40+
)
41+
assert (response.status, response.body) == (
42+
200,
43+
'{"response_action": "errors", '
44+
'"errors": {'
45+
'"block_title": "Title is required", '
46+
'"block_description": "Description must be longer than 10 characters"'
47+
"}"
48+
"}",
49+
)
50+
51+
def test_view_update(self):
52+
ack = Ack()
53+
response: BoltResponse = ack(
54+
response_action="update",
55+
view={
56+
"type": "modal",
57+
"callback_id": "view-id",
58+
"title": {"type": "plain_text", "text": "My App",},
59+
"close": {"type": "plain_text", "text": "Cancel",},
60+
"blocks": [{"type": "divider", "block_id": "b"}],
61+
},
62+
)
63+
assert (response.status, response.body) == (
64+
200,
65+
'{"response_action": "update", '
66+
'"view": {'
67+
'"type": "modal", '
68+
'"callback_id": "view-id", '
69+
'"title": {"type": "plain_text", "text": "My App"}, '
70+
'"close": {"type": "plain_text", "text": "Cancel"}, '
71+
'"blocks": [{"type": "divider", "block_id": "b"}]'
72+
"}"
73+
"}",
74+
)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import pytest
2+
3+
from slack_bolt import BoltResponse
4+
from slack_bolt.context.ack.async_ack import AsyncAck
5+
6+
7+
class TestAsyncAsyncAck:
8+
@pytest.mark.asyncio
9+
async def test_text(self):
10+
ack = AsyncAck()
11+
response: BoltResponse = await ack(text="foo")
12+
assert (response.status, response.body) == (200, "foo")
13+
14+
@pytest.mark.asyncio
15+
async def test_blocks(self):
16+
ack = AsyncAck()
17+
response: BoltResponse = await ack(text="foo", blocks=[{"type": "divider"}])
18+
assert (response.status, response.body) == (
19+
200,
20+
'{"text": "foo", "blocks": [{"type": "divider"}]}',
21+
)
22+
23+
@pytest.mark.asyncio
24+
async def test_response_type(self):
25+
ack = AsyncAck()
26+
response: BoltResponse = await ack(text="foo", response_type="in_channel")
27+
assert (response.status, response.body) == (
28+
200,
29+
'{"text": "foo", "response_type": "in_channel"}',
30+
)
31+
32+
@pytest.mark.asyncio
33+
async def test_view_errors(self):
34+
ack = AsyncAck()
35+
response: BoltResponse = await ack(
36+
response_action="errors",
37+
errors={
38+
"block_title": "Title is required",
39+
"block_description": "Description must be longer than 10 characters",
40+
},
41+
)
42+
assert (response.status, response.body) == (
43+
200,
44+
'{"response_action": "errors", '
45+
'"errors": {'
46+
'"block_title": "Title is required", '
47+
'"block_description": "Description must be longer than 10 characters"'
48+
"}"
49+
"}",
50+
)
51+
52+
@pytest.mark.asyncio
53+
async def test_view_update(self):
54+
ack = AsyncAck()
55+
response: BoltResponse = await ack(
56+
response_action="update",
57+
view={
58+
"type": "modal",
59+
"callbAsyncAck_id": "view-id",
60+
"title": {"type": "plain_text", "text": "My App",},
61+
"close": {"type": "plain_text", "text": "Cancel",},
62+
"blocks": [{"type": "divider", "block_id": "b"}],
63+
},
64+
)
65+
assert (response.status, response.body) == (
66+
200,
67+
'{"response_action": "update", '
68+
'"view": {'
69+
'"type": "modal", '
70+
'"callbAsyncAck_id": "view-id", '
71+
'"title": {"type": "plain_text", "text": "My App"}, '
72+
'"close": {"type": "plain_text", "text": "Cancel"}, '
73+
'"blocks": [{"type": "divider", "block_id": "b"}]'
74+
"}"
75+
"}",
76+
)

0 commit comments

Comments
 (0)
X Tutup