diff --git a/elementary/monitor/alerts/alert_messages/builder.py b/elementary/monitor/alerts/alert_messages/builder.py index 486cf9692..bc7d06760 100644 --- a/elementary/monitor/alerts/alert_messages/builder.py +++ b/elementary/monitor/alerts/alert_messages/builder.py @@ -57,6 +57,7 @@ class MessageBuilderConfig(BaseModel): alert_groups_subscribers: bool = False maximum_columns_in_alert_samples: int = 4 + maximum_rows_in_alert_samples: int = 25 class AlertMessageBuilder: @@ -377,9 +378,31 @@ def _get_result_blocks( and len(result_sample[0].keys()) <= self.config.maximum_columns_in_alert_samples ): + rows_to_render = result_sample + omitted_rows_count = 0 + if len(result_sample) > self.config.maximum_rows_in_alert_samples: + rows_to_render = result_sample[ + : self.config.maximum_rows_in_alert_samples + ] + omitted_rows_count = len(result_sample) - len(rows_to_render) result_blocks.append( - TableBlock.from_dicts(result_sample), + TableBlock.from_dicts(rows_to_render), ) + if omitted_rows_count: + result_blocks.append( + LinesBlock( + lines=[ + ItalicTextLineBlock( + text=( + f"Showing {len(rows_to_render)} of " + f"{len(result_sample)} rows " + f"({omitted_rows_count} omitted to keep " + "the alert size manageable)." + ) + ), + ] + ) + ) else: result_blocks.append( JsonCodeBlock(content=result_sample), diff --git a/tests/unit/alerts/alert_messages/test_result_blocks_row_truncation.py b/tests/unit/alerts/alert_messages/test_result_blocks_row_truncation.py new file mode 100644 index 000000000..5e233d01d --- /dev/null +++ b/tests/unit/alerts/alert_messages/test_result_blocks_row_truncation.py @@ -0,0 +1,92 @@ +from typing import List + +from elementary.messages.blocks import ExpandableBlock, LinesBlock, TableBlock +from elementary.monitor.alerts.alert_messages.builder import MessageBuilderConfig +from tests.unit.alerts.alert_messages.test_alert_utils import ( + build_base_test_alert_model, + get_alert_message_body, +) + + +def _build_test_rows_sample(row_count: int) -> List[dict]: + return [{"column1": f"value_{i}", "column2": i} for i in range(row_count)] + + +def _get_test_result_expandable(message_body) -> ExpandableBlock: + for block in message_body.blocks: + if isinstance(block, ExpandableBlock) and block.title == "Test Result": + return block + raise AssertionError("Expected a 'Test Result' expandable block") + + +def _get_table_block(blocks) -> TableBlock: + for block in blocks: + if isinstance(block, TableBlock): + return block + raise AssertionError("Expected a table block among the result blocks") + + +def _extract_line_texts(blocks) -> List[str]: + texts: List[str] = [] + for block in blocks: + if not isinstance(block, LinesBlock): + continue + for line in block.lines: + for inline in line.inlines: + text = getattr(inline, "text", None) + if text: + texts.append(text) + return texts + + +def test_large_result_sample_is_truncated_to_configured_row_limit(): + config = MessageBuilderConfig(maximum_rows_in_alert_samples=10) + alert = build_base_test_alert_model( + status="fail", + table_name="test_table", + tags=None, + owners=None, + subscribers=None, + test_description=None, + error_message=None, + test_rows_sample=_build_test_rows_sample(37), + test_results_query=None, + test_params=None, + ) + + message_body = get_alert_message_body(alert, config=config) + result_body = _get_test_result_expandable(message_body).body + + table_block = _get_table_block(result_body) + assert len(table_block.rows) == 10 + + truncation_notes = _extract_line_texts(result_body) + assert any( + "Showing 10 of 37 rows" in text and "27 omitted" in text + for text in truncation_notes + ), f"Expected a truncation note, got: {truncation_notes}" + + +def test_small_result_sample_is_not_truncated(): + config = MessageBuilderConfig(maximum_rows_in_alert_samples=10) + alert = build_base_test_alert_model( + status="fail", + table_name="test_table", + tags=None, + owners=None, + subscribers=None, + test_description=None, + error_message=None, + test_rows_sample=_build_test_rows_sample(3), + test_results_query=None, + test_params=None, + ) + + message_body = get_alert_message_body(alert, config=config) + result_body = _get_test_result_expandable(message_body).body + + table_block = _get_table_block(result_body) + assert len(table_block.rows) == 3 + + truncation_notes = _extract_line_texts(result_body) + assert not any("omitted" in text for text in truncation_notes)