Repository: slackapi/python-slackclient
Pull request #844 (2020-10-09 10:38:43)

Add Dispatch Action in Block Kit #841
## Summary

This pull request adds the feature described in #841 to 2.9 series.

### Category (place an `x` in each of the `[ ]`)

- [ ] **slack.web.WebClient** (Web API client)
- [ ] **slack.webhook.WebhookClient** (Incoming Webhook, response_url sender)
- [x] **slack.web.classes** (UI component builders)
- [ ] **slack.rtm.RTMClient** (RTM client)
- [ ] Documents
- [ ] Others

## Requirements (place an `x` in each `[ ]`)

- [x] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slackclient/blob/main/.github/contributing.md) and have done my best effort to follow them.
- [x] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
- [x] I've run `python setup.py validate` after making the changes.


codecov[bot]: # [Codecov](https://codecov.io/gh/slackapi/python-slackclient/pull/844?src=pr&el=h1) Report
> Merging [#844](https://codecov.io/gh/slackapi/python-slackclient/pull/844?src=pr&el=desc) into [main](https://codecov.io/gh/slackapi/python-slackclient/commit/c959e4534baa13d0cbc11a10a449a8d93356ddcf?el=desc) will **decrease** coverage by `0.27%`.
> The diff coverage is `47.61%`.

[![Impacted file tree graph](https://codecov.io/gh/slackapi/python-slackclient/pull/844/graphs/tree.svg?width=650&height=150&src=pr&token=F0O7meYmtD)](https://codecov.io/gh/slackapi/python-slackclient/pull/844?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##             main     #844      +/-   ##
==========================================
- Coverage   87.29%   87.02%   -0.28%     
==========================================
  Files          30       30              
  Lines        3779     3800      +21     
  Branches      337      341       +4     
==========================================
+ Hits         3299     3307       +8     
- Misses        331      344      +13     
  Partials      149      149              
```


| [Impacted Files](https://codecov.io/gh/slackapi/python-slackclient/pull/844?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [slack/web/classes/objects.py](https://codecov.io/gh/slackapi/python-slackclient/pull/844/diff?src=pr&el=tree#diff-c2xhY2svd2ViL2NsYXNzZXMvb2JqZWN0cy5weQ==) | `82.10% <35.29%> (-3.61%)` | :arrow_down: |
| [slack/web/classes/blocks.py](https://codecov.io/gh/slackapi/python-slackclient/pull/844/diff?src=pr&el=tree#diff-c2xhY2svd2ViL2NsYXNzZXMvYmxvY2tzLnB5) | `93.77% <100.00%> (+0.02%)` | :arrow_up: |
| [slack/web/classes/elements.py](https://codecov.io/gh/slackapi/python-slackclient/pull/844/diff?src=pr&el=tree#diff-c2xhY2svd2ViL2NsYXNzZXMvZWxlbWVudHMucHk=) | `87.96% <100.00%> (+0.03%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/slackapi/python-slackclient/pull/844?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/slackapi/python-slackclient/pull/844?src=pr&el=footer). Last update [c959e45...3758f50](https://codecov.io/gh/slackapi/python-slackclient/pull/844?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/slack/web/classes/blocks.py b/slack/web/classes/blocks.py
index 1f412d8..6bba870 100644
--- a/slack/web/classes/blocks.py
+++ b/slack/web/classes/blocks.py
@@ -280,7 +280,9 @@ class InputBlock(Block):
 
     @property
     def attributes(self) -> Set[str]:
-        return super().attributes.union({"label", "hint", "element", "optional"})
+        return super().attributes.union(
+            {"label", "hint", "element", "optional", "dispatch_action"}
+        )
 
     def __init__(
         self,
@@ -290,6 +292,7 @@ class InputBlock(Block):
         block_id: Optional[str] = None,
         hint: Optional[Union[str, dict, PlainTextObject]] = None,
         optional: Optional[bool] = None,
+        dispatch_action: Optional[bool] = None,
         **others: dict,
     ):
         """A block that collects information from users - it can hold a plain-text input element,
@@ -304,6 +307,7 @@ class InputBlock(Block):
         self.element = BlockElement.parse(element)
         self.hint = TextObject.parse(hint, default_type=PlainTextObject.type)
         self.optional = optional
+        self.dispatch_action = dispatch_action
 
     @JsonValidator(f"label attribute cannot exceed {label_max_length} characters")
     def _validate_label_length(self):
diff --git a/slack/web/classes/elements.py b/slack/web/classes/elements.py
index 1271b38..129900d 100644
--- a/slack/web/classes/elements.py
+++ b/slack/web/classes/elements.py
@@ -9,6 +9,7 @@ from . import EnumValidator, JsonObject, JsonValidator, show_unknown_key_warning
 from .objects import (
     ButtonStyles,
     ConfirmObject,
+    DispatchActionConfig,
     Option,
     OptionGroup,
     TextObject,
@@ -956,7 +957,13 @@ class PlainTextInputElement(InputInteractiveElement):
     @property
     def attributes(self) -> Set[str]:
         return super().attributes.union(
-            {"initial_value", "multiline", "min_length", "max_length"}
+            {
+                "initial_value",
+                "multiline",
+                "min_length",
+                "max_length",
+                "dispatch_action_config",
+            }
         )
 
     def __init__(
@@ -969,6 +976,7 @@ class PlainTextInputElement(InputInteractiveElement):
         multiline: Optional[bool] = None,
         min_length: Optional[int] = None,
         max_length: Optional[int] = None,
+        dispatch_action_config: Optional[Union[dict, DispatchActionConfig]] = None,
         **others: dict,
     ):
         """
@@ -988,6 +996,7 @@ class PlainTextInputElement(InputInteractiveElement):
         self.multiline = multiline
         self.min_length = min_length
         self.max_length = max_length
+        self.dispatch_action_config = dispatch_action_config
 
 
 # -------------------------------------------------
diff --git a/slack/web/classes/objects.py b/slack/web/classes/objects.py
index 50bb9f7..214deb8 100644
--- a/slack/web/classes/objects.py
+++ b/slack/web/classes/objects.py
@@ -540,3 +540,35 @@ class OptionGroup(JsonObject):
                 "label": dict_label,
                 "options": dict_options,
             }
+
+
+class DispatchActionConfig(JsonObject):
+    attributes = {"trigger_actions_on"}
+
+    @classmethod
+    def parse(cls, config: Union["DispatchActionConfig", dict]):
+        if config:
+            if isinstance(config, DispatchActionConfig):  # skipcq: PYL-R1705
+                return config
+            elif isinstance(config, dict):
+                return DispatchActionConfig(**config)
+            else:
+                # Not yet implemented: show some warning here
+                return None
+        return None
+
+    def __init__(
+        self, *, trigger_actions_on: Optional[list] = None,
+    ):
+        """
+        Determines when a plain-text input element will return a block_actions interaction payload.
+        https://api.slack.com/reference/block-kit/composition-objects#dispatch_action_config
+        """
+        self._trigger_actions_on = trigger_actions_on or []
+
+    def to_dict(self) -> dict:  # skipcq: PYL-W0221
+        self.validate_json()
+        json = {}
+        if self._trigger_actions_on:
+            json["trigger_actions_on"] = self._trigger_actions_on
+        return json


Test patch:
diff --git a/tests/web/classes/test_blocks.py b/tests/web/classes/test_blocks.py
index 547ac7e..8f69b64 100644
--- a/tests/web/classes/test_blocks.py
+++ b/tests/web/classes/test_blocks.py
@@ -607,6 +607,19 @@ class InputBlockTests(unittest.TestCase):
                     "emoji": True,
                 },
             },
+            {
+                "dispatch_action": True,
+                "type": "input",
+                "element": {
+                    "type": "plain_text_input",
+                    "action_id": "plain_text_input-action"
+                },
+                "label": {
+                    "type": "plain_text",
+                    "text": "Label",
+                    "emoji": True
+                }
+            }
         ]
         for input in blocks:
             self.assertDictEqual(input, InputBlock(**input).to_dict())
diff --git a/tests/web/classes/test_elements.py b/tests/web/classes/test_elements.py
index 012191d..ff2fdca 100644
--- a/tests/web/classes/test_elements.py
+++ b/tests/web/classes/test_elements.py
@@ -803,6 +803,16 @@ class PlainTextInputElementTests(unittest.TestCase):
         }
         self.assertDictEqual(input, PlainTextInputElement(**input).to_dict())
 
+    def test_document_3(self):
+        input = {
+            "type": "plain_text_input",
+            "multiline": True,
+            "dispatch_action_config": {
+                "trigger_actions_on": ["on_character_entered"]
+            }
+        }
+        self.assertDictEqual(input, PlainTextInputElement(**input).to_dict())
+
 
 # -------------------------------------------------
 # Radio Buttons