Skip to content

feat: add --slack-full-width for full-width Slack alerts with markdown test result tables#2133

Closed
devin-ai-integration[bot] wants to merge 9 commits intomasterfrom
devin/1772365421-slack-full-width-with-fixes
Closed

feat: add --slack-full-width for full-width Slack alerts with markdown test result tables#2133
devin-ai-integration[bot] wants to merge 9 commits intomasterfrom
devin/1772365421-slack-full-width-with-fixes

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Mar 1, 2026

feat: add --slack-full-width for full-width Slack alerts with Markdown test result tables

Summary

Adds a --slack-full-width CLI flag that renders Slack alerts at full message width using Block Kit main-body blocks (no attachments) and displays test result samples as Markdown tables. This addresses readability issues when test results contain many columns or long values (#1079).

Key implementation details:

  • Full-width rendering: A rich_text sentinel block is injected at the start to force Slack into full-width layout; attachments are cleared
  • Markdown tables: list_of_dicts_to_markdown_table() converts test result samples to GitHub-flavored Markdown tables with graceful row-by-row truncation (avoids cutting mid-row at the 3000-char Slack limit)
  • Message builder refactor: SlackAlertMessageBuilder gains a full_width flag that routes preview/detail blocks to main blocks instead of attachments
  • Description rendering simplification: Both _get_dbt_test_template and _get_elementary_test_template now use a single text_section_block for descriptions (was two blocks: header + context)
  • disable_numparse=True added to all tabulate calls to preserve numeric formatting

Review & Testing Checklist for Human

  • Verify the rich_text space-character block renders acceptably in Slack. The {"type": "text", "text": " "} is valid per API but may show as a blank line. Test with --slack-full-width in a real Slack channel.
  • Check preview padding blocks in full-width mode. Preview validation pads to 5 blocks (designed for attachment fold control). In full-width mode these appear as empty whitespace sections in the main body — this is likely a visible UX issue. Consider whether full-width should skip padding.
  • Description rendering change affects ALL alerts, not just full-width. The two-block → single-block simplification in _get_elementary_test_template (and create_context_blockcreate_text_section_block swaps for "Result message") changes the appearance of all elementary test alerts. Verify this is intentional.
  • "Powered by Elementary" branding block is added unconditionally to all dbt test alert titles (not gated behind full_width). Verify this is desired.
  • Column field omission — When column_name is falsy, the column section is now entirely omitted instead of showing "No column". Applies to all alerts, not just full-width.
  • Integration selectionconfig.is_slack_workflow or config.slack_full_width forces SlackIntegration even for webhook users. Verify this is correct.
  • End-to-end Slack rendering — Manual testing in a Slack workspace is required. Unit tests pass but actual rendering hasn't been verified. Test with alerts that have large test result samples to ensure table truncation works.

Notes

  • Trade-off: Full-width mode loses the colored severity indicator (yellow/red pipe) since attachments are cleared
  • Requested by: @haritamar
  • Devin Session
  • Unit tests: 14/14 passing. code-quality CI passed. Full end-to-end Slack rendering not tested.

Summary by CodeRabbit

  • New Features

    • Added --slack-full-width CLI flag to enable full-width Slack alert messages with Markdown tables
    • Slack alerts can now send richer, full-width message layouts for improved readability
  • Improvements

    • Table rendering updated to preserve values (avoids unintended numeric parsing) and produce cleaner Markdown tables
  • Documentation

    • Updated Slack deployment and alert guides with full-width configuration and usage examples
  • Tests

    • Added unit tests covering full-width Slack messages and Markdown table generation

Open with Devin

michrzan and others added 8 commits February 9, 2026 17:33
- Capitalize 'markdown' to 'Markdown' in docs
- Replace invalid empty rich_text block with valid Slack Block Kit structure
- Add preview validation for full-width mode
- Handle markdown table truncation gracefully (row-by-row instead of mid-row)
- Align description rendering between dbt and elementary test templates
- Update tests to reflect valid rich_text block and exact assertions

Co-Authored-By: Itamar Hartstein <haritamar@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 1, 2026

👋 @devin-ai-integration[bot]
Thank you for raising your pull request.
Please make sure to add tests and document all user-facing changes.
You can do this by editing the docs files in this pull request.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 659064f and db96f8e.

📒 Files selected for processing (1)
  • elementary/utils/json_utils.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • elementary/utils/json_utils.py

📝 Walkthrough

Walkthrough

The PR adds full-width Slack alert support via a new --slack-full-width CLI flag. Changes extend configuration, CLI, integration selection, and Slack message building to enable Block Kit formatting with Markdown tables and icon-based headers. Documentation and tests are included.

Changes

Cohort / File(s) Summary
Documentation
docs/oss/deployment-and-configuration/slack.mdx, docs/oss/guides/alerts/send-slack-alerts.mdx
New "Full-width alerts" docs and CLI usage note describing --slack-full-width and Block Kit Markdown table behavior.
Configuration
elementary/config/config.py
Added slack_full_width parameter to Config.__init__ and initialization precedence (arg → slack_config["full_width"] → False).
CLI
elementary/monitor/cli.py
Added --slack-full-width Click option and propagated value to Config for monitor runs.
Integration Selection
elementary/monitor/data_monitoring/alerts/integrations/integrations.py
SlackIntegration chosen when config.has_slack and (config.is_slack_workflow OR config.slack_full_width).
Message Building
elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py
Added full_width: bool ctor arg; when true, prepends a rich_text block, clears attachments, and routes preview/details into main blocks instead of attachments. Public ctor signature changed.
Slack Templates
elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py
Refactored templates to use icon-based headers, text sections for descriptions/results/queries, Markdown tables for samples (via new util), and initialize builder with full_width from config.
Table formatting (tabulate)
elementary/messages/formats/...
elementary/messages/formats/block_kit.py, elementary/messages/formats/markdown.py, elementary/messages/formats/text.py
Added disable_numparse=True to tabulate calls to prevent numeric-like strings from being coerced to numbers.
Utilities
elementary/utils/json_utils.py
New _format_value() helper and list_of_dicts_to_markdown_table() to produce GitHub-style Markdown tables (with truncation support); imports and typing extended.
Tests
tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py, tests/unit/utils/test_json_utils.py
Added tests for full-width Slack message behavior and for markdown table utility (empty, None, floats, inf/nan, truncation). Exported SlackAlertMessageSchema used in tests.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as CLI Monitor
    participant Config as Config
    participant Selector as Integration Selector
    participant Builder as SlackAlertMessageBuilder
    participant Slack as Slack API

    User->>CLI: run monitor --slack-full-width
    CLI->>Config: Config(slack_full_width=True)
    Config->>Selector: evaluate integrations
    Selector->>Builder: create SlackAlertMessageBuilder(full_width=True)
    Builder->>Builder: prepend rich_text block
    Builder->>Builder: route preview/details to main blocks
    Builder->>Builder: clear attachments
    Builder->>Slack: send Block Kit payload (Markdown tables)
    Slack->>User: display full-width alert
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 I nibbled code beneath the moon,
Full-width alerts arrive quite soon,
Tables crisp and icons bright,
CLI flag hops into the night,
Blocks aligned — a rabbit's delight! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature added: a new --slack-full-width CLI flag for rendering Slack alerts at full width with markdown test result tables.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/1772365421-slack-full-width-with-fixes

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py (1)

199-205: Extract shared description block construction to prevent drift.

Both template builders now have identical description rendering. A small helper would keep these paths synchronized as Slack formatting evolves.

♻️ Proposed refactor
+def _append_description_block(self, preview: list, description: Optional[str]) -> None:
+    description_text = description or "_No description_"
+    preview.append(
+        self.message_builder.create_text_section_block(
+            f"*Description*\n{description_text}"
+        )
+    )
@@
-        if DESCRIPTION_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS):
-            description_text = alert.test_description or "_No description_"
-            preview.append(
-                self.message_builder.create_text_section_block(
-                    f"*Description*\n{description_text}"
-                )
-            )
+        if DESCRIPTION_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS):
+            self._append_description_block(preview, alert.test_description)
@@
-        if DESCRIPTION_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS):
-            description_text = alert.test_description or "_No description_"
-            preview.append(
-                self.message_builder.create_text_section_block(
-                    f"*Description*\n{description_text}"
-                )
-            )
+        if DESCRIPTION_FIELD in (alert.alert_fields or DEFAULT_ALERT_FIELDS):
+            self._append_description_block(preview, alert.test_description)

