Skip to content
Merged
2 changes: 1 addition & 1 deletion dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pytest
pytest-parametrization>=2022.2.1
pre-commit
mypy

deepdiff
# MyPy stubs
types-requests
networkx-stubs
Expand Down
9 changes: 8 additions & 1 deletion elementary/messages/block_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
LineBlock,
LinesBlock,
LinkBlock,
MentionBlock,
TextBlock,
TextStyle,
)
Expand Down Expand Up @@ -41,7 +42,9 @@ def BulletListBlock(
icon_inline: InlineBlock = (
IconBlock(icon=icon) if isinstance(icon, Icon) else TextBlock(text=icon)
)
lines = [LineBlock(inlines=[icon_inline] + line.inlines) for line in lines]
lines = [
LineBlock(inlines=[icon_inline, *line.inlines], sep=line.sep) for line in lines
]
return LinesBlock(lines=lines)


Expand Down Expand Up @@ -138,3 +141,7 @@ def TitledParagraphBlock(

def JsonCodeBlock(*, content: Union[str, dict, list], indent: int = 2) -> CodeBlock:
return CodeBlock(text=json.dumps(content, indent=indent))


def MentionLineBlock(*users: str) -> LineBlock:
return LineBlock(inlines=[MentionBlock(user=user) for user in users], sep=", ")
35 changes: 27 additions & 8 deletions 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 Any, Dict, List, Optional, Union
from typing import Any, Dict, List, Optional, Sequence, Union

from pydantic import BaseModel
from typing_extensions import Literal
Expand Down Expand Up @@ -51,7 +51,32 @@ class IconBlock(BaseInlineTextBlock):
icon: Icon


InlineBlock = Union[TextBlock, LinkBlock, IconBlock]
class InlineCodeBlock(BaseInlineTextBlock):
type: Literal["inline_code"] = "inline_code"
code: str


class MentionBlock(BaseInlineTextBlock):
type: Literal["mention"] = "mention"
user: str


class LineBlock(BaseBlock):
type: Literal["line"] = "line"
inlines: Sequence["InlineBlock"]
sep: str = " "


InlineBlock = Union[
TextBlock,
LinkBlock,
IconBlock,
InlineCodeBlock,
MentionBlock,
"LineBlock",
]

LineBlock.update_forward_refs()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed to join a subsection of the line with a different sep (the owners with a ',')



class HeaderBlock(BaseBlock):
Expand All @@ -68,12 +93,6 @@ class DividerBlock(BaseBlock):
type: Literal["divider"] = "divider"


class LineBlock(BaseBlock):
type: Literal["line"] = "line"
inlines: List[InlineBlock]
sep: str = " "


class BaseLinesBlock(BaseBlock):
lines: List[LineBlock]

Expand Down
8 changes: 8 additions & 0 deletions elementary/messages/formats/adaptive_cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
Icon,
IconBlock,
InlineBlock,
InlineCodeBlock,
LineBlock,
LinesBlock,
LinkBlock,
MentionBlock,
TableBlock,
TextBlock,
TextStyle,
Expand Down Expand Up @@ -47,6 +49,12 @@ def format_inline_block(block: InlineBlock) -> str:
return format_text_block(block)
elif isinstance(block, LinkBlock):
return f"[{block.text}]({block.url})"
elif isinstance(block, InlineCodeBlock):
return block.code
elif isinstance(block, MentionBlock):
return block.user
elif isinstance(block, LineBlock):
return format_line_block_text(block)
else:
raise ValueError(f"Unsupported inline block type: {type(block)}")

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

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

Expand All @@ -14,9 +15,11 @@
Icon,
IconBlock,
InlineBlock,
InlineCodeBlock,
LineBlock,
LinesBlock,
LinkBlock,
MentionBlock,
TableBlock,
TextBlock,
TextStyle,
Expand All @@ -31,15 +34,26 @@
}


class FormattedBlockKitMessage(BaseModel):
blocks: List[dict]
attachments: List[dict]


ResolveMentionCallback = Callable[[str], Optional[str]]


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:
def __init__(
self, resolve_mention: Optional[ResolveMentionCallback] = None
) -> None:
self._blocks: List[dict] = []
self._attachment_blocks: List[dict] = []
self._is_divided = False
self._resolve_mention = resolve_mention or (lambda x: None)

def _format_icon(self, icon: Icon) -> str:
return ICON_TO_HTML[icon]
Expand All @@ -59,6 +73,16 @@ def _format_inline_block(self, block: InlineBlock) -> str:
return self._format_text_block(block)
elif isinstance(block, LinkBlock):
return f"<{block.url}|{block.text}>"
elif isinstance(block, InlineCodeBlock):
return f"`{block.code}`"
elif isinstance(block, MentionBlock):
resolved_user = self._resolve_mention(block.user)
if resolved_user:
return f"<@{resolved_user}>"
else:
return block.user
elif isinstance(block, LineBlock):
return self._format_line_block_text(block)
else:
raise ValueError(f"Unsupported inline block type: {type(block)}")

Expand Down Expand Up @@ -192,12 +216,6 @@ def _add_expandable_block(self, block: ExpandableBlock) -> None:
Expandable blocks are not supported in Slack Block Kit.
However, slack automatically collapses a large section block into an expandable block.
"""
self._add_block(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding the button label as a title in slack looked really bad, looks better without it

{
"type": "section",
"text": self._format_markdown_section_text(f"*{block.title}*"),
}
)
self._add_message_blocks(block.body)

def _add_message_block(self, block: MessageBlock) -> None:
Expand Down Expand Up @@ -239,25 +257,28 @@ def _get_final_blocks(
else:
return [], self._blocks

def build(self, message: MessageBody) -> Dict[str, Any]:
def build(self, message: MessageBody) -> FormattedBlockKitMessage:
self._blocks = []
self._attachment_blocks = []
self._add_message_blocks(message.blocks)
color_code = COLOR_MAP.get(message.color) if message.color else None
blocks, attachment_blocks = self._get_final_blocks(message.color)
built_message = {
"blocks": blocks,
"attachments": [
built_message = FormattedBlockKitMessage(
blocks=blocks,
attachments=[
{
"blocks": attachment_blocks,
}
],
}
)
if color_code:
built_message["attachments"][0]["color"] = color_code
for attachment in built_message.attachments:
attachment["color"] = color_code
return built_message


def format_block_kit(message: MessageBody) -> Dict[str, Any]:
builder = BlockKitBuilder()
def format_block_kit(
message: MessageBody, resolve_mention: Optional[ResolveMentionCallback] = None
) -> FormattedBlockKitMessage:
builder = BlockKitBuilder(resolve_mention)
return builder.build(message)
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ def reply_to_message(
body: MessageBody,
) -> MessageSendResult[MessageContextType]:
if not self.supports_reply():
raise MessageIntegrationReplyNotSupportedError
raise MessageIntegrationReplyNotSupportedError(type(self).__name__)
raise NotImplementedError
4 changes: 3 additions & 1 deletion elementary/messages/messaging_integrations/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ class MessagingIntegrationError(Exception):


class MessageIntegrationReplyNotSupportedError(MessagingIntegrationError):
pass
def __init__(self, integration_name: str):
self.integration_name = integration_name
super().__init__(f"{integration_name} does not support replying to messages")
Loading
Loading