Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions snowcap/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from itertools import zip_longest
from typing import (
Any,
Generator,
Expand Down Expand Up @@ -887,6 +888,29 @@ def _summarize_plan_value(value, max_len: int = 60) -> str:
return f"<{lines} {unit}, {len(text)} chars>"


def _plan_value_rows(key: str, before, after) -> list[list[str]]:
"""Rows of the plan table for one changed property.

A list is shown one item per row, before and after side by side, so the plan names the
items that changed. As one value, a list of IP addresses or triggers soon outgrows the
table width and collapses to its length. A dict item is shown as `key: value` pairs, and
an item is shown in full however long it is: only a multiline item collapses, because the
table cannot lay out a newline.
"""
if isinstance(before, list) or isinstance(after, list):
pairs = list(zip_longest(map(_plan_item, before or []), map(_plan_item, after or []), fillvalue=""))
if pairs:
return [[key if index == 0 else "", old, new] for index, (old, new) in enumerate(pairs)]
return [[key, _summarize_plan_value(before), _summarize_plan_value(after)]]


def _plan_item(item) -> str:
if isinstance(item, dict):
item = ", ".join(f"{field}: {value}" for field, value in item.items())
text = str(item)
return _summarize_plan_value(text) if "\n" in text else text


def _render_table(rows: list[list[str]], headers: list[str]) -> str:
"""
Render a table with box-drawing characters.
Expand Down Expand Up @@ -1191,14 +1215,7 @@ def _dump_plan_text(plan: Plan) -> str:
for key, new_value in change.delta.items():
if key.startswith("_"):
continue
before = change.before.get(key, "")
rows.append(
[
key,
_summarize_plan_value(before),
_summarize_plan_value(new_value),
]
)
rows.extend(_plan_value_rows(key, change.before.get(key, ""), new_value))

if rows:
table = _render_table(rows, ["Property", "Before", "After"])
Expand Down
48 changes: 48 additions & 0 deletions tests/test_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2849,6 +2849,54 @@ def test_tables_are_listed_from_account_usage_when_it_is_on(self):
assert not any("SHOW TABLES IN ACCOUNT" in statement for statement in sql)


@pytest.mark.parametrize(
"resource_cls, urn, key, before, after, expected_rows",
[
(
res.NetworkPolicy,
"urn::ABCD123:network_policy/OFFICE",
"allowed_ip_list",
["10.0.0.1/32", "10.0.0.2/32", "10.0.0.3/32", "10.0.0.4/32", "10.0.0.5/32"],
["10.0.0.1/32", "10.0.0.2/32", "10.0.0.3/32", "10.0.0.4/32", "10.0.0.6/32"],
[
"│ allowed_ip_list │ 10.0.0.1/32 │ 10.0.0.1/32 │",
"│ │ 10.0.0.2/32 │ 10.0.0.2/32 │",
"│ │ 10.0.0.3/32 │ 10.0.0.3/32 │",
"│ │ 10.0.0.4/32 │ 10.0.0.4/32 │",
"│ │ 10.0.0.5/32 │ 10.0.0.6/32 │",
],
),
(
res.HybridTable,
"urn::ABCD123:hybrid_table/DB.SCH.LISTINGS",
"indexes",
[{"name": "IDX_CITY", "columns": ["CITY"]}],
[
{"name": "IDX_CITY", "columns": ["CITY"]},
{"name": "IDX_REGION", "columns": ["STATE", "COUNTY", "CITY", "POSTAL_CODE"]},
],
[
"│ indexes │ name: IDX_CITY, columns: ['CITY'] │ name: IDX_CITY, columns: ['CITY'] │",
"│ │ │ name: IDX_REGION, columns: ['STATE', 'COUNTY', 'CITY', 'POSTAL_CODE'] │",
],
),
],
)
def test_plan_text_shows_each_item_of_a_changed_list(resource_cls, urn, key, before, after, expected_rows):
"""A list longer than the table's value width is shown one item per row, so the plan says
which items changed instead of only how long the list is."""
change = UpdateResource(
urn=parse_URN(urn),
resource_cls=resource_cls,
before={key: before},
after={key: after},
delta={key: after},
)
plan_lines = [line.strip() for line in strip_ansi(dump_plan([change], format="text")).splitlines()]
start = plan_lines.index(expected_rows[0])
assert plan_lines[start : start + len(expected_rows)] == expected_rows


class TestSummarizePlanValue:
"""The plan table must not dump a multiline SQL body (alert THEN, task body)."""

Expand Down
Loading