Skip to content

Commit

Permalink
Merge pull request #1785 from elementary-data/ele-3960-adaptive-cards
Browse files Browse the repository at this point in the history
Ele 3960 adaptive cards
  • Loading branch information
MikaKerman authored Jan 27, 2025
2 parents 066e712 + cc614cf commit 9c055a1
Show file tree
Hide file tree
Showing 27 changed files with 1,454 additions and 0 deletions.
Empty file.
229 changes: 229 additions & 0 deletions elementary/messages/formats/adaptive_cards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import uuid
from typing import Any, Dict, List, Optional

from elementary.messages.blocks import (
CodeBlock,
DividerBlock,
ExpandableBlock,
FactListBlock,
HeaderBlock,
Icon,
IconBlock,
InlineBlock,
LineBlock,
LinesBlock,
LinkBlock,
TextBlock,
TextStyle,
)
from elementary.messages.message_body import Color, MessageBlock, MessageBody

ICON_TO_HTML = {
Icon.RED_TRIANGLE: "🔺",
Icon.X: "❌",
Icon.WARNING: "⚠️",
Icon.EXCLAMATION: "❗",
Icon.CHECK: "✅",
Icon.MAGNIFYING_GLASS: "🔎",
Icon.HAMMER_AND_WRENCH: "🛠️",
Icon.POLICE_LIGHT: "🚨",
Icon.INFO: "ℹ️",
Icon.EYE: "👁️",
Icon.GEAR: "⚙️",
Icon.BELL: "🔔",
}

COLOR_TO_STYLE = {
Color.RED: "Attention",
Color.YELLOW: "Warning",
Color.GREEN: "Good",
}


def format_icon(icon: Icon) -> str:
return ICON_TO_HTML[icon]


def format_text_block(block: TextBlock) -> str:
if block.style == TextStyle.BOLD:
return f"**{block.text}**"
elif block.style == TextStyle.ITALIC:
return f"_{block.text}_"
else:
return block.text


def format_inline_block(block: InlineBlock) -> str:
if isinstance(block, IconBlock):
return format_icon(block.icon)
elif isinstance(block, TextBlock):
return format_text_block(block)
elif isinstance(block, LinkBlock):
return f"[{block.text}]({block.url})"
else:
raise ValueError(f"Unsupported inline block type: {type(block)}")


def format_line_block_text(block: LineBlock) -> str:
return block.sep.join([format_inline_block(inline) for inline in block.inlines])


def format_line_block(block: LineBlock) -> Dict[str, Any]:
text = format_line_block_text(block)

return {
"type": "TextBlock",
"text": text,
"wrap": True,
}


def format_lines_block(block: LinesBlock) -> List[Dict[str, Any]]:
return [format_line_block(line_block) for line_block in block.lines]


def format_header_block(
block: HeaderBlock, color: Optional[Color] = None
) -> Dict[str, Any]:
return {
"type": "Container",
"items": [
{
"type": "TextBlock",
"text": block.text,
"weight": "bolder",
"size": "large",
"wrap": True,
}
],
"style": COLOR_TO_STYLE[color] if color else "Default",
}


def format_code_block(block: CodeBlock) -> Dict[str, Any]:
return {
"type": "Container",
"style": "emphasis",
"items": [
{
"type": "RichTextBlock",
"inlines": [
{
"type": "TextRun",
"text": block.text,
"fontType": "Monospace",
}
],
}
],
}


def format_fact_list_block(block: FactListBlock) -> Dict[str, Any]:
return {
"type": "FactSet",
"facts": [
{
"title": format_line_block_text(fact.title),
"value": format_line_block_text(fact.value),
}
for fact in block.facts
],
}


def format_message_block(
block: MessageBlock, color: Optional[Color] = None
) -> List[Dict[str, Any]]:
if isinstance(block, HeaderBlock):
return [format_header_block(block, color)]
elif isinstance(block, CodeBlock):
return [format_code_block(block)]
elif isinstance(block, LinesBlock):
return format_lines_block(block)
elif isinstance(block, FactListBlock):
return [format_fact_list_block(block)]
elif isinstance(block, ExpandableBlock):
return format_expandable_block(block)
else:
raise ValueError(f"Unsupported message block type: {type(block)}")


