FRO-211 / ROB-3946 Fix corrupted Alerts Summary table when the digest is too long#2123
FRO-211 / ROB-3946 Fix corrupted Alerts Summary table when the digest is too long#2123alonelish wants to merge 9 commits into
Conversation
The Slack "Alerts Summary" digest rendered a corrupted ASCII table: long values in text columns (e.g. Java class names in `label:site`) were passed to tabulate via `maxcolwidths`, which *wraps* cells onto extra physical lines rather than truncating them, mangling every following row. TableBlock.to_table_string now truncates over-wide cells itself (with a single-char "…" ellipsis) so each row stays on one line, and no longer passes maxcolwidths to tabulate. Dotted, space-free qualified names are trimmed from the left to keep the distinctive class-name suffix (e.g. `…settler.AbstractBetSettler`); other text is trimmed from the right. __calc_max_width now water-fills the reduction across the widest text columns (fair distribution, with a minimum width floor) and never shrinks numeric columns, so the Fired/Resolved counters are always shown in full. Adds regression tests proving rows stay single-line and are truncated (not wrapped). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
✅ Docker image ready for
Use this tag to pull the image for testing. 📋 Copy commandsgcloud auth configure-docker us-central1-docker.pkg.dev
docker pull us-central1-docker.pkg.dev/robusta-development/temporary-builds/robusta-runner:510aab7
docker tag us-central1-docker.pkg.dev/robusta-development/temporary-builds/robusta-runner:510aab7 me-west1-docker.pkg.dev/robusta-development/development/robusta-runner-dev:510aab7
docker push me-west1-docker.pkg.dev/robusta-development/development/robusta-runner-dev:510aab7Patch Helm values in one line: helm upgrade --install robusta robusta/robusta \
--reuse-values \
--set runner.image=me-west1-docker.pkg.dev/robusta-development/development/robusta-runner-dev:510aab7 |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough
ChangesTable rendering and Slack summaries
Timeline URL encoding
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant SlackSender
participant TableBlock
participant Slack
SlackSender->>TableBlock: render sorted summary rows
TableBlock->>TableBlock: fit whole rows within block budget
TableBlock-->>SlackSender: return table and omission note
SlackSender->>Slack: send summary and optional all-groups link
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/robusta/core/reporting/blocks.py`:
- Around line 410-413: Update the shrinkable-column selection around
__numeric_column_indices so an all-numeric table does not fall back to
list(range(num_columns)). Preserve numeric columns at full width when no
non-numeric column exists, while retaining the existing behavior of shrinking
non-numeric columns when available.
- Around line 399-403: Update the table width calculation around num_columns and
columns_max_widths so it expands to cover every column in rendered_rows,
including when headers is empty or rows contain more values than headers.
Preserve header widths and use the resulting column widths for subsequent
rendering without indexing beyond the list.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7dc50a4d-7ee1-45bf-9126-dc189ce565f8
📒 Files selected for processing (2)
src/robusta/core/reporting/blocks.pytests/test_blocks.py
- __calc_max_width now sizes columns to the widest row, not just the headers, so headerless or ragged tables no longer raise IndexError. - An all-numeric over-width table is left at full width instead of ellipsizing the numbers. - Shorten code/test comments; add regression tests for both cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/test_blocks.py (1)
159-162: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove redundant and ineffective wrap-spillover assertion.
The assertion
not any(line.strip() in ("ServiceImpl", "erviceImpl") for line in lines)is technically ineffective. Because theprestotable format includes column dividers (e.g.,|), a wrapped line would look something likeerviceImpl | |, meaningline.strip()would never exactly match the literal strings in the tuple.Furthermore, the explicit line-count assertion earlier (
len(lines) == 2 + len(rows)) already perfectly guarantees that no extra physical lines were generated. You can safely remove this check.♻️ Proposed fix
- # The class name is truncated, not present in full, and no wrap-spillover fragment. + # The class name is truncated, not present in full. assert LONG_CLASS_NAME not in output assert "…" in output - assert not any(line.strip() in ("ServiceImpl", "erviceImpl") for line in lines)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_blocks.py` around lines 159 - 162, Remove the ineffective any-based wrap-spillover assertion from the test around LONG_CLASS_NAME. Keep the existing LONG_CLASS_NAME, ellipsis, and line-count assertions unchanged, since the line-count check already verifies that no extra physical lines are produced.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@tests/test_blocks.py`:
- Around line 159-162: Remove the ineffective any-based wrap-spillover assertion
from the test around LONG_CLASS_NAME. Keep the existing LONG_CLASS_NAME,
ellipsis, and line-count assertions unchanged, since the line-count check
already verifies that no extra physical lines are produced.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 80dc6ee8-bc69-449e-8dbe-2737c81ce6ae
📒 Files selected for processing (2)
src/robusta/core/reporting/blocks.pytests/test_blocks.py
🚧 Files skipped from review as they are similar to previous changes (1)
- src/robusta/core/reporting/blocks.py
Drop the Slack-digest-specific "Fired/Resolved" example, since blocks.py is core code shared across all sinks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer feedback: the full alert path must stay visible, so revert the cell-level ellipsis truncation and let long values wrap as before. Instead of cutting values, guard the real breakage: - to_markdown now renders within a char budget and drops WHOLE rows, appending "... N more rows not shown". The previous line-based trim cut mid-wrapped-row, leaving a value's continuation behind and presenting a partial name as if it were complete. - The budget always stays under BLOCK_SIZE_LIMIT, so MarkdownBlock can no longer blind-cut the text and lose the closing ``` fence - which is what made the large digest render as unformatted, non-monospace text. - Column widths are computed once from all rows so dropping rows doesn't reflow the table. to_table_string keeps its previous wrapping behaviour; only column width allocation changed (numeric columns are never shrunk, so counters don't wrap, plus the headerless/ragged-row fix). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/robusta/core/reporting/blocks.py (1)
389-418: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAccount for presto’s separator and padding width in
__calc_max_width.
prestorenders column contents plus framing: separators, padding, and the separator row. Withtable_max_width=27, the returned widths sum to 27, but the rendered physical lines reach 52 characters. Track the rendered/separator row width instead of only content widths, and add a regression asserting every rendered line fits the requested limit.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/robusta/core/reporting/blocks.py` around lines 389 - 418, Update __calc_max_width to budget for presto’s full rendered table width, including column padding, separators, and the separator row, rather than comparing only the sum of content widths to table_max_width. Adjust the shrink calculation and stopping condition so every physical line produced by presto remains within the requested limit, while preserving numeric-column and minimum-width behavior. Add a regression test asserting that all rendered lines respect the configured maximum width.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/robusta/core/reporting/blocks.py`:
- Around line 491-499: The table rendering flow around __render_within_budget
must keep the full MarkdownBlock within max_chars and BLOCK_SIZE_LIMIT. Trim or
validate the prefix before calculating the content budget, reserve the suffix
accordingly, and when the remaining content budget is non-positive return no
content rather than adding an overflow marker; preserve the closing fence and
existing normal rendering behavior.
---
Outside diff comments:
In `@src/robusta/core/reporting/blocks.py`:
- Around line 389-418: Update __calc_max_width to budget for presto’s full
rendered table width, including column padding, separators, and the separator
row, rather than comparing only the sum of content widths to table_max_width.
Adjust the shrink calculation and stopping condition so every physical line
produced by presto remains within the requested limit, while preserving
numeric-column and minimum-width behavior. Add a regression test asserting that
all rendered lines respect the configured maximum width.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0ab78349-2fce-4d5e-8b4d-8f385c983ef0
📒 Files selected for processing (2)
src/robusta/core/reporting/blocks.pytests/test_blocks.py
When the digest is too long to fit in a Slack message, rows are dropped.
Previously the rows were sorted alphabetically, so whichever groups
happened to sort last were the ones discarded - unrelated to how noisy
they were. Sort by notification count instead (key as a stable secondary
criterion), so the dropped rows are the least significant ones.
Also:
- The omission note now carries the residual totals ("... 44 more groups
(89 fired, 39 resolved) not shown"), so the table still reconciles with
the notification count in the message header. TableBlock takes an
optional omission_note callback for this, keeping the digest-specific
wording out of the shared reporting module.
- Add a "View all N groups" link below the table, since rows may be
hidden. It has to sit outside the code block - Slack won't render a
link inside one.
- get_timeline_uri now passes "clusters" as a JSON list, which is what
the timeline actually reads; the previous singular "cluster" param was
ignored, so the existing Investigate link was never cluster-filtered.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/robusta/integrations/slack/sender.py`:
- Around line 903-907: Normalize each summary key to its display-safe string
representation before using it as the secondary sort criterion in the sorted
call within handle_notification_grouping(), while preserving descending
notification-count ordering and stable key ordering. Add a regression test
covering equal-count rows with mixed None and string keys.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 503a27b1-516b-438c-a816-71439c0741c3
📒 Files selected for processing (4)
src/robusta/core/reporting/blocks.pysrc/robusta/core/sinks/slack/slack_sink.pysrc/robusta/integrations/slack/sender.pytests/test_blocks.py
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/test_blocks.py
- src/robusta/core/reporting/blocks.py
- Sort summary rows by firing count, then resolved count, rather than by the total. The group key is now only a final tie-break, so the Fired column reads in order instead of interleaving groups with equal totals. - Fix an IndexError when rendering a table with no rows: tabulate rejects maxcolwidths when there are no rows. This was pre-existing in to_table_string, and the new row-dropping path could also hit it when not even one row fits the size limit. - Add regression tests: an empty table renders, and to_markdown always closes its code fence and stays under BLOCK_SIZE_LIMIT across a sweep of row counts and value widths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he UI The "View all N groups" platform link was misleading: the timeline's grouping is a fixed enum (ALERT_NAME/APP_NAME/CLUSTER/NAMESPACE/POD_NAME) and can't group by an arbitrary label, so the link could never show the same breakdown as the digest. Use the approach already taken for wide tables on regular findings (Transformer.tableblock_to_fileblocks) instead: when rows don't fit in the message, attach the complete table as a file rendered at width 250, where nothing wraps and every group is present. Slack files are immutable while the summary message is rewritten on every notification, so the file is managed rather than edited: - uploaded only when rows were actually dropped - refreshed at most once a minute, not once per notification - the superseded file is deleted whenever a new one is uploaded - the file id outlives the interval reset (the link does not), so the last file of an interval is still cleaned up by the next upload - an upload failure (e.g. no files:write scope) leaves the digest intact, just without the attachment The channel is now resolved before the upload, so an update that can't proceed no longer leaves an orphaned file behind. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…y keys - to_markdown reserved the code-fence envelope after capping the budget, so a very long table_name (~2990+ chars) still produced a block over the limit, which MarkdownBlock then cut - losing the closing fence, the exact failure this PR set out to fix. The name is now trimmed to fit, and a non-positive content budget yields an empty but properly closed block rather than an overflow marker that wouldn't fit either. - Summary group keys hold raw attribute values, so a key can contain None (e.g. "workload" for a finding with no service). Comparing None with str raised TypeError while sorting; keys are now normalised to strings first. This crashed on master too, where the sort compared keys unconditionally. Both found by review; verified against a 192-combination sweep of row counts, value widths, table-name lengths and max_chars values. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e-truncation-fix-4627ca
What & why
Jira ROB-3946 / Linear FRO-211
The Slack "Alerts Summary - N Notifications" digest rendered a corrupted table. Both screenshots on the ticket wrap long
label:sitevalues (Java class names) across two lines — but only the large one (prod-nj1, 103 notifications) is broken. That difference is the key to the bug.Root cause
Two separate things, and it takes both to produce the corruption:
label:siteis capped at 42 chars (the 70-char budget minuslabel:component15,Fired5,Resolved8), andtabulate(maxcolwidths=...)wraps over-wide values onto a second line rather than truncating.MarkdownBlockblind-cuts atBLOCK_SIZE_LIMIT(2997) without repairing the code fence. Doubling the height pushes a large digest past that limit, so the closing```gets chopped — Slack then renders the whole table as proportional, unaligned text. The cut also lands mid-row, leaving a wrapped value's continuation behind and displaying a partial class name as if it were complete.The small digest stays under the limit, so it survives with wrapping intact. That's why one screenshot looks fine and the other doesn't.
The fix
Long values are not truncated — per review feedback the full alert path must stay visible, so wrapping is kept. Instead the actual breakage is guarded, and the complete table is attached as a file.
Making the message safe
... 184 more groups (502 fired, 170 resolved) not shown. The residual counts keep the table reconciling with theN Notificationsheader.to_markdownrenders within a budget that always stays underBLOCK_SIZE_LIMIT, soMarkdownBlocknever blind-cuts the text.chat_updatethat happens on every notification).Fired/Resolveddon't wrap; the reduction is water-filled across the widest text columns instead of gutting one.Attaching the full table
Nothing is lost when rows are dropped: the complete table is uploaded as a file, rendered at width 250 where nothing wraps and every group is present. This reuses the approach already taken for wide tables on regular findings (
Transformer.tableblock_to_fileblocks), which the summary path simply never used.Slack files are immutable while the summary message is rewritten on every notification, so the file is managed rather than edited:
files:writescope) leaves the digest intact, just without the attachmentAn earlier revision linked to the platform timeline instead. That was dropped: the UI's grouping is a fixed enum (
ALERT_NAME/APP_NAME/CLUSTER/NAMESPACE/POD_NAME) and can't group by an arbitrary label, so the link could never show the same breakdown as the digest.Also fixed along the way:
get_timeline_uripassedcluster=<name>, but the timeline readsclustersas a JSON list — the existing "Investigate" link was never cluster-filtered.tabulateraisesIndexErrorwhen givenmaxcolwidthswith zero rows, so an emptyTableBlockcrashed. Pre-existing on master and reachable from every sink.TypeErrorwhen a group key mixedNonewith strings (workloadisNonefor a finding with no service). Also pre-existing, and hit more easily on master.Before / after (200 groups)
Before — cut mid-value, fence lost:
After — whole rows, explicit omission, fence intact, full table attached:
Scope
blocks.pyis shared, soto_markdownaffects every markdown sink (Slack, Discord, Mattermost, RocketChat, Jira) — the fence bug was never Slack-specific.to_table_stringkeeps its previous behaviour; only column-width allocation changed. The attachment is Slack-only, in the summary path.Tests
76 passing, including a new
tests/test_slack_summary_attachment.pycovering the attachment lifecycle (no upload when the table fits, upload when rows are dropped, throttling, deletion of the superseded file, cross-interval cleanup, and an upload failure leaving the digest intact).tests/test_blocks.pycovers the rendering: values wrap rather than being cut, whole rows are dropped, the omission note receives the dropped rows, numeric columns aren't shrunk, headerless/ragged rows, empty tables, and a sweep asserting the fence always closes and output stays under the limit across row counts, value widths, table-name lengths andmax_chars.Verified end-to-end by posting real digests to Slack through
send_or_update_summary_message— at 12 rows (fits, renders identically to master) and at 200 rows (overflows, drops rows and attaches the full table).Not addressed
clustersquery-param fix is inferred from the frontend route definition; worth clicking the Investigate button once against a real account to confirm.🤖 Generated with Claude Code