Repository: slackapi/python-slack-sdk
Pull request #1399 (2023-09-08 06:20:01)

Add workflow_button block element
## Summary

This pull request adds a new block element (https://api.slack.com/reference/block-kit/block-elements#workflow_button) to slack_sdk.models package.

### Category (place an `x` in each of the `[ ]`)

- [ ] **slack_sdk.web.WebClient (sync/async)** (Web API client)
- [ ] **slack_sdk.webhook.WebhookClient (sync/async)** (Incoming Webhook, response_url sender)
- [ ] **slack_sdk.socket_mode** (Socket Mode client)
- [ ] **slack_sdk.signature** (Request Signature Verifier)
- [ ] **slack_sdk.oauth** (OAuth Flow Utilities)
- [x] **slack_sdk.models** (UI component builders)
- [ ] **slack_sdk.scim** (SCIM API client)
- [ ] **slack_sdk.audit_logs** (Audit Logs API client)
- [ ] **slack_sdk.rtm_v2** (RTM client)
- [ ] `/docs-src` (Documents, have you run `./scripts/docs.sh`?)
- [ ] `/docs-src-v2` (Documents, have you run `./scripts/docs-v2.sh`?)
- [ ] `/tutorial` (PythOnBoardingBot tutorial)
- [x] `tests`/`integration_tests` (Automated tests for this library)

## Requirements (place an `x` in each `[ ]`)

- [x] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slack-sdk/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 `python3 -m venv .venv && source .venv/bin/activate && ./scripts/run_validation.sh` after making the changes.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/slackapi/python-slack-sdk/pull/1399?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi) Report
> Merging [#1399](https://app.codecov.io/gh/slackapi/python-slack-sdk/pull/1399?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi) (626bf34) into [main](https://app.codecov.io/gh/slackapi/python-slack-sdk/commit/d468b354ca8d37702198c1c26ee6fd77133dfb71?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi) (d468b35) will **increase** coverage by `0.06%`.
> The diff coverage is `97.14%`.

```diff
@@            Coverage Diff             @@
##             main    #1399      +/-   ##
==========================================
+ Coverage   85.34%   85.41%   +0.06%     
==========================================
  Files         111      111              
  Lines       11786    11820      +34     
==========================================
+ Hits        10059    10096      +37     
+ Misses       1727     1724       -3     
```


| [Files Changed](https://app.codecov.io/gh/slackapi/python-slack-sdk/pull/1399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi) | Coverage Δ | |
|---|---|---|
| [slack\_sdk/models/blocks/basic\_components.py](https://app.codecov.io/gh/slackapi/python-slack-sdk/pull/1399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi#diff-c2xhY2tfc2RrL21vZGVscy9ibG9ja3MvYmFzaWNfY29tcG9uZW50cy5weQ==) | `87.76% <95.45%> (+0.64%)` | :arrow_up: |
| [slack\_sdk/models/blocks/block\_elements.py](https://app.codecov.io/gh/slackapi/python-slack-sdk/pull/1399?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi#diff-c2xhY2tfc2RrL21vZGVscy9ibG9ja3MvYmxvY2tfZWxlbWVudHMucHk=) | `95.53% <100.00%> (+0.12%)` | :arrow_up: |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/slackapi/python-slack-sdk/pull/1399/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi)

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi)

:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=slackapi).

Patch:
diff --git a/slack_sdk/models/blocks/basic_components.py b/slack_sdk/models/blocks/basic_components.py
index 1e708ff..3999b91 100644
--- a/slack_sdk/models/blocks/basic_components.py
+++ b/slack_sdk/models/blocks/basic_components.py
@@ -524,3 +524,38 @@ class DispatchActionConfig(JsonObject):
         if self._trigger_actions_on:
             json["trigger_actions_on"] = self._trigger_actions_on
         return json
+
+
+class WorkflowTrigger(JsonObject):
+    attributes = {"trigger"}
+
+    def __init__(self, *, url: str, customizable_input_parameters: Optional[List[Dict[str, str]]] = None):
+        self._url = url
+        self._customizable_input_parameters = customizable_input_parameters
+
+    def to_dict(self) -> Dict[str, Any]:  # skipcq: PYL-W0221
+        self.validate_json()
+        json = {"url": self._url}
+        if self._customizable_input_parameters is not None:
+            json.update({"customizable_input_parameters": self._customizable_input_parameters})
+        return json
+
+
+class Workflow(JsonObject):
+    attributes = {"trigger"}
+
+    def __init__(
+        self,
+        *,
+        trigger: Union[WorkflowTrigger, dict],
+    ):
+        self._trigger = trigger
+
+    def to_dict(self) -> Dict[str, Any]:  # skipcq: PYL-W0221
+        self.validate_json()
+        json = {}
+        if isinstance(self._trigger, WorkflowTrigger):
+            json["trigger"] = self._trigger.to_dict()
+        else:
+            json["trigger"] = self._trigger
+        return json
diff --git a/slack_sdk/models/blocks/block_elements.py b/slack_sdk/models/blocks/block_elements.py
index 65157e1..4cf1519 100644
--- a/slack_sdk/models/blocks/block_elements.py
+++ b/slack_sdk/models/blocks/block_elements.py
@@ -11,7 +11,7 @@ from slack_sdk.models.basic_objects import (
     JsonValidator,
     EnumValidator,
 )
-from .basic_components import ButtonStyles
+from .basic_components import ButtonStyles, Workflow
 from .basic_components import ConfirmObject
 from .basic_components import DispatchActionConfig
 from .basic_components import MarkdownTextObject
@@ -1689,3 +1689,60 @@ class OverflowMenuElement(InteractiveElement):
     @JsonValidator(f"options attribute must have between {options_min_length} " f"and {options_max_length} items")
     def _validate_options_length(self) -> bool:
         return self.options_min_length <= len(self.options) <= self.options_max_length
+
+
+# -------------------------------------------------
+# Workflow Button
+# -------------------------------------------------
+
+
+class WorkflowButtonElement(InteractiveElement):
+    type = "workflow_button"
+
+    @property
+    def attributes(self) -> Set[str]:
+        return super().attributes.union({"text", "workflow", "style", "accessibility_label"})
+
+    def __init__(
+        self,
+        *,
+        text: Union[str, dict, TextObject],
+        action_id: Optional[str] = None,
+        workflow: Optional[Union[dict, Workflow]] = None,
+        style: Optional[str] = None,  # primary, danger
+        accessibility_label: Optional[str] = None,
+        **others: dict,
+    ):
+        """Allows users to run a link trigger with customizable inputs
+        Interactive component - but interactions with workflow button elements will not send block_actions events,
+        since these are used to start new workflow runs.
+        https://api.slack.com/reference/block-kit/block-elements#workflow_button
+
+        Args:
+            text (required): A text object that defines the button's text.
+                Can only be of type: plain_text. text may truncate with ~30 characters.
+                Maximum length for the text in this field is 75 characters.
+            action_id (required): An identifier for this action.
+                You can use this when you receive an interaction payload to identify the source of the action.
+                Should be unique among all other action_ids in the containing block.
+                Maximum length for this field is 255 characters.
+            workflow: A workflow object that contains details about the workflow
+                that will run when the button is clicked.
+            style: Decorates buttons with alternative visual color schemes. Use this option with restraint.
+                "primary" gives buttons a green outline and text, ideal for affirmation or confirmation actions.
+                "primary" should only be used for one button within a set.
+                "danger" gives buttons a red outline and text, and should be used when the action is destructive.
+                Use "danger" even more sparingly than "primary".
+                If you don't include this field, the default button style will be used.
+            accessibility_label: A label for longer descriptive text about a button element.
+                This label will be read out by screen readers instead of the button text object.
+                Maximum length for this field is 75 characters.
+        """
+        super().__init__(action_id=action_id, type=self.type)
+        show_unknown_key_warning(self, others)
+
+        # NOTE: default_type=PlainTextObject.type here is only for backward-compatibility with version 2.5.0
+        self.text = TextObject.parse(text, default_type=PlainTextObject.type)
+        self.workflow = workflow
+        self.style = style
+        self.accessibility_label = accessibility_label


Test patch:
diff --git a/tests/slack_sdk/models/test_elements.py b/tests/slack_sdk/models/test_elements.py
index 906147c..1b246b0 100644
--- a/tests/slack_sdk/models/test_elements.py
+++ b/tests/slack_sdk/models/test_elements.py
@@ -33,6 +33,7 @@ from slack_sdk.models.blocks.block_elements import (
     EmailInputElement,
     NumberInputElement,
     UrlInputElement,
+    WorkflowButtonElement,
 )
 from . import STRING_3001_CHARS, STRING_301_CHARS
 
@@ -1213,3 +1214,26 @@ class RadioButtonsElementTest(unittest.TestCase):
             "focus_on_load": True,
         }
         self.assertDictEqual(input, RadioButtonsElement(**input).to_dict())
+
+
+# -------------------------------------------------
+# Workflow Button
+# -------------------------------------------------
+
+
+class WorkflowButtonElementTests(unittest.TestCase):
+    def test_load(self):
+        input = {
+            "type": "workflow_button",
+            "text": {"type": "plain_text", "text": "Run Workflow"},
+            "workflow": {
+                "trigger": {
+                    "url": "https://slack.com/shortcuts/Ft0123ABC456/xyz...zyx",
+                    "customizable_input_parameters": [
+                        {"name": "input_parameter_a", "value": "Value for input param A"},
+                        {"name": "input_parameter_b", "value": "Value for input param B"},
+                    ],
+                }
+            },
+        }
+        self.assertDictEqual(input, WorkflowButtonElement(**input).to_dict())
diff --git a/tests/slack_sdk/models/test_objects.py b/tests/slack_sdk/models/test_objects.py
index 532cddd..383abee 100644
--- a/tests/slack_sdk/models/test_objects.py
+++ b/tests/slack_sdk/models/test_objects.py
@@ -11,6 +11,7 @@ from slack_sdk.models.blocks import (
     OptionGroup,
     PlainTextObject,
 )
+from slack_sdk.models.blocks.basic_components import Workflow, WorkflowTrigger
 from slack_sdk.models.messages import (
     ChannelLink,
     DateLink,
@@ -566,3 +567,28 @@ class OptionGroupTests(unittest.TestCase):
                     "style": "something-wrong",
                 }
             ).validate_json()
+
+
+class WorkflowTests(unittest.TestCase):
+    def test_creation(self):
+        workflow = Workflow(
+            trigger=WorkflowTrigger(
+                url="https://slack.com/shortcuts/Ft0123ABC456/xyz...zyx",
+                customizable_input_parameters=[
+                    {"name": "input_parameter_a", "value": "Value for input param A"},
+                    {"name": "input_parameter_b", "value": "Value for input param B"},
+                ],
+            )
+        )
+        self.assertDictEqual(
+            workflow.to_dict(),
+            {
+                "trigger": {
+                    "url": "https://slack.com/shortcuts/Ft0123ABC456/xyz...zyx",
+                    "customizable_input_parameters": [
+                        {"name": "input_parameter_a", "value": "Value for input param A"},
+                        {"name": "input_parameter_b", "value": "Value for input param B"},
+                    ],
+                }
+            },
+        )