diff --git a/slack_sdk/models/blocks/__init__.py b/slack_sdk/models/blocks/__init__.py index d2776a9dc..b8592c2e9 100644 --- a/slack_sdk/models/blocks/__init__.py +++ b/slack_sdk/models/blocks/__init__.py @@ -55,6 +55,7 @@ StaticSelectElement, TimePickerElement, UrlInputElement, + UrlSourceElement, UserMultiSelectElement, UserSelectElement, ) @@ -70,9 +71,11 @@ ImageBlock, InputBlock, MarkdownBlock, + PlanBlock, RichTextBlock, SectionBlock, TableBlock, + TaskCardBlock, VideoBlock, ) @@ -111,6 +114,7 @@ "PlainTextInputElement", "EmailInputElement", "UrlInputElement", + "UrlSourceElement", "NumberInputElement", "RadioButtonsElement", "SelectElement", @@ -135,8 +139,10 @@ "ImageBlock", "InputBlock", "MarkdownBlock", + "PlanBlock", "SectionBlock", "TableBlock", + "TaskCardBlock", "VideoBlock", "RichTextBlock", ] diff --git a/slack_sdk/models/blocks/block_elements.py b/slack_sdk/models/blocks/block_elements.py index 89f0a7994..dc6fb6304 100644 --- a/slack_sdk/models/blocks/block_elements.py +++ b/slack_sdk/models/blocks/block_elements.py @@ -1654,6 +1654,48 @@ def __init__( self.dispatch_action_config = dispatch_action_config +# ------------------------------------------------- +# Url Source Element +# ------------------------------------------------- + + +class UrlSourceElement(BlockElement): + type = "url" + + @property + def attributes(self) -> Set[str]: + return super().attributes.union( + { + "url", + "text", + "icon_url", + } + ) + + def __init__( + self, + *, + url: str, + text: str, + icon_url: Optional[str] = None, + **others: Dict, + ): + """ + A URL source element to reference in a task card block. + https://docs.slack.dev/reference/block-kit/block-elements/url-source-element + + Args: + url (required): The URL type source. + text (required): Display text for the URL. + icon_url: Optional icon URL to display with the source. + """ + super().__init__(type=self.type) + show_unknown_key_warning(self, others) + self.url = url + self.text = text + self.icon_url = icon_url + + # ------------------------------------------------- # Number Input Element # ------------------------------------------------- diff --git a/slack_sdk/models/blocks/blocks.py b/slack_sdk/models/blocks/blocks.py index cac463c99..1ad4b0a38 100644 --- a/slack_sdk/models/blocks/blocks.py +++ b/slack_sdk/models/blocks/blocks.py @@ -16,6 +16,7 @@ InputInteractiveElement, InteractiveElement, RichTextElement, + UrlSourceElement, ) # ------------------------------------------------- @@ -97,6 +98,10 @@ def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]: return RichTextBlock(**block) elif type == TableBlock.type: return TableBlock(**block) + elif type == TaskCardBlock.type: + return TaskCardBlock(**block) + elif type == PlanBlock.type: + return PlanBlock(**block) else: cls.logger.warning(f"Unknown block detected and skipped ({block})") return None @@ -777,3 +782,103 @@ def __init__( @JsonValidator("rows attribute must be specified") def _validate_rows(self): return self.rows is not None and len(self.rows) > 0 + + +class TaskCardBlock(Block): + type = "task_card" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "task_id", + "title", + "details", + "output", + "sources", + "status", + } + ) + + def __init__( + self, + *, + task_id: str, + title: str, + details: Optional[Union[RichTextBlock, dict]] = None, + output: Optional[Union[RichTextBlock, dict]] = None, + sources: Optional[Sequence[Union[UrlSourceElement, dict]]] = None, + status: str, # pending, in_progress, complete, error + block_id: Optional[str] = None, + **others: dict, + ): + """A discrete action or tool call. + https://docs.slack.dev/reference/block-kit/blocks/task-card-block/ + + Args: + block_id: A string acting as a unique identifier for a block. If not specified, one will be generated. + Maximum length for this field is 255 characters. + block_id should be unique for each message and each iteration of a message. + If a message is updated, use a new block_id. + task_id (required): ID for the task + title (required): Title of the task in plain text + details: Details of the task in the form of a single "rich_text" entity. + output: Output of the task in the form of a single "rich_text" entity. + sources: List of sources used to generate a response + """ + super().__init__(type=self.type, block_id=block_id) + show_unknown_key_warning(self, others) + + self.task_id = task_id + self.title = title + self.details = details + self.output = output + self.sources = sources + self.status = status + + @JsonValidator("status must be an expected value (pending, in_progress, complete, or error)") + def _validate_rows(self): + return self.status in ["pending", "in_progress", "complete", "error"] + + +class PlanBlock(Block): + type = "plan" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + { + "plan_id", + "title", + "tasks", + } + ) + + def __init__( + self, + *, + plan_id: str, + title: str, + tasks: Optional[Sequence[Union[Dict, TaskCardBlock]]] = None, + block_id: Optional[str] = None, + **others: dict, + ): + """A collection of related tasks. + https://docs.slack.dev/reference/block-kit/blocks/plan-block/ + + Args: + block_id: A string acting as a unique identifier for a block. If not specified, one will be generated. + Maximum length for this field is 255 characters. + block_id should be unique for each message and each iteration of a message. + If a message is updated, use a new block_id. + plan_id (required): ID for the plan (May be removed / made optional, feel free to pass in a random UUID + for now) + title (required): Title of the plan in plain text + tasks: Details of the task in the form of a single "rich_text" entity. + """ + super().__init__(type=self.type, block_id=block_id) + show_unknown_key_warning(self, others) + + self.plan_id = plan_id + self.title = title + self.tasks = tasks diff --git a/slack_sdk/models/messages/chunk.py b/slack_sdk/models/messages/chunk.py index 837714af0..b00daebd1 100644 --- a/slack_sdk/models/messages/chunk.py +++ b/slack_sdk/models/messages/chunk.py @@ -1,9 +1,9 @@ import logging -from typing import Any, Dict, Optional, Sequence, Set, Union +from typing import Dict, Optional, Sequence, Set, Union -from slack_sdk.errors import SlackObjectFormationError from slack_sdk.models import show_unknown_key_warning from slack_sdk.models.basic_objects import JsonObject +from slack_sdk.models.blocks.block_elements import UrlSourceElement class Chunk(JsonObject): @@ -67,44 +67,6 @@ def __init__( self.text = text -class URLSource(JsonObject): - type = "url" - - @property - def attributes(self) -> Set[str]: - return super().attributes.union( - { - "url", - "text", - "icon_url", - } - ) - - def __init__( - self, - *, - url: str, - text: str, - icon_url: Optional[str] = None, - **others: Dict, - ): - show_unknown_key_warning(self, others) - self._url = url - self._text = text - self._icon_url = icon_url - - def to_dict(self) -> Dict[str, Any]: - self.validate_json() - json: Dict[str, Union[str, Dict]] = { - "type": self.type, - "url": self._url, - "text": self._text, - } - if self._icon_url: - json["icon_url"] = self._icon_url - return json - - class TaskUpdateChunk(Chunk): type = "task_update" @@ -129,7 +91,7 @@ def __init__( status: str, # "pending", "in_progress", "complete", "error" details: Optional[str] = None, output: Optional[str] = None, - sources: Optional[Sequence[Union[Dict, URLSource]]] = None, + sources: Optional[Sequence[Union[Dict, UrlSourceElement]]] = None, **others: Dict, ): """Used for displaying tool execution progress in a timeline-style UI. @@ -144,12 +106,4 @@ def __init__( self.status = status self.details = details self.output = output - if sources is not None: - self.sources = [] - for src in sources: - if isinstance(src, Dict): - self.sources.append(src) - elif isinstance(src, URLSource): - self.sources.append(src.to_dict()) - else: - raise SlackObjectFormationError(f"Unsupported type for source in task update chunk: {type(src)}") + self.sources = sources diff --git a/tests/slack_sdk/models/test_chunks.py b/tests/slack_sdk/models/test_chunks.py index 1b8b58c96..202b4bf77 100644 --- a/tests/slack_sdk/models/test_chunks.py +++ b/tests/slack_sdk/models/test_chunks.py @@ -1,6 +1,7 @@ import unittest -from slack_sdk.models.messages.chunk import MarkdownTextChunk, TaskUpdateChunk, URLSource +from slack_sdk.models.blocks.block_elements import UrlSourceElement +from slack_sdk.models.messages.chunk import MarkdownTextChunk, TaskUpdateChunk class MarkdownTextChunkTests(unittest.TestCase): @@ -47,7 +48,11 @@ def test_json(self): status="complete", output="Found a solution", sources=[ - URLSource( + UrlSourceElement( + text="Discussion of Life's Questions", + url="https://www.answers.com", + ), + UrlSourceElement( text="The Free Encyclopedia", url="https://wikipedia.org", icon_url="https://example.com/globe.png", @@ -61,6 +66,11 @@ def test_json(self): "status": "complete", "output": "Found a solution", "sources": [ + { + "type": "url", + "text": "Discussion of Life's Questions", + "url": "https://www.answers.com", + }, { "type": "url", "text": "The Free Encyclopedia",