From bc44e402b905b4eb718692d5392254347734f355 Mon Sep 17 00:00:00 2001 From: Eden Zimbelman Date: Fri, 26 Jun 2026 16:57:43 -0700 Subject: [PATCH] feat(block-kit): add data table block example Add a data_table Block Kit example mirroring the official docs example, including a typed DataTableBlock (net-new; not yet in slack_sdk), a unit test asserting the produced block, and a README entry. Co-Authored-By: Claude --- block-kit/README.md | 1 + block-kit/src/blocks/data_table.py | 130 ++++++++++++++++++++++ block-kit/tests/blocks/test_data_table.py | 78 +++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 block-kit/src/blocks/data_table.py create mode 100644 block-kit/tests/blocks/test_data_table.py diff --git a/block-kit/README.md b/block-kit/README.md index ee77ff0..4954f09 100644 --- a/block-kit/README.md +++ b/block-kit/README.md @@ -11,6 +11,7 @@ Read the [docs](https://docs.slack.dev/block-kit/) to learn concepts behind thes - **[Actions](https://docs.slack.dev/reference/block-kit/blocks/actions-block)**: Holds multiple interactive elements. [Implementation](./src/blocks/actions.py). - **[Context](https://docs.slack.dev/reference/block-kit/blocks/context-block)**: Provides contextual info, which can include both images and text. [Implementation](./src/blocks/context.py). - **[Context actions](https://docs.slack.dev/reference/block-kit/blocks/context-actions-block)**: Displays actions as contextual info, which can include both feedback buttons and icon buttons. [Implementation](./src/blocks/context_actions.py). +- **[Data table](https://docs.slack.dev/reference/block-kit/blocks/data-table-block)**: Displays a rich table that supports pagination, sorting, and filtering. [Implementation](./src/blocks/data_table.py). - **[Divider](https://docs.slack.dev/reference/block-kit/blocks/divider-block)**: Visually separates pieces of info inside of a message. [Implementation](./src/blocks/divider.py). - **[File](https://docs.slack.dev/reference/block-kit/blocks/file-block)**: Displays info about remote files. [Implementation](./src/blocks/file.py). - **[Header](https://docs.slack.dev/reference/block-kit/blocks/header-block)**: Displays a larger-sized text. [Implementation](./src/blocks/header.py). diff --git a/block-kit/src/blocks/data_table.py b/block-kit/src/blocks/data_table.py new file mode 100644 index 0000000..880bd1b --- /dev/null +++ b/block-kit/src/blocks/data_table.py @@ -0,0 +1,130 @@ +from typing import Any, Dict, Optional, Sequence, Set + +from slack_sdk.models.basic_objects import JsonValidator +from slack_sdk.models.blocks import Block + + +class DataTableBlock(Block): + type = "data_table" + + @property + def attributes(self) -> Set[str]: # type: ignore[override] + return super().attributes.union( + {"rows", "caption", "page_size", "row_header_column_index"} + ) + + def __init__( + self, + *, + rows: Sequence[Sequence[Dict[str, Any]]], + caption: str, + page_size: Optional[int] = None, + row_header_column_index: Optional[int] = None, + block_id: Optional[str] = None, + ): + """Displays a rich table that supports pagination, sorting, and filtering. + https://docs.slack.dev/reference/block-kit/blocks/data-table-block + + Args: + rows (required): An array consisting of table rows. The first row is always a + header row. Tables have between 2 and 101 rows and between 1 and 20 columns. + Cells can have a type of raw_text, raw_number, or rich_text, but the header + row does not allow rich_text cells. + caption (required): A caption describing the contents of the table. + page_size: The number of rows to display per page, between 1 and 100. Defaults to 5. + row_header_column_index: The zero-based index of the column that identifies row + headers. Defaults to 0. + block_id: A unique identifier for a block. If not specified, a block_id will be + generated. Maximum length for this field is 255 characters. + """ + super().__init__(type=self.type, block_id=block_id) + self.rows = rows + self.caption = caption + self.page_size = page_size + self.row_header_column_index = row_header_column_index + + @JsonValidator("rows attribute must be specified") + def _validate_rows(self) -> bool: + return self.rows is not None and len(self.rows) > 0 + + @JsonValidator("caption attribute must be specified") + def _validate_caption(self) -> bool: + return self.caption is not None and len(self.caption) > 0 + + +def example01() -> DataTableBlock: + """ + Displays a rich table that supports pagination, sorting, and filtering. + https://docs.slack.dev/reference/block-kit/blocks/data-table-block/ + + A data table with raw text and rich text cells alongside a caption. + """ + block = DataTableBlock( + caption="A Fabulous Table", + rows=[ + [ + {"type": "raw_text", "text": "Name"}, + {"type": "raw_text", "text": "Department"}, + {"type": "raw_text", "text": "Badge"}, + ], + [ + {"type": "raw_text", "text": "Data Refinement Department"}, + {"type": "raw_text", "text": "MDR"}, + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Blue", + "style": {"bold": True}, + } + ], + } + ], + }, + ], + [ + {"type": "raw_text", "text": "Art Sourcing Department"}, + {"type": "raw_text", "text": "O&D"}, + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + {"type": "text", "text": "Green"}, + { + "type": "text", + "text": "review", + "style": {"italic": True}, + }, + ], + } + ], + }, + ], + [ + {"type": "raw_text", "text": "Wellness Department"}, + {"type": "raw_text", "text": "Wellness Center"}, + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Limited", + "style": {"bold": True}, + } + ], + } + ], + }, + ], + ], + ) + return block diff --git a/block-kit/tests/blocks/test_data_table.py b/block-kit/tests/blocks/test_data_table.py new file mode 100644 index 0000000..9752d4e --- /dev/null +++ b/block-kit/tests/blocks/test_data_table.py @@ -0,0 +1,78 @@ +import json + +from src.blocks import data_table + + +def test_example01(): + block = data_table.example01() + actual = block.to_dict() + expected = { + "type": "data_table", + "caption": "A Fabulous Table", + "rows": [ + [ + {"type": "raw_text", "text": "Name"}, + {"type": "raw_text", "text": "Department"}, + {"type": "raw_text", "text": "Badge"}, + ], + [ + {"type": "raw_text", "text": "Data Refinement Department"}, + {"type": "raw_text", "text": "MDR"}, + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Blue", + "style": {"bold": True}, + } + ], + } + ], + }, + ], + [ + {"type": "raw_text", "text": "Art Sourcing Department"}, + {"type": "raw_text", "text": "O&D"}, + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + {"type": "text", "text": "Green"}, + { + "type": "text", + "text": "review", + "style": {"italic": True}, + }, + ], + } + ], + }, + ], + [ + {"type": "raw_text", "text": "Wellness Department"}, + {"type": "raw_text", "text": "Wellness Center"}, + { + "type": "rich_text", + "elements": [ + { + "type": "rich_text_section", + "elements": [ + { + "type": "text", + "text": "Limited", + "style": {"bold": True}, + } + ], + } + ], + }, + ], + ], + } + assert json.dumps(actual, sort_keys=True) == json.dumps(expected, sort_keys=True)