Also applies to: 365-371

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py` around
lines 199 - 205, Two identical blocks build the Description Slack section;
extract a small helper to centralize construction and avoid divergence. Create a
private helper method (e.g., _build_description_block or
build_description_block) that takes an Alert-like object or the description
string, computes description_text = alert.test_description or "_No
description_", and returns the result of
self.message_builder.create_text_section_block(f"*Description*\n{description_text}");
then replace both occurrences (the block using DESCRIPTION_FIELD /
(alert.alert_fields or DEFAULT_ALERT_FIELDS) and the identical block at the
other location) to call that helper so formatting logic lives in one place.
elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py (1)

69-75: Consider skipping preview padding when full_width=True.

Line 71 currently reuses validation that pads preview to a fixed size. In full-width mode this adds empty visible blocks and uses block budget without affecting cutoff behavior.

♻️ Proposed refactor
 def add_preview_to_slack_alert(
     self, preview_blocks: Optional[SlackBlocksType] = None
 ):
     if not preview_blocks:
         return
-    validated_preview_blocks = self._validate_preview_blocks(preview_blocks)
+    validated_preview_blocks = self._validate_preview_blocks(
+        preview_blocks, pad_to_max=not self.full_width
+    )
     if self.full_width:
         self._add_always_displayed_blocks(validated_preview_blocks)
     else:
         self._add_blocks_as_attachments(validated_preview_blocks)
-@classmethod
-def _validate_preview_blocks(cls, preview_blocks: Optional[SlackBlocksType] = None):
+@classmethod
+def _validate_preview_blocks(
+    cls,
+    preview_blocks: Optional[SlackBlocksType] = None,
+    pad_to_max: bool = True,
+):
@@
-    if preview_blocks_count == SlackMessageBuilder._MAX_ALERT_PREVIEW_BLOCKS:
+    if (
+        preview_blocks_count == SlackMessageBuilder._MAX_ALERT_PREVIEW_BLOCKS
+        or not pad_to_max
+    ):
         return preview_blocks
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py`
around lines 69 - 75, The current flow always calls
_validate_preview_blocks(preview_blocks) which pads the preview to a fixed size;
when full_width=True that padding produces empty visible blocks and wastes Slack
block budget. Modify message_builder.py so that when self.full_width is true you
skip preview padding: either call a new validation variant (e.g.,
_validate_preview_blocks(preview_blocks, pad=False)) or add a parameter/flag to
_validate_preview_blocks to disable padding, then pass the unpadded
validated_preview_blocks into _add_always_displayed_blocks; keep existing padded
validation for the non-full_width branch used by _add_blocks_as_attachments.
Ensure references to _validate_preview_blocks, _add_always_displayed_blocks, and
_add_blocks_as_attachments are updated accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py`:
- Around line 225-237: The Slack message can exceed Slack limits (50 blocks and
3000 chars per mrkdwn section) when embedding large code-fenced tables; update
the logic around table_max_length / list_of_dicts_to_markdown_table to enforce
truncation so the resulting test_rows_sample_table plus the surrounding "```"
stays <= 3000 chars, and ensure you do not create more than 50 blocks when
building with self.message_builder.create_text_section_block (consider
collapsing or omitting less critical sections). Specifically, adjust the
reserve-for-fence calculation (currently using SectionBlock.text_max_length -
6), enforce a hard cap on test_rows_sample_table length before wrapping it in a
code fence, and add a guard in the message-building flow to skip or summarize
this sample when total blocks would exceed Slack's 50-block limit.

