From b4977bd3d7435411982800b0cf7163f57542e1e6 Mon Sep 17 00:00:00 2001 From: michrzan Date: Sun, 1 Feb 2026 18:16:14 +0200 Subject: [PATCH 1/6] feat: add --slack-full-width option where test results are shown as a markdown table --- elementary/config/config.py | 6 +++ elementary/messages/formats/block_kit.py | 4 +- elementary/messages/formats/markdown.py | 7 ++- elementary/messages/formats/text.py | 7 ++- elementary/monitor/cli.py | 8 ++++ .../alerts/integrations/integrations.py | 2 +- .../integrations/slack/message_builder.py | 22 ++++++++- .../alerts/integrations/slack/slack.py | 45 ++++++++++--------- elementary/utils/json_utils.py | 37 ++++++++++++++- 9 files changed, 110 insertions(+), 28 deletions(-) diff --git a/elementary/config/config.py b/elementary/config/config.py index b8e56143c..39f1cae9e 100644 --- a/elementary/config/config.py +++ b/elementary/config/config.py @@ -72,6 +72,7 @@ def __init__( report_url: Optional[str] = None, teams_webhook: Optional[str] = None, maximum_columns_in_alert_samples: Optional[int] = None, + slack_full_width: Optional[bool] = None, env: str = DEFAULT_ENV, run_dbt_deps_if_needed: Optional[bool] = None, project_name: Optional[str] = None, @@ -144,6 +145,11 @@ def __init__( slack_config.get("group_alerts_threshold"), self.DEFAULT_GROUP_ALERTS_THRESHOLD, ) + self.slack_full_width = self._first_not_none( + slack_full_width, + slack_config.get("full_width"), + False, + ) teams_config = config.get(self._TEAMS, {}) self.teams_webhook = self._first_not_none( diff --git a/elementary/messages/formats/block_kit.py b/elementary/messages/formats/block_kit.py index fe543a00f..3b12dbbe4 100644 --- a/elementary/messages/formats/block_kit.py +++ b/elementary/messages/formats/block_kit.py @@ -273,7 +273,9 @@ def _add_table_block(self, block: TableBlock) -> None: new_headers = [ self._format_table_cell(cell, column_count) for cell in block.headers ] - table_text = tabulate(new_rows, headers=new_headers, tablefmt="simple") + table_text = tabulate( + new_rows, headers=new_headers, tablefmt="simple", disable_numparse=True + ) self._add_block(self._format_markdown_section(f"```{table_text}```")) def _add_actions_block(self, block: ActionsBlock) -> None: diff --git a/elementary/messages/formats/markdown.py b/elementary/messages/formats/markdown.py index a8a454211..d0427e337 100644 --- a/elementary/messages/formats/markdown.py +++ b/elementary/messages/formats/markdown.py @@ -95,7 +95,12 @@ def format_fact_list_block(self, block: FactListBlock) -> str: def format_table_block(self, block: TableBlock) -> str: if self._table_style == TableStyle.TABULATE: - table = tabulate(block.rows, headers=block.headers, tablefmt="simple") + table = tabulate( + block.rows, + headers=block.headers, + tablefmt="simple", + disable_numparse=True, + ) return f"```\n{table}\n```" elif self._table_style == TableStyle.JSON: dicts = [ diff --git a/elementary/messages/formats/text.py b/elementary/messages/formats/text.py index 18aaaddb5..ed015bfd8 100644 --- a/elementary/messages/formats/text.py +++ b/elementary/messages/formats/text.py @@ -90,7 +90,12 @@ def format_fact_list_block(self, block: FactListBlock) -> str: def format_table_block(self, block: TableBlock) -> str: if self._table_style == TableStyle.TABULATE: - return tabulate(block.rows, headers=block.headers, tablefmt="simple") + return tabulate( + block.rows, + headers=block.headers, + tablefmt="simple", + disable_numparse=True, + ) elif self._table_style == TableStyle.JSON: dicts = [ {header: cell for header, cell in zip(block.headers, row)} diff --git a/elementary/monitor/cli.py b/elementary/monitor/cli.py index 9b47133ff..2b066a9d9 100644 --- a/elementary/monitor/cli.py +++ b/elementary/monitor/cli.py @@ -298,6 +298,12 @@ def get_cli_properties() -> dict: default=4, help="Maximum number of columns to display as a table in alert samples. Above this, the output is shown as raw JSON.", ) +@click.option( + "--slack-full-width", + is_flag=True, + default=False, + help="When set, Slack alerts use rich text to achieve full message width instead of the default narrower layout with attachments.", +) @click.pass_context def monitor( ctx, @@ -331,6 +337,7 @@ def monitor( teams_webhook, maximum_columns_in_alert_samples, quiet_logs, + slack_full_width, ): """ Get alerts on failures in dbt jobs. @@ -365,6 +372,7 @@ def monitor( teams_webhook=teams_webhook, maximum_columns_in_alert_samples=maximum_columns_in_alert_samples, quiet_logs=quiet_logs, + slack_full_width=slack_full_width, ) anonymous_tracking = AnonymousCommandLineTracking(config) anonymous_tracking.set_env("use_select", bool(select)) diff --git a/elementary/monitor/data_monitoring/alerts/integrations/integrations.py b/elementary/monitor/data_monitoring/alerts/integrations/integrations.py index 7a8ed59db..9139ddedb 100644 --- a/elementary/monitor/data_monitoring/alerts/integrations/integrations.py +++ b/elementary/monitor/data_monitoring/alerts/integrations/integrations.py @@ -43,7 +43,7 @@ def get_integration( tracking: Optional[Tracking] = None, ) -> Union[BaseMessagingIntegration, BaseIntegration]: if config.has_slack: - if config.is_slack_workflow: + if config.is_slack_workflow or config.slack_full_width: return SlackIntegration( config=config, tracking=tracking, diff --git a/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py b/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py index 435d18684..e0ef099dd 100644 --- a/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py +++ b/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py @@ -27,18 +27,38 @@ class SlackAlertMessageSchema(BaseModel): class SlackAlertMessageBuilder(SlackMessageBuilder): - def __init__(self) -> None: + def __init__(self, full_width: bool = False) -> None: super().__init__() + self.full_width = full_width def get_slack_message( self, alert_schema: SlackAlertMessageSchema, ) -> SlackMessageSchema: + if self.full_width: + return self._get_full_width_slack_message(alert_schema) self.add_title_to_slack_alert(alert_schema.title) self.add_preview_to_slack_alert(alert_schema.preview) self.add_details_to_slack_alert(alert_schema.details) return super().get_slack_message() + def _get_full_width_slack_message( + self, + alert_schema: SlackAlertMessageSchema, + ) -> SlackMessageSchema: + # Add empty rich_text block first to force Slack to render full width + self._add_always_displayed_blocks([{"type": "rich_text", "elements": []}]) + self.add_title_to_slack_alert(alert_schema.title) + # Add preview and details to main blocks instead of attachments + # Skip padding for full-width mode since all content is displayed + if alert_schema.preview: + self._add_always_displayed_blocks(alert_schema.preview) + if alert_schema.details: + self._add_always_displayed_blocks(alert_schema.details) + # Clear attachments for full-width mode + self.slack_message["attachments"] = [] + return super().get_slack_message() + def add_title_to_slack_alert(self, title_blocks: Optional[SlackBlocksType] = None): if title_blocks: title = [*title_blocks, self.create_divider_block()] diff --git a/elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py b/elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py index a0e5ce30d..62f44b630 100644 --- a/elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py +++ b/elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py @@ -26,6 +26,7 @@ ) from elementary.tracking.tracking_interface import Tracking from elementary.utils.json_utils import ( + list_of_dicts_to_markdown_table, list_of_lists_of_strings_to_comma_delimited_unique_strings, ) from elementary.utils.log import get_logger @@ -78,7 +79,9 @@ def __init__( self.config = config self.tracking = tracking self.override_config_defaults = override_config_defaults - self.message_builder = SlackAlertMessageBuilder() + self.message_builder = SlackAlertMessageBuilder( + full_width=config.slack_full_width + ) super().__init__() # Enforce typing @@ -116,7 +119,10 @@ def _get_dbt_test_template( title = [ self.message_builder.create_header_block( f"{self._get_display_name(alert.status)}: {alert.summary}" - ) + ), + self.message_builder.create_text_section_block( + "Powered by " + ), ] if alert.suppression_interval: title.extend( @@ -186,21 +192,12 @@ def _get_dbt_test_template( ) if DESCRIPTION_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS): - if alert.test_description: - preview.extend( - [ - self.message_builder.create_text_section_block("*Description*"), - self.message_builder.create_context_block( - [alert.test_description] - ), - ] - ) - else: - preview.append( - self.message_builder.create_text_section_block( - "*Description*\n_No description_" - ) + description_text = alert.test_description or "_No description_" + preview.append( + self.message_builder.create_text_section_block( + f"*Description*\n{description_text}" ) + ) result = [] if ( @@ -209,7 +206,7 @@ def _get_dbt_test_template( ): result.extend( [ - self.message_builder.create_context_block(["*Result message*"]), + self.message_builder.create_text_section_block("*Result message*"), self.message_builder.create_text_section_block( f"```{alert.error_message.strip()}```" ), @@ -220,13 +217,16 @@ def _get_dbt_test_template( TEST_RESULTS_SAMPLE_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS) and alert.test_rows_sample ): + test_rows_sample_table = list_of_dicts_to_markdown_table( + alert.test_rows_sample + ) result.extend( [ - self.message_builder.create_context_block( - ["*Test results sample*"] + self.message_builder.create_text_section_block( + "*Test results sample*" ), self.message_builder.create_text_section_block( - f"```{alert.test_rows_sample}```" + f"```{test_rows_sample_table}```" ), ] ) @@ -235,7 +235,9 @@ def _get_dbt_test_template( TEST_QUERY_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS) and alert.test_results_query ): - result.append(self.message_builder.create_context_block(["*Test query*"])) + result.append( + self.message_builder.create_text_section_block("*Test query*") + ) msg = f"```{alert.test_results_query}```" if len(msg) > SectionBlock.text_max_length: @@ -1194,7 +1196,6 @@ def _create_single_alert_details_blocks( if result: details_blocks.extend( [ - self.message_builder.create_text_section_block(":mag: *Result*"), self.message_builder.create_divider_block(), *result, ] diff --git a/elementary/utils/json_utils.py b/elementary/utils/json_utils.py index d0e3ee3c1..e43246d43 100644 --- a/elementary/utils/json_utils.py +++ b/elementary/utils/json_utils.py @@ -1,6 +1,8 @@ import json import math -from typing import Any, List, Optional, Union +from typing import Any, Dict, List, Optional, Union + +from tabulate import tabulate def try_load_json(value: Optional[Union[str, dict, list]]): @@ -94,3 +96,36 @@ def inf_and_nan_to_str(obj) -> Any: return [inf_and_nan_to_str(i) for i in obj] else: return obj + + +def _format_value(value: Any) -> str: + """Format a value for table display, avoiding scientific notation for floats.""" + if value is None: + return "" + if isinstance(value, float): + if math.isinf(value) or math.isnan(value): + return str(value) + # Format floats without scientific notation + if value == int(value) and abs(value) < 1e15: + return str(int(value)) + return f"{value:.10f}".rstrip("0").rstrip(".") + return str(value) + + +def list_of_dicts_to_markdown_table(data: List[Dict[str, Any]]) -> str: + """ + Convert a list of dictionaries to a markdown table string. + + Args: + data: List of dictionaries with consistent keys + + Returns: + A markdown-formatted table string using GitHub table format + """ + if not data: + return "" + + processed_data = [{k: _format_value(v) for k, v in row.items()} for row in data] + return tabulate( + processed_data, headers="keys", tablefmt="github", disable_numparse=True + ) From 1945d187a4467daacad4b966bba74ea9f8bb2b30 Mon Sep 17 00:00:00 2001 From: michrzan Date: Mon, 9 Feb 2026 12:42:13 +0000 Subject: [PATCH 2/6] chore: refactor slack message builder to use super() --- .../integrations/slack/message_builder.py | 34 ++++++++----------- elementary/utils/json_utils.py | 4 +-- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py b/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py index e0ef099dd..a5c5a8cb8 100644 --- a/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py +++ b/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py @@ -36,27 +36,13 @@ def get_slack_message( alert_schema: SlackAlertMessageSchema, ) -> SlackMessageSchema: if self.full_width: - return self._get_full_width_slack_message(alert_schema) + # Add empty rich_text block first to force Slack to render full width + self._add_always_displayed_blocks([{"type": "rich_text", "elements": []}]) self.add_title_to_slack_alert(alert_schema.title) self.add_preview_to_slack_alert(alert_schema.preview) self.add_details_to_slack_alert(alert_schema.details) - return super().get_slack_message() - - def _get_full_width_slack_message( - self, - alert_schema: SlackAlertMessageSchema, - ) -> SlackMessageSchema: - # Add empty rich_text block first to force Slack to render full width - self._add_always_displayed_blocks([{"type": "rich_text", "elements": []}]) - self.add_title_to_slack_alert(alert_schema.title) - # Add preview and details to main blocks instead of attachments - # Skip padding for full-width mode since all content is displayed - if alert_schema.preview: - self._add_always_displayed_blocks(alert_schema.preview) - if alert_schema.details: - self._add_always_displayed_blocks(alert_schema.details) - # Clear attachments for full-width mode - self.slack_message["attachments"] = [] + if self.full_width: + self.slack_message["attachments"] = [] return super().get_slack_message() def add_title_to_slack_alert(self, title_blocks: Optional[SlackBlocksType] = None): @@ -67,7 +53,11 @@ def add_title_to_slack_alert(self, title_blocks: Optional[SlackBlocksType] = Non def add_preview_to_slack_alert( self, preview_blocks: Optional[SlackBlocksType] = None ): - if preview_blocks: + if not preview_blocks: + return + if self.full_width: + self._add_always_displayed_blocks(preview_blocks) + else: validated_preview_blocks = self._validate_preview_blocks(preview_blocks) self._add_blocks_as_attachments(validated_preview_blocks) @@ -75,7 +65,11 @@ def add_details_to_slack_alert( self, detail_blocks: Optional[SlackBlocksType] = None, ): - if detail_blocks: + if not detail_blocks: + return + if self.full_width: + self._add_always_displayed_blocks(detail_blocks) + else: self._add_blocks_as_attachments(detail_blocks) @classmethod diff --git a/elementary/utils/json_utils.py b/elementary/utils/json_utils.py index e43246d43..cf224be15 100644 --- a/elementary/utils/json_utils.py +++ b/elementary/utils/json_utils.py @@ -114,10 +114,10 @@ def _format_value(value: Any) -> str: def list_of_dicts_to_markdown_table(data: List[Dict[str, Any]]) -> str: """ - Convert a list of dictionaries to a markdown table string. + Convert a list of dictionaries with consistent keys to a markdown table string. Args: - data: List of dictionaries with consistent keys + data: List of dictionaries Returns: A markdown-formatted table string using GitHub table format From e4d81f843d760a67132894bde7b7c55817738d45 Mon Sep 17 00:00:00 2001 From: michrzan Date: Mon, 9 Feb 2026 13:26:19 +0000 Subject: [PATCH 3/6] chore: update docs --- docs/oss/deployment-and-configuration/slack.mdx | 12 ++++++++++++ docs/oss/guides/alerts/send-slack-alerts.mdx | 2 ++ 2 files changed, 14 insertions(+) diff --git a/docs/oss/deployment-and-configuration/slack.mdx b/docs/oss/deployment-and-configuration/slack.mdx index 18079bc65..9b45b5c8c 100644 --- a/docs/oss/deployment-and-configuration/slack.mdx +++ b/docs/oss/deployment-and-configuration/slack.mdx @@ -53,3 +53,15 @@ The alert format is: ``` --- + +## Full-width alerts + +By default, Slack alerts use a narrower layout with some content in attachments. To use the full message width and show test results as a markdown table in the main message body, pass the flag when running the monitor: + +```shell +edr monitor --slack-token --slack-channel-name --slack-full-width +``` + +With `--slack-full-width`, alerts are sent using Slack Block Kit in the main message body instead of attachments, so the full channel width is used and test result samples appear as formatted markdown tables. + +--- diff --git a/docs/oss/guides/alerts/send-slack-alerts.mdx b/docs/oss/guides/alerts/send-slack-alerts.mdx index 79cd4e635..a8052b0d3 100644 --- a/docs/oss/guides/alerts/send-slack-alerts.mdx +++ b/docs/oss/guides/alerts/send-slack-alerts.mdx @@ -24,6 +24,8 @@ Make sure to run the following command after your dbt runs and tests: edr monitor --slack-token --slack-channel-name --group-by [table | alert] ``` +Add `--slack-full-width` to use the full message width and show test results as markdown tables. See [Slack setup - Full-width alerts](/oss/deployment-and-configuration/slack#full-width-alerts). + Or just `edr monitor` if you used `config.yml`. Please note that when you specify the --slack-channel-name, it's the default channel name to which all the alerts will be sent that are not attributed to any custom channel. Therefore, if you execute several `edr monitor` commands at the same time with different `slack-channel-name` arguments, they can From bf14655a559e995aab64ab8aecd43686f8ac0256 Mon Sep 17 00:00:00 2001 From: michrzan Date: Mon, 9 Feb 2026 17:25:27 +0000 Subject: [PATCH 4/6] feat: unit tests --- .../slack/test_slack_alert_message_builder.py | 45 ++++++++++++++ tests/unit/utils/test_json_utils.py | 58 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 tests/unit/utils/test_json_utils.py diff --git a/tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py b/tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py index 244f6478a..65bd7b753 100644 --- a/tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py +++ b/tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py @@ -5,6 +5,7 @@ from elementary.monitor.data_monitoring.alerts.integrations.slack.message_builder import ( PreviewIsTooLongError, SlackAlertMessageBuilder, + SlackAlertMessageSchema, ) @@ -173,3 +174,47 @@ def test_add_details_to_slack_alert(): }, sort_keys=True, ) + + +def test_full_width_preview_goes_to_blocks_not_attachments(): + """With full_width=True, preview blocks are added to main blocks.""" + message_builder = SlackAlertMessageBuilder(full_width=True) + block = message_builder.create_header_block("Preview header") + message_builder.add_preview_to_slack_alert([block]) + assert len(message_builder.slack_message["blocks"]) == 1 + assert message_builder.slack_message["blocks"][0] == block + assert message_builder.slack_message["attachments"][0]["blocks"] == [] + + +def test_full_width_details_go_to_blocks_not_attachments(): + """With full_width=True, detail blocks are added to main blocks.""" + message_builder = SlackAlertMessageBuilder(full_width=True) + block = message_builder.create_divider_block() + message_builder.add_details_to_slack_alert([block]) + assert len(message_builder.slack_message["blocks"]) == 1 + assert message_builder.slack_message["blocks"][0] == block + assert message_builder.slack_message["attachments"][0]["blocks"] == [] + + +def test_full_width_get_slack_message_structure(): + """With full_width=True, get_slack_message adds rich_text first, title/preview/details in blocks, and clears attachments.""" + message_builder = SlackAlertMessageBuilder(full_width=True) + title = message_builder.create_header_block("Alert title") + preview_block = message_builder.create_text_section_block("Preview text") + detail_block = message_builder.create_divider_block() + schema = SlackAlertMessageSchema( + title=[title], + preview=[preview_block], + details=[detail_block], + ) + result = message_builder.get_slack_message(alert_schema=schema) + + blocks = result.blocks + assert len(blocks) >= 4 + assert blocks[0] == {"type": "rich_text", "elements": []} + assert blocks[1] == title + assert blocks[2]["type"] == "divider" + assert blocks[3] == preview_block + assert blocks[4] == detail_block + + assert result.attachments == [] diff --git a/tests/unit/utils/test_json_utils.py b/tests/unit/utils/test_json_utils.py new file mode 100644 index 000000000..d8cdacb18 --- /dev/null +++ b/tests/unit/utils/test_json_utils.py @@ -0,0 +1,58 @@ +from elementary.utils.json_utils import list_of_dicts_to_markdown_table + + +def test_list_of_dicts_to_markdown_table_empty(): + assert list_of_dicts_to_markdown_table([]) == "" + + +def test_list_of_dicts_to_markdown_table_single_row(): + result = list_of_dicts_to_markdown_table([{"a": 1, "b": "two"}]) + # tabulate "github" format pads columns; assert header and row content + assert "a" in result and "b" in result + assert "1" in result and "two" in result + assert "|" in result and "-----" in result + + +def test_list_of_dicts_to_markdown_table_multiple_rows(): + data = [ + {"col1": "a", "col2": "b"}, + {"col1": "c", "col2": "d"}, + ] + result = list_of_dicts_to_markdown_table(data) + assert "col1" in result and "col2" in result + assert "a" in result and "b" in result and "c" in result and "d" in result + assert result.count("\n") >= 3 # header, separator, 2 data rows + + +def test_list_of_dicts_to_markdown_table_none_values(): + result = list_of_dicts_to_markdown_table([{"x": None, "y": "ok"}]) + assert "x" in result and "y" in result + assert "ok" in result + # None is formatted as empty string (empty cell between pipes) + assert "|" in result + + +def test_list_of_dicts_to_markdown_table_float_int_like(): + """Floats that are whole numbers are formatted as ints (no scientific notation).""" + result = list_of_dicts_to_markdown_table([{"n": 1.0}, {"n": 2.0}]) + assert "n" in result + assert " 1 " in result or "| 1 " in result + assert " 2 " in result or "| 2 " in result + + +def test_list_of_dicts_to_markdown_table_float_decimal(): + """Decimal floats are formatted without scientific notation.""" + result = list_of_dicts_to_markdown_table([{"x": 1.23456789}]) + assert "1.23456789" in result or "1.2345678" in result + + +def test_list_of_dicts_to_markdown_table_inf_nan(): + """inf and nan are stringified.""" + data = [ + {"v": float("inf")}, + {"v": float("-inf")}, + {"v": float("nan")}, + ] + result = list_of_dicts_to_markdown_table(data) + assert "inf" in result + assert "nan" in result From 99cb7833ad63bb9ca1b7dd459a0b4f18f2d9f603 Mon Sep 17 00:00:00 2001 From: michrzan Date: Mon, 9 Feb 2026 17:33:10 +0000 Subject: [PATCH 5/6] chore: docs --- .../alerts/integrations/slack/message_builder.py | 3 ++- .../integrations/slack/test_slack_alert_message_builder.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py b/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py index a5c5a8cb8..e3dbb9149 100644 --- a/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py +++ b/elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py @@ -36,7 +36,8 @@ def get_slack_message( alert_schema: SlackAlertMessageSchema, ) -> SlackMessageSchema: if self.full_width: - # Add empty rich_text block first to force Slack to render full width + # Empty rich_text block forces Slack to use full message width for following + # blocks instead of the narrower attachment-style layout. self._add_always_displayed_blocks([{"type": "rich_text", "elements": []}]) self.add_title_to_slack_alert(alert_schema.title) self.add_preview_to_slack_alert(alert_schema.preview) diff --git a/tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py b/tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py index 65bd7b753..b7edacb98 100644 --- a/tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py +++ b/tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py @@ -210,7 +210,7 @@ def test_full_width_get_slack_message_structure(): result = message_builder.get_slack_message(alert_schema=schema) blocks = result.blocks - assert len(blocks) >= 4 + assert len(blocks) >= 5 assert blocks[0] == {"type": "rich_text", "elements": []} assert blocks[1] == title assert blocks[2]["type"] == "divider" From 68ddb4400348e10803f5cc16836cc320afb474c6 Mon Sep 17 00:00:00 2001 From: michrzan Date: Tue, 10 Feb 2026 13:56:01 +0000 Subject: [PATCH 6/6] fix: sync emojis --- .../alerts/integrations/slack/slack.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py b/elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py index 62f44b630..62d4c525f 100644 --- a/elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py +++ b/elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py @@ -9,6 +9,8 @@ from elementary.clients.slack.schema import SlackBlocksType, SlackMessageSchema from elementary.clients.slack.slack_message_builder import MessageColor from elementary.config.config import Config +from elementary.messages.blocks import Icon +from elementary.messages.formats.unicode import ICON_TO_UNICODE from elementary.monitor.alerts.alerts_groups import AlertsGroup, GroupedByTableAlerts from elementary.monitor.alerts.alerts_groups.base_alerts_group import BaseAlertsGroup from elementary.monitor.alerts.model_alert import ModelAlertModel @@ -171,8 +173,11 @@ def _get_dbt_test_template( ) compacted_sections = [] - if COLUMN_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS): - compacted_sections.append(f"*Column*\n{alert.column_name or '_No column_'}") + if ( + COLUMN_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS) + and alert.column_name + ): + compacted_sections.append(f"*Column*\n{alert.column_name}") if TAGS_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS): tags = prettify_and_dedup_list(alert.tags or []) compacted_sections.append(f"*Tags*\n{tags or '_No tags_'}") @@ -223,7 +228,7 @@ def _get_dbt_test_template( result.extend( [ self.message_builder.create_text_section_block( - "*Test results sample*" + f"{ICON_TO_UNICODE[Icon.MAGNIFYING_GLASS]} *Test results sample*" ), self.message_builder.create_text_section_block( f"```{test_rows_sample_table}```" @@ -332,8 +337,11 @@ def _get_elementary_test_template( ) compacted_sections = [] - if COLUMN_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS): - compacted_sections.append(f"*Column*\n{alert.column_name or '_No column_'}") + if ( + COLUMN_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS) + and alert.column_name + ): + compacted_sections.append(f"*Column*\n{alert.column_name}") if TAGS_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS): tags = prettify_and_dedup_list(alert.tags or []) compacted_sections.append(f"*Tags*\n{tags or '_No tags_'}") @@ -1196,6 +1204,9 @@ def _create_single_alert_details_blocks( if result: details_blocks.extend( [ + self.message_builder.create_text_section_block( + f"{ICON_TO_UNICODE[Icon.INFO]} *Details*" + ), self.message_builder.create_divider_block(), *result, ]