Skip to content

FRO-211 / ROB-3946 Fix corrupted Alerts Summary table when the digest is too long#2123

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

FRO-211 / ROB-3946 Fix corrupted Alerts Summary table when the digest is too long#2123
alonelish wants to merge 9 commits into
masterfrom
claude/summary-table-truncation-fix-4627ca

Conversation

@alonelish

@alonelish alonelish commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

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:site values (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:

  1. Wrapping doubles the table's height. label:site is capped at 42 chars (the 70-char budget minus label:component 15, Fired 5, Resolved 8), and tabulate(maxcolwidths=...) wraps over-wide values onto a second line rather than truncating.
  2. MarkdownBlock blind-cuts at BLOCK_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

  • Drop whole rows, never part of one. When the table exceeds the budget, complete rows are removed and a note is appended: ... 184 more groups (502 fired, 170 resolved) not shown. The residual counts keep the table reconciling with the N Notifications header.
  • The fence can no longer break. to_markdown renders within a budget that always stays under BLOCK_SIZE_LIMIT, so MarkdownBlock never blind-cuts the text.
  • Drop the least significant rows. Rows were previously sorted alphabetically, so whichever groups sorted last were discarded regardless of how noisy they were. Now sorted by firing count, then resolved count, with the group key only as a tie-break (keeping order stable across the chat_update that happens on every notification).
  • Numeric columns are never shrunk, so Fired/Resolved don'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:

  • 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. missing files:write scope) leaves the digest intact, just without the attachment

An 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_uri passed cluster=<name>, but the timeline reads clusters as a JSON list — the existing "Investigate" link was never cluster-filtered.
  • tabulate raises IndexError when given maxcolwidths with zero rows, so an empty TableBlock crashed. Pre-existing on master and reachable from every sink.
  • Sorting the summary crashed with TypeError when a group key mixed None with strings (workload is None for a finding with no service). Also pre-existing, and hit more easily on master.

Before / after (200 groups)

Before — cut mid-value, fence lost:

 ats.betting.betcatcher.validation.impl.Val | n...
[chars=3000  fence_closed=False]

After — whole rows, explicit omission, fence intact, full table attached:

 idator016Foo                               |                   |         |
... 183 more rows not shown
[chars=2981  fence_closed=True]

Scope

blocks.py is shared, so to_markdown affects every markdown sink (Slack, Discord, Mattermost, RocketChat, Jira) — the fence bug was never Slack-specific. to_table_string keeps 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.py covering 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.py covers 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 and max_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

  • A digest with many distinct groups still shows only ~18 rows inline, since wrapping costs two lines each. The attachment covers the rest.
  • The clusters query-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

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 510aab7 (built in 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: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:510aab7

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:510aab7

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

TableBlock now preserves numeric columns while fitting widths, truncates Markdown by whole rows with omission notes, and supports Slack summary integration. Timeline links use encoded query parameters, with regression tests covering rendering and size limits.

Changes

Table rendering and Slack summaries

Layer / File(s) Summary
Numeric-aware width calculation
src/robusta/core/reporting/blocks.py, tests/test_blocks.py
Adds minimum column widths, preserves numeric-only values during shrinking, and covers long, numeric, all-numeric, headerless, and ragged tables.
Budgeted Markdown rendering
src/robusta/core/reporting/blocks.py, tests/test_blocks.py
Fits whole rows within the Markdown budget, appends default or custom omission notes, preserves code fences, and validates complete wrapped values.
Slack summary ordering and links
src/robusta/integrations/slack/sender.py
Sorts groups by notification totals, renders omission-aware summaries, and conditionally adds a link to all groups.

Timeline URL encoding

Layer / File(s) Summary
Encoded timeline URI
src/robusta/core/sinks/slack/slack_sink.py
Builds timeline URLs with JSON-encoded cluster parameters and URL-encoded query strings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: avi-robusta

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly names the main change: fixing corrupted Alerts Summary tables when the digest is too long.
Description check ✅ Passed The description is detailed and clearly describes the table corruption bug, the fix, and related behavior changes.
✨ Finishing Touches
🧪 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 thread src/robusta/core/reporting/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>

@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

🤖 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

📥 Commits

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

📒 Files selected for processing (4)
  • src/robusta/core/reporting/blocks.py
  • src/robusta/core/sinks/slack/slack_sink.py
  • src/robusta/integrations/slack/sender.py
  • tests/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

Comment thread src/robusta/integrations/slack/sender.py Outdated
- 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>
@alonelish alonelish changed the title FRO-211 / ROB-3946 Truncate over-wide summary table cells instead of wrapping FRO-211 / ROB-3946 Fix corrupted Alerts Summary table when the digest is too long Jul 27, 2026
alonelish and others added 3 commits July 27, 2026 09:09
…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>
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