In `@elementary/utils/json_utils.py`:
- Around line 142-161: The fallback can still exceed max_length when a
single_row_table is too long; update the fallback logic after producing
single_row_table (from processed_data[:1]) to check its length against
effective_max and, if necessary, truncate the table string to effective_max
characters and append truncation_note so the final return never exceeds
max_length; use the existing truncation_note and effective_max variables and
return the truncated single_row_table + truncation_note when trimming is
required.

---

Nitpick comments:
In
`@elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py`:
- Around line 69-75: The current flow always calls
_validate_preview_blocks(preview_blocks) which pads the preview to a fixed size;
when full_width=True that padding produces empty visible blocks and wastes Slack
block budget. Modify message_builder.py so that when self.full_width is true you
skip preview padding: either call a new validation variant (e.g.,
_validate_preview_blocks(preview_blocks, pad=False)) or add a parameter/flag to
_validate_preview_blocks to disable padding, then pass the unpadded
validated_preview_blocks into _add_always_displayed_blocks; keep existing padded
validation for the non-full_width branch used by _add_blocks_as_attachments.
Ensure references to _validate_preview_blocks, _add_always_displayed_blocks, and
_add_blocks_as_attachments are updated accordingly.

In `@elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py`:
- Around line 199-205: Two identical blocks build the Description Slack section;
extract a small helper to centralize construction and avoid divergence. Create a
private helper method (e.g., _build_description_block or
build_description_block) that takes an Alert-like object or the description
string, computes description_text = alert.test_description or "_No
description_", and returns the result of
self.message_builder.create_text_section_block(f"*Description*\n{description_text}");
then replace both occurrences (the block using DESCRIPTION_FIELD /
(alert.alert_fields or DEFAULT_ALERT_FIELDS) and the identical block at the
other location) to call that helper so formatting logic lives in one place.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 746ce41 and 659064f.

