|
2 | 2 | from typing import Any, Callable, List, Optional, Tuple |
3 | 3 |
|
4 | 4 | from slack_sdk.models import blocks as slack_blocks |
5 | | -from tabulate import tabulate |
6 | 5 |
|
7 | 6 | from elementary.messages.blocks import ( |
8 | 7 | ActionBlock, |
@@ -50,14 +49,16 @@ class FormattedBlockKitMessage(BaseModel): |
50 | 49 | class BlockKitBuilder: |
51 | 50 | _SECONDARY_FACT_CHUNK_SIZE = 2 |
52 | 51 | _LONGEST_MARKDOWN_SUFFIX_LEN = 3 # length of markdown's code suffix (```) |
53 | | - _MAX_CELL_LENGTH_BY_COLUMN_COUNT = {4: 11, 3: 14, 2: 22, 1: 40, 0: 40} |
| 52 | + _MAX_SLACK_TABLE_COLUMNS = 20 |
| 53 | + _MAX_SLACK_TABLE_ROWS = 100 |
54 | 54 |
|
55 | 55 | def __init__( |
56 | 56 | self, resolve_mention: Optional[ResolveMentionCallback] = None |
57 | 57 | ) -> None: |
58 | 58 | self._blocks: List[dict] = [] |
59 | 59 | self._attachment_blocks: List[dict] = [] |
60 | 60 | self._is_divided = False |
| 61 | + self._has_table_block = False |
61 | 62 | self._resolve_mention = resolve_mention or (lambda x: None) |
62 | 63 |
|
63 | 64 | def _format_icon(self, icon: Icon) -> str: |
@@ -98,13 +99,6 @@ def _format_line_block_text(self, block: LineBlock) -> str: |
98 | 99 | [self._format_inline_block(inline) for inline in block.inlines] |
99 | 100 | ) |
100 | 101 |
|
101 | | - def _format_table_cell(self, cell_value: Any, column_count: int) -> str: |
102 | | - value = str(cell_value) |
103 | | - max_cell_length = self._MAX_CELL_LENGTH_BY_COLUMN_COUNT[column_count] |
104 | | - if len(value) > max_cell_length: |
105 | | - return value[: max_cell_length - 2] + ".." |
106 | | - return value |
107 | | - |
108 | 102 | def _format_markdown_section_text(self, text: str) -> dict: |
109 | 103 | if len(text) > slack_blocks.SectionBlock.text_max_length: |
110 | 104 | text = ( |
@@ -257,24 +251,50 @@ def _add_divider_block(self, block: DividerBlock) -> None: |
257 | 251 | self._add_block({"type": "divider"}) |
258 | 252 | self._is_divided = True |
259 | 253 |
|
| 254 | + def _make_header_cell(self, text: str) -> dict: |
| 255 | + return { |
| 256 | + "type": "rich_text", |
| 257 | + "elements": [ |
| 258 | + { |
| 259 | + "type": "rich_text_section", |
| 260 | + "elements": [ |
| 261 | + {"type": "text", "text": str(text), "style": {"bold": True}} |
| 262 | + ], |
| 263 | + } |
| 264 | + ], |
| 265 | + } |
| 266 | + |
| 267 | + def _make_data_cell(self, value: Any) -> dict: |
| 268 | + text = str(value) if value is not None else "NULL" |
| 269 | + return {"type": "raw_text", "text": text or " "} |
| 270 | + |
260 | 271 | def _add_table_block(self, block: TableBlock) -> None: |
261 | 272 | column_count = len(block.headers) |
262 | | - if column_count not in self._MAX_CELL_LENGTH_BY_COLUMN_COUNT: |
| 273 | + |
| 274 | + if column_count > self._MAX_SLACK_TABLE_COLUMNS or self._has_table_block: |
263 | 275 | dicts = [ |
264 | 276 | {header: cell for header, cell in zip(block.headers, row)} |
265 | 277 | for row in block.rows |
266 | 278 | ] |
267 | | - table_text = json.dumps(dicts, indent=2) |
268 | | - else: |
269 | | - new_rows = [ |
270 | | - [self._format_table_cell(cell, column_count) for cell in row] |
271 | | - for row in block.rows |
272 | | - ] |
273 | | - new_headers = [ |
274 | | - self._format_table_cell(cell, column_count) for cell in block.headers |
275 | | - ] |
276 | | - table_text = tabulate(new_rows, headers=new_headers, tablefmt="simple") |
277 | | - self._add_block(self._format_markdown_section(f"```{table_text}```")) |
| 279 | + self._add_block( |
| 280 | + self._format_markdown_section(f"```{json.dumps(dicts, indent=2)}```") |
| 281 | + ) |
| 282 | + return |
| 283 | + |
| 284 | + rows: List[List[dict]] = [[self._make_header_cell(h) for h in block.headers]] |
| 285 | + for row in block.rows[: self._MAX_SLACK_TABLE_ROWS - 1]: |
| 286 | + rows.append([self._make_data_cell(v) for v in row]) |
| 287 | + |
| 288 | + self._add_block( |
| 289 | + { |
| 290 | + "type": "table", |
| 291 | + "rows": rows, |
| 292 | + "column_settings": [ |
| 293 | + {"align": "left", "is_wrapped": False} for _ in block.headers |
| 294 | + ], |
| 295 | + } |
| 296 | + ) |
| 297 | + self._has_table_block = True |
278 | 298 |
|
279 | 299 | def _add_actions_block(self, block: ActionsBlock) -> None: |
280 | 300 | self._add_block( |
@@ -337,6 +357,7 @@ def _get_final_blocks( |
337 | 357 | def build(self, message: MessageBody) -> FormattedBlockKitMessage: |
338 | 358 | self._blocks = [] |
339 | 359 | self._attachment_blocks = [] |
| 360 | + self._has_table_block = False |
340 | 361 | self._add_message_blocks(message.blocks) |
341 | 362 | color_code = COLOR_MAP.get(message.color) if message.color else None |
342 | 363 | blocks, attachment_blocks = self._get_final_blocks(message.color) |
|
0 commit comments