def split_message_blocks_by_divider(
blocks: List[MessageBlock],
) -> List[List[MessageBlock]]:
first_divider_index = next(
(i for i, block in enumerate(blocks) if isinstance(block, DividerBlock)),
None,
)
if first_divider_index is None:
return [blocks] if blocks else []
return [
blocks[:first_divider_index],
*split_message_blocks_by_divider(blocks[first_divider_index + 1 :]),
]


def format_divided_message_blocks(
blocks: List[MessageBlock],
divider: bool = False,
color: Optional[Color] = None,
) -> Dict[str, Any]:
return {
"type": "Container",
"separator": divider,
"items": [
item for block in blocks for item in format_message_block(block, color)
],
}


def format_expandable_block(block: ExpandableBlock) -> List[Dict[str, Any]]:
block_title = block.title
expandable_target_id = f"expandable-{uuid.uuid4()}"
return [
{
"type": "ActionSet",
"actions": [
{
"type": "Action.ToggleVisibility",
"title": block_title,
"targetElements": [expandable_target_id],
}
],
},
{
"type": "Container",
"id": expandable_target_id,
"items": format_message_blocks(block.body),
"isVisible": block.expanded,
},
]


def format_message_blocks(
blocks: List[MessageBlock], color: Optional[Color] = None
) -> List[Dict[str, Any]]:
if not blocks:
return []

message_blocks = split_message_blocks_by_divider(blocks)
# The divider is not a block in adaptive cards, it's a property of the container.
return [
format_divided_message_blocks(blocks, divider=True, color=color)
for blocks in message_blocks
]


def format_adaptive_card_body(message: MessageBody) -> List[Dict[str, Any]]:
return format_message_blocks(message.blocks, message.color)


def format_adaptive_card(message: MessageBody, version: str = "1.6") -> Dict[str, Any]:
if version < "1.2" or version > "1.6":
raise ValueError(f"Version {version} is not supported")
return {
"type": "AdaptiveCard",
"body": format_adaptive_card_body(message),
"version": version,
}
Empty file added tests/unit/messages/__init__.py
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"type": "AdaptiveCard",
"body": [
{
"type": "Container",
"separator": true,
"items": [
{
"type": "Container",
"items": [
{
"type": "TextBlock",
"text": "Main Header",
"weight": "bolder",
"size": "large",
"wrap": true
}
],
"style": "Good"
},
{
"type": "TextBlock",
"text": "Normal text **Bold text** _Italic text_",
"wrap": true
},
{
"type": "TextBlock",
"text": "- First bullet point",
"wrap": true
},
{
"type": "TextBlock",
"text": "- Second bullet point",
"wrap": true
},
{
"type": "TextBlock",
"text": "\u2705 Check item",
"wrap": true
},
{
"type": "FactSet",
"facts": [
{
"title": "Status",
"value": "Passed"
},
{
"title": "Tags",
"value": "test, example"
}
]
},
{
"type": "ActionSet",
"actions": [
{
"type": "Action.ToggleVisibility",
"title": "Show Details",
"targetElements": [
"expandable-00000000-0000-0000-0000-000000000001"
]
}
]
},
{
"type": "Container",
"id": "expandable-00000000-0000-0000-0000-000000000001",
"items": [
{
"type": "Container",
"separator": true,
"items": [
{
"type": "TextBlock",
"text": "\ud83d\udd0e **Details Section**",
"wrap": true
},
{
"type": "TextBlock",
"text": "Here's some content with a [link](https://example.com)",
"wrap": true
}
]
}
],
"isVisible": false
}
]
}
],
"version": "1.6"
}
Loading

0 comments on commit 9c055a1

Please sign in to comment.