📒 Files selected for processing (13)
  • docs/oss/deployment-and-configuration/slack.mdx
  • docs/oss/guides/alerts/send-slack-alerts.mdx
  • elementary/config/config.py
  • elementary/messages/formats/block_kit.py
  • elementary/messages/formats/markdown.py
  • elementary/messages/formats/text.py
  • elementary/monitor/cli.py
  • elementary/monitor/data_monitoring/alerts/integrations/integrations.py
  • elementary/monitor/data_monitoring/alerts/integrations/slack/message_builder.py
  • elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py
  • elementary/utils/json_utils.py
  • tests/unit/monitor/data_monitoring/alerts/integrations/slack/test_slack_alert_message_builder.py
  • tests/unit/utils/test_json_utils.py

Comment on lines +225 to 237
# Reserve space for the code fence (``` prefix and ``` suffix = 6 chars)
table_max_length = SectionBlock.text_max_length - 6
test_rows_sample_table = list_of_dicts_to_markdown_table(
alert.test_rows_sample, max_length=table_max_length
)
result.extend(
[
self.message_builder.create_context_block(
["*Test results sample*"]
self.message_builder.create_text_section_block(
f"{ICON_TO_UNICODE[Icon.MAGNIFYING_GLASS]} *Test results sample*"
),
self.message_builder.create_text_section_block(
f"```{alert.test_rows_sample}```"
f"```{test_rows_sample_table}```"
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Slack API maximum blocks per message limit documentation

💡 Result:

Slack’s Block Kit documentation states that a message’s blocks array can include up to 50 blocks per message (and up to 100 blocks in modals or Home tabs). [1]

Docs: Slack Developer Docs → “Blocks” (Block Kit reference). [1]

Sources
[1] Slack Developer Docs — “Blocks” (Block Kit reference): https://docs.slack.dev/reference/block-kit/blocks/


🌐 Web query:

Slack mrkdwn section text field maximum character length limit

💡 Result:

In Slack Block Kit, a section block’s text field (a mrkdwn text object) has a maximum length of 3000 characters. [1][2]

If you’re using section.fields (an array of text objects), each field’s text max is 2000 characters (max 10 fields). [1]


Run end-to-end Slack integration test with large sample payloads before release.

This path combines multiple section blocks with code-fenced sample tables. Slack enforces a 50-block limit per message and 3000-character limit per mrkdwn section text field. Validate that near-limit payloads (with large test result samples) do not trigger invalid_blocks or msg_too_long errors against the actual Slack API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@elementary/monitor/data_monitoring/alerts/integrations/slack/slack.py` around
lines 225 - 237, The Slack message can exceed Slack limits (50 blocks and 3000
chars per mrkdwn section) when embedding large code-fenced tables; update the
logic around table_max_length / list_of_dicts_to_markdown_table to enforce
truncation so the resulting test_rows_sample_table plus the surrounding "```"
stays <= 3000 chars, and ensure you do not create more than 50 blocks when
building with self.message_builder.create_text_section_block (consider
collapsing or omitting less critical sections). Specifically, adjust the
reserve-for-fence calculation (currently using SectionBlock.text_max_length -
6), enforce a hard cap on test_rows_sample_table length before wrapping it in a
code fence, and add a guard in the message-building flow to skip or summarize
this sample when total blocks would exceed Slack's 50-block limit.

Comment thread elementary/utils/json_utils.py Outdated
Co-Authored-By: Itamar Hartstein <haritamar@gmail.com>
Copy link
Copy Markdown
Contributor Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 6 additional findings.

Open in Devin Review

@haritamar haritamar closed this Mar 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants