Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Table Block #1826

Merged
merged 7 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ types-PyYAML
types-setuptools
types-retry
types-decorator
types-tabulate
18 changes: 17 additions & 1 deletion elementary/messages/blocks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from enum import Enum
from typing import List, Optional, Union
from typing import Any, Dict, List, Optional, Union

from pydantic import BaseModel
from typing_extensions import Literal
Expand Down Expand Up @@ -94,6 +94,21 @@ class FactListBlock(BaseBlock):
facts: List[FactBlock]


class TableBlock(BaseBlock):
type: Literal["table"] = "table"
headers: List[str]
rows: List[List[Any]]

@classmethod
def from_dicts(cls, data: List[Dict[str, Any]]) -> "TableBlock":
if not data:
return cls(headers=[], rows=[])

headers = list(data[0].keys())
rows = [[row.get(header) for header in headers] for row in data]
return cls(headers=headers, rows=rows)


class ExpandableBlock(BaseBlock):
type: Literal["expandable"] = "expandable"
title: str
Expand All @@ -107,6 +122,7 @@ class ExpandableBlock(BaseBlock):
DividerBlock,
LinesBlock,
FactListBlock,
TableBlock,
"ExpandableBlock",
]

Expand Down
28 changes: 28 additions & 0 deletions elementary/messages/formats/adaptive_cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
LineBlock,
LinesBlock,
LinkBlock,
TableBlock,
TextBlock,
TextStyle,
)
Expand Down Expand Up @@ -118,6 +119,31 @@ def format_fact_list_block(block: FactListBlock) -> Dict[str, Any]:
}


def format_table_block(block: TableBlock) -> Dict[str, Any]:
return {
"type": "Table",
"columns": [{"width": 1} for _ in block.headers],
"rows": [
{
"type": "TableRow",
"cells": [
{
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": str(cell),
}
],
}
for cell in row
],
}
for row in [block.headers, *block.rows]
],
}


def format_message_block(
block: MessageBlock, color: Optional[Color] = None
) -> List[Dict[str, Any]]:
Expand All @@ -131,6 +157,8 @@ def format_message_block(
return [format_fact_list_block(block)]
elif isinstance(block, ExpandableBlock):
return format_expandable_block(block)
elif isinstance(block, TableBlock):
return [format_table_block(block)]
else:
raise ValueError(f"Unsupported message block type: {type(block)}")

Expand Down
32 changes: 32 additions & 0 deletions elementary/messages/formats/block_kit.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
from typing import Any, Dict, List, Optional, Tuple

from slack_sdk.models import blocks as slack_blocks
from tabulate import tabulate

from elementary.messages.blocks import (
CodeBlock,
Expand All @@ -15,6 +17,7 @@
LineBlock,
LinesBlock,
LinkBlock,
TableBlock,
TextBlock,
TextStyle,
)
Expand All @@ -31,6 +34,7 @@
class BlockKitBuilder:
_SECONDARY_FACT_CHUNK_SIZE = 2
_LONGEST_MARKDOWN_SUFFIX_LEN = 3 # length of markdown's code suffix (```)
_MAX_CELL_LENGTH_BY_COLUMN_COUNT = {4: 11, 3: 14, 2: 22, 1: 40, 0: 40}

def __init__(self) -> None:
self._blocks: List[dict] = []
Expand Down Expand Up @@ -63,6 +67,13 @@ def _format_line_block_text(self, block: LineBlock) -> str:
[self._format_inline_block(inline) for inline in block.inlines]
)

def _format_table_cell(self, cell_value: Any, column_count: int) -> str:
value = str(cell_value)
max_cell_length = self._MAX_CELL_LENGTH_BY_COLUMN_COUNT[column_count]
if len(value) > max_cell_length:
return value[: max_cell_length - 2] + ".."
return value

