Skip to content

FRO-211 / ROB-3946 Truncate over-wide summary table cells instead of wrapping#2123

Open
alonelish wants to merge 4 commits into
masterfrom
claude/summary-table-truncation-fix-4627ca
Open

FRO-211 / ROB-3946 Truncate over-wide summary table cells instead of wrapping#2123
alonelish wants to merge 4 commits into
masterfrom
claude/summary-table-truncation-fix-4627ca

Conversation

@alonelish

Copy link
Copy Markdown
Collaborator

What & why

Jira ROB-3946 / Linear FRO-211

The Slack "Alerts Summary - N Notifications" grouping digest rendered a corrupted ASCII table. When the label:site column held long Java class names (e.g. ats.betting.betcatcher.settlement.settler.AbstractBetSettler), each value was word-wrapped across multiple physical lines instead of being truncated — mangling every row (ServiceImpl / erviceImpl spilling onto the next line).

Root cause

TableBlock.to_table_string() passed maxcolwidths=col_max_width to tabulate, and tabulate wraps over-wide cells onto extra physical lines rather than truncating them. The digest is built in send_or_update_summary_message()TableBlock(...)to_markdown()to_table_string(). The bug title says "trimmed" but the code wrapped.

The fix (src/robusta/core/reporting/blocks.py)

  • Truncate, don't wrap. to_table_string() now truncates over-wide cells itself with a single-char ellipsis and no longer passes maxcolwidths to tabulate, so every row stays on one physical line.
  • Left-trim dotted class names. For space-free dotted qualified names (Java FQCNs), truncation keeps the distinctive suffix (…settler.AbstractBetSettler), since the class name is at the end; plain text (with spaces) is trimmed from the right. (1 char) is used over ... (3 chars) so width accounting stays exact.
  • Never shrink numeric columns. __calc_max_width now detects columns whose non-empty cells are all numeric (the Fired / Resolved counters) and leaves them at full width; the overflow is water-filled across the widest text columns (fair distribution + a minimum-width floor) instead of gutting a single column.

Tables that already fit render byte-identical to before.

Before / after (real prod-az2 screenshot data)

Before — wrapped:

 ats.betting.betcatcher.service.BetcatcherS | az  |   0 |   1
 erviceImpl                                 |     |     |

After — single line, suffix preserved:

 ….betcatcher.service.BetcatcherServiceImpl | az                |       0 |          1
 …ation.impl.AccountRestrictionBetValidator | az                |       0 |          1
 ats.wallet.DefaultFundsAdjuster            | az                |       0 |          1

Tests

  • Adds regression tests in tests/test_blocks.py proving rows stay single-line and over-wide cells are truncated (not wrapped): line-count invariant, truncation, dotted-name suffix retention, numeric columns never truncated, and trailing-cut for plain text.
  • Existing test_transformer.py table/HTML tests still pass (the HTML path uses tabulate directly and is unaffected).
pytest tests/test_blocks.py tests/test_transformer.py -k "to_table_string or to_markdown_wide or to_html"
21 passed

Manual verification in Slack

The change is centralized in to_table_string, so it affects every sink that renders a TableBlock (Slack, Discord, Google Chat, Zulip, PagerDuty, incident.io, …). To exercise the exact digest path end-to-end, run the manual repro (not part of this PR unless requested):

SLACK_TOKEN=xoxb-... SLACK_CHANNEL=test-robusta python tests/manual_tests/summary_table_slack_repro.py

🤖 Generated with Claude Code

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>
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

Docker image ready for e80816d (built in 2m 36s)

⚠️ Warning: does not support ARM (ARM images are built on release only - not on every PR)

Use this tag to pull the image for testing.

📋 Copy commands

⚠️ Temporary images are deleted after 30 days. Copy to a permanent registry before using them:

