Skip to content

Commit f421d92

Browse files
committed
Display impacts in cli
1 parent 27e6595 commit f421d92

File tree

2 files changed

+59
-16
lines changed

2 files changed

+59
-16
lines changed

datajunction-clients/python/datajunction/cli.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
logger = logging.getLogger(__name__)
2323

2424

25-
2625
class DJCLI:
2726
"""DJ command-line tool"""
2827

datajunction-clients/python/datajunction/deployment.py

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
def _short_name(name: str, namespace: str) -> str:
5757
"""Strip the namespace prefix from a node name for compact display."""
5858
prefix = namespace + "."
59-
return name[len(prefix):] if name.startswith(prefix) else name
59+
return name[len(prefix) :] if name.startswith(prefix) else name
6060

6161

6262
def _node_type_display(node_type: str) -> str:
@@ -65,11 +65,11 @@ def _node_type_display(node_type: str) -> str:
6565

6666

6767
def _impact_annotation(imp: dict, namespace: str, current_parent: str = "") -> str:
68-
"""Dim annotation: cube reason or multi-cause 'also via' list."""
69-
if imp.get("node_type") == "cube":
70-
reason = imp.get("impact_reason", "")
71-
return f" [dim]({reason})[/dim]" if reason else ""
68+
"""Dim annotation: cube 'via' list or multi-cause 'also via' list."""
7269
caused_by = imp.get("caused_by", [])
70+
if imp.get("node_type") == "cube":
71+
via = [_short_name(c, namespace) for c in caused_by]
72+
return f" [dim](via: {', '.join(via)})[/dim]" if via else ""
7373
others = [_short_name(c, namespace) for c in caused_by if c != current_parent]
7474
return f" [dim](also via: {', '.join(others)})[/dim]" if others else ""
7575

@@ -120,7 +120,7 @@ def _render_impact_item(
120120
children = [
121121
ch
122122
for ch in impacts_by_cause.get(item_name, [])
123-
if ch.get("name") not in rendered
123+
if ch.get("name") not in rendered and ch.get("node_type") != "cube"
124124
]
125125
rendered.update(ch["name"] for ch in children if ch.get("name"))
126126
if children:
@@ -150,7 +150,14 @@ def _render_impacts(
150150
b = indent + ("└" if is_last else "├")
151151
c = indent + (" " if is_last else "│")
152152
_render_impact_item(
153-
imp, b, c, con, namespace, impacts_by_cause, rendered, current_parent,
153+
imp,
154+
b,
155+
c,
156+
con,
157+
namespace,
158+
impacts_by_cause,
159+
rendered,
160+
current_parent,
154161
)
155162

156163

@@ -192,6 +199,28 @@ def _print_changed_fields(console: Console, change: dict) -> None:
192199
console.print(f" [red dim]{truncated}[/red dim]")
193200

194201

202+
def _collect_transitive_cubes(
203+
node_name: str,
204+
impacts_by_cause: dict[str, list[dict]],
205+
) -> list[dict]:
206+
"""Collect all cube impacts transitively reachable from node_name, deduped by name."""
207+
cubes: dict[str, dict] = {}
208+
visited: set[str] = set()
209+
queue = [node_name]
210+
while queue:
211+
current = queue.pop()
212+
for imp in impacts_by_cause.get(current, []):
213+
name = imp.get("name", "")
214+
if name in visited:
215+
continue
216+
visited.add(name)
217+
if imp.get("node_type") == "cube":
218+
cubes[name] = imp
219+
else:
220+
queue.append(name)
221+
return list(cubes.values())
222+
223+
195224
def _print_change_tree(
196225
console: Console,
197226
change: dict,
@@ -201,20 +230,25 @@ def _print_change_tree(
201230
"""Print the tree of column changes, dim link changes, and downstream impacts."""
202231
column_changes = change.get("column_changes", [])
203232
dim_link_changes = change.get("dim_link_changes", [])
204-
node_downstream = impacts_by_cause.get(change.get("name", ""), [])
233+
change_name = change.get("name", "")
234+
node_downstream = impacts_by_cause.get(change_name, [])
205235

206236
explicit_removals = [d for d in dim_link_changes if d.get("operation") == "removed"]
207237
dim_link_additions = [d for d in dim_link_changes if d.get("operation") == "added"]
208238
dim_link_updates = [d for d in dim_link_changes if d.get("operation") == "updated"]
209239
broken_by_col = _build_broken_by_col(dim_link_changes)
210240
rendered: set[str] = set()
211241

242+
non_cube_downstream = [d for d in node_downstream if d.get("node_type") != "cube"]
243+
cube_downstream = _collect_transitive_cubes(change_name, impacts_by_cause)
244+
212245
tree_items = (
213246
list(column_changes)
214247
+ explicit_removals
215248
+ dim_link_additions
216249
+ dim_link_updates
217-
+ node_downstream
250+
+ non_cube_downstream
251+
+ cube_downstream
218252
)
219253
for i, item in enumerate(tree_items):
220254
is_last = i == len(tree_items) - 1
@@ -257,8 +291,14 @@ def _print_change_tree(
257291
item_name = item.get("name", "")
258292
rendered.add(item_name)
259293
_render_impact_item(
260-
item, branch, cont, console, namespace, impacts_by_cause,
261-
rendered, change.get("name", ""),
294+
item,
295+
branch,
296+
cont,
297+
console,
298+
namespace,
299+
impacts_by_cause,
300+
rendered,
301+
change_name,
262302
)
263303

264304

@@ -286,13 +326,17 @@ def _print_impact_summary(
286326
f"[yellow]{update_count} update{'s' if update_count != 1 else ''}[/yellow]",
287327
)
288328
if delete_count:
289-
summary_parts.append(f"[red]{delete_count} delete{'s' if delete_count != 1 else ''}[/red]")
329+
summary_parts.append(
330+
f"[red]{delete_count} delete{'s' if delete_count != 1 else ''}[/red]",
331+
)
290332
if skip_count:
291333
summary_parts.append(f"[dim]{skip_count} skipped[/dim]")
292334
if will_invalidate:
293335
summary_parts.append(f"[red]{will_invalidate} downstream invalidated[/red]")
294336
if may_affect:
295-
summary_parts.append(f"[yellow]{may_affect} downstream may be affected[/yellow]")
337+
summary_parts.append(
338+
f"[yellow]{may_affect} downstream may be affected[/yellow]",
339+
)
296340

297341
if not active_changes and not downstream_impacts:
298342
console.print(
@@ -529,7 +573,6 @@ def pull(
529573
}
530574
yaml.safe_dump(project_spec, yaml_file, sort_keys=False)
531575

532-
533576
def push(
534577
self,
535578
source_path: str | Path,
@@ -596,7 +639,8 @@ def push(
596639
if deployment_data.get("status") == "failed":
597640
changes = deployment_data.get("changes", [])
598641
errors = [
599-
c for c in changes
642+
c
643+
for c in changes
600644
if c.get("validation_errors") or c.get("predicted_status") == "invalid"
601645
]
602646
raise DJDeploymentFailure(

0 commit comments

Comments
 (0)