def _format_markdown_section_text(self, text: str) -> dict:
if len(text) > slack_blocks.SectionBlock.text_max_length:
text = (
Expand Down Expand Up @@ -157,6 +168,25 @@ def _add_divider_block(self, block: DividerBlock) -> None:
self._add_block({"type": "divider"})
self._is_divided = True

def _add_table_block(self, block: TableBlock) -> None:
column_count = len(block.headers)
if column_count not in self._MAX_CELL_LENGTH_BY_COLUMN_COUNT:
dicts = [
{header: cell for header, cell in zip(block.headers, row)}
for row in block.rows
]
table_text = json.dumps(dicts, indent=2)
else:
new_rows = [
[self._format_table_cell(cell, column_count) for cell in row]
for row in block.rows
]
new_headers = [
self._format_table_cell(cell, column_count) for cell in block.headers
]
table_text = tabulate(new_rows, headers=new_headers, tablefmt="simple")
self._add_block(self._format_markdown_section(f"```{table_text}```"))

def _add_expandable_block(self, block: ExpandableBlock) -> None:
"""
Expandable blocks are not supported in Slack Block Kit.
Expand All @@ -183,6 +213,8 @@ def _add_message_block(self, block: MessageBlock) -> None:
self._add_divider_block(block)
elif isinstance(block, ExpandableBlock):
self._add_expandable_block(block)
elif isinstance(block, TableBlock):
self._add_table_block(block)
else:
raise ValueError(f"Unsupported message block type: {type(block)}")

Expand Down
2 changes: 2 additions & 0 deletions elementary/messages/message_body.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
FactListBlock,
HeaderBlock,
LinesBlock,
TableBlock,
)


Expand All @@ -26,6 +27,7 @@ class Color(Enum):
LinesBlock,
FactListBlock,
ExpandableBlock,
TableBlock,
]


Expand Down
12 changes: 9 additions & 3 deletions elementary/monitor/alerts/alert_messages/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
LineBlock,
LinesBlock,
LinkBlock,
TableBlock,
TextBlock,
TextStyle,
)
Expand Down Expand Up @@ -284,9 +285,14 @@ def _get_result_blocks(
]
)
)
result_blocks.append(
JsonCodeBlock(content=result_sample),
)
if isinstance(result_sample, list) and len(result_sample[0].keys()) <= 4:
result_blocks.append(
TableBlock.from_dicts(result_sample),
)
else:
result_blocks.append(
JsonCodeBlock(content=result_sample),
)
if result_query:
result_blocks.append(
LinesBlock(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,59 @@
"wrap": true
},
{
"type": "Container",
"style": "emphasis",
"items": [
"type": "Table",
"columns": [
{
"type": "RichTextBlock",
"inlines": [
"width": 1
},
{
"width": 1
}
],
"rows": [
{
"type": "TableRow",
"cells": [
{
"type": "TextRun",
"text": "{\n \"column1\": \"value1\",\n \"column2\": \"value2\"\n}",
"fontType": "Monospace"
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": "column1"
}
]
},
{
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": "column2"
}
]
}
]
},
{
"type": "TableRow",
"cells": [
{
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": "value1"
}
]
},
{
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": "value2"
}
]
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,59 @@
"wrap": true
},
{
"type": "Container",
"style": "emphasis",
"items": [
"type": "Table",
"columns": [
{
"type": "RichTextBlock",
"inlines": [
"width": 1
},
{
"width": 1
}
],
"rows": [
{
"type": "TableRow",
"cells": [
{
"type": "TextRun",
"text": "{\n \"column1\": \"value1\",\n \"column2\": \"value2\"\n}",
"fontType": "Monospace"
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": "column1"
}
]
},
{
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": "column2"
}
]
}
]
},
{
"type": "TableRow",
"cells": [
{
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": "value1"
}
]
},
{
"type": "TableCell",
"items": [
{
"type": "TextBlock",
"text": "value2"
}
]
}
]
}
Expand Down
Loading
Loading