gcloud auth configure-docker us-central1-docker.pkg.dev
docker pull us-central1-docker.pkg.dev/robusta-development/temporary-builds/robusta-runner:e80816d
docker tag us-central1-docker.pkg.dev/robusta-development/temporary-builds/robusta-runner:e80816d me-west1-docker.pkg.dev/robusta-development/development/robusta-runner-dev:e80816d
docker push me-west1-docker.pkg.dev/robusta-development/development/robusta-runner-dev:e80816d

Patch 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:e80816d

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

TableBlock now calculates numeric-aware column widths, renders within character budgets by dropping whole rows, and applies the remaining Markdown block budget. Tests cover wrapped values, numeric columns, ragged rows, and fenced Markdown output.

Changes

Table rendering

Layer / File(s) Summary
Numeric-aware width calculation
src/robusta/core/reporting/blocks.py
Adds a minimum column width, preserves numeric columns during width reduction, and water-fills reductions across non-numeric columns.
Budgeted table and Markdown rendering
src/robusta/core/reporting/blocks.py
Renders with computed widths, removes whole rows to fit character limits, falls back to line trimming when necessary, and accounts for the Markdown block limit.
Rendering regression coverage
tests/test_blocks.py
Tests wrapped long values, numeric and ragged tables, all-numeric width handling, omission notes, complete values, and closing Markdown fences.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: avi-robusta

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: adjusting summary table cells to avoid wrapping.
Description check ✅ Passed The description clearly describes the table truncation and width-handling changes in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/summary-table-truncation-fix-4627ca

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between eb350a4 and bdd71c6.

📒 Files selected for processing (2)
  • src/robusta/core/reporting/blocks.py
  • tests/test_blocks.py

Comment thread src/robusta/core/reporting/blocks.py Outdated
Comment thread src/robusta/core/reporting/blocks.py Outdated
- __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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/test_blocks.py (1)

159-162: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant and ineffective wrap-spillover assertion.

The assertion not any(line.strip() in ("ServiceImpl", "erviceImpl") for line in lines) is technically ineffective. Because the presto table format includes column dividers (e.g., |), a wrapped line would look something like erviceImpl | |, meaning line.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

📥 Commits

Reviewing files that changed from the base of the PR and between bdd71c6 and aeac173.

📒 Files selected for processing (2)
  • src/robusta/core/reporting/blocks.py
  • tests/test_blocks.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/robusta/core/reporting/blocks.py

@alonelish alonelish changed the title ROB-3946 Truncate over-wide summary table cells instead of wrapping FRO-211 / ROB-3946 Truncate over-wide summary table cells instead of wrapping Jul 15, 2026
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>
@alonelish
alonelish requested a review from Avi-Robusta July 15, 2026 14:27
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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 win

Account for presto’s separator and padding width in __calc_max_width.

presto renders column contents plus framing: separators, padding, and the separator row. With table_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

📥 Commits

Reviewing files that changed from the base of the PR and between 82fb6a2 and e24c43d.

📒 Files selected for processing (2)
  • src/robusta/core/reporting/blocks.py
  • tests/test_blocks.py

Comment on lines +491 to 499
# Stay under BLOCK_SIZE_LIMIT even when no caller-supplied limit: MarkdownBlock
# would otherwise blindly cut the text and lose the closing ``` fence, which
# makes the whole table render as unformatted (non-monospace) text.
budget = BLOCK_SIZE_LIMIT - 1 if max_chars is None else min(max_chars, BLOCK_SIZE_LIMIT - 1)
table_contents = self.__render_within_budget(
budget - len(prefix) - len(suffix), PRINTED_TABLE_MAX_WIDTH, "presto"
)

return MarkdownBlock(f"{prefix}{table_contents}{suffix}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the prefix inside the requested budget.

Line 494 caps the total budget before reserving table_name and the fences. A long table name—or max_chars smaller than the fence envelope—passes a negative inner budget; __trim_rows() then returns "\n...", and the final Markdown exceeds the limit. With the default limit, this can still trigger the blind truncation and lost closing fence described here. Trim/validate the prefix first, and handle a non-positive content budget without adding an overflow marker.

🤖 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 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.

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.

1 participant