Skip to content

Commit 83c0029

Browse files
authored
Fix a couple of edge and node filtering bugs (#56)
* Fix FocusPackEdgeDirection::None not properly handled for show edge calculation * More compact tests for .show_edge_builder * Fix bug where dependencies would cause non-focus packs to be shown despite the fact that the edges would not be shown * Make readable calculations for filters for dependents, dependencies, todos out, and todos * Nicer switch between show edge on/off and in/out/inout/none/all!? * Show todo types at the end of title * Rename packs used in .filtered tests * Fix in/out directionality bug * Table-based tests for filtering * No need to make empty focus_pack array a special case * Test partial node input lists * Added some more specs * Add visual test for edge focus mode “in" * Trim title length * Updated cassettes * Bump gem version * Fix digram title tests
1 parent bdb547b commit 83c0029

22 files changed

Lines changed: 359 additions & 324 deletions

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
visualize_packs (0.5.19)
4+
visualize_packs (0.5.20)
55
packs-specification
66
parse_packwerk (>= 0.20.0)
77
sorbet-runtime

diagram_examples.png

66.1 KB
Loading

lib/visualize_packs.rb

Lines changed: 81 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -70,25 +70,31 @@ def self.code_owner(package)
7070
def self.diagram_title(options, max_todo_count)
7171
return "<<b>#{options.title}</b>>" if options.title
7272

73-
app_name = File.basename(Dir.pwd)
74-
focus_edge_info = options.focus_pack && options.show_only_edges_to_focus_pack != FocusPackEdgeDirection::All ? "showing only edges to/from focus pack" : "showing all edges between visible packs"
75-
focus_info = options.focus_pack ? "Focus on #{limited_sentence(options.focus_pack)} (#{focus_edge_info})" : "All packs"
76-
skipped_info =
77-
[
78-
options.show_legend ? nil : "hiding legend",
79-
options.show_layers ? nil : "hiding layers",
80-
options.show_dependencies ? nil : "hiding dependencies",
81-
options.show_todos ? nil : "hiding todos",
82-
EdgeTodoTypes.values.size == options.only_todo_types.size ? nil : "only #{limited_sentence(options.only_todo_types.map &:serialize)} todos",
83-
options.show_privacy ? nil : "hiding privacy",
84-
options.show_teams ? nil : "hiding teams",
85-
options.show_visibility ? nil : "hiding visibility",
86-
options.roll_nested_into_parent_packs ? "hiding nested packs" : nil,
87-
options.show_nested_relationships ? nil : "hiding nested relationships",
88-
options.exclude_packs.empty? ? nil : "excluding pack#{options.exclude_packs.size > 1 ? 's' : ''}: #{limited_sentence(options.exclude_packs)}",
73+
focus_info = if options.focus_pack
74+
"Focus on #{limited_sentence(options.focus_pack)} (Edge mode: #{options.show_only_edges_to_focus_pack.serialize})"
75+
else
76+
"All packs"
77+
end
78+
79+
hidden_aspects = [
80+
options.show_legend ? nil : "legend",
81+
options.show_layers ? nil : "layers",
82+
options.show_dependencies ? nil : "dependencies",
83+
options.show_todos ? nil : "todos",
84+
options.show_privacy ? nil : "privacy",
85+
options.show_teams ? nil : "teams",
86+
options.show_visibility ? nil : "visibility",
87+
options.roll_nested_into_parent_packs ? "nested packs" : nil,
88+
options.show_nested_relationships ? nil : "nested relationships",
8989
].compact.join(', ').strip
90-
main_title = "#{app_name}: #{focus_info}#{skipped_info != '' ? ' - ' + skipped_info : ''}"
91-
sub_title = ""
90+
hidden_aspects_title = hidden_aspects != '' ? "Hiding #{hidden_aspects}" : nil
91+
92+
todo_types = EdgeTodoTypes.values.size == options.only_todo_types.size ? nil : "Only #{options.only_todo_types.map &:serialize} todos",
93+
94+
exclusions = options.exclude_packs.empty? ? nil : "Excluding pack#{options.exclude_packs.size > 1 ? 's' : ''}: #{limited_sentence(options.exclude_packs)}",
95+
96+
main_title = [focus_info, hidden_aspects_title, todo_types, exclusions].compact.join('. ')
97+
9298
if options.show_todos && max_todo_count
9399
sub_title = "<br/><font point-size='12'>Widest todo edge is #{max_todo_count} todo#{max_todo_count > 1 ? 's' : ''}</font>"
94100
end
@@ -115,6 +121,8 @@ def self.show_edge_builder(options, all_package_names)
115121
case options.show_only_edges_to_focus_pack
116122
when FocusPackEdgeDirection::All then
117123
true
124+
when FocusPackEdgeDirection::None then
125+
match_packs?(start_node, options.focus_pack) && match_packs?(end_node, options.focus_pack)
118126
when FocusPackEdgeDirection::InOut then
119127
match_packs?(start_node, options.focus_pack) || match_packs?(end_node, options.focus_pack)
120128
when FocusPackEdgeDirection::In then
@@ -200,36 +208,32 @@ def self.filtered(packages, options)
200208
result = T.let([], T::Array[T.nilable(String)])
201209
result = packages.map { |pack| pack.name }
202210

203-
if focus_pack && !focus_pack.empty?
211+
if focus_pack
204212
result = []
205-
result += packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_pack) }
206-
if options.show_dependencies
207-
result += packages.select { |p| p.dependencies.any? { |d| match_packs?(d, focus_pack) }}.map { |pack| pack.name }
208-
end
209-
if options.show_todos && [FocusPackEdgeDirection::All, FocusPackEdgeDirection::In, FocusPackEdgeDirection::InOut].include?(options.show_only_edges_to_focus_pack)
210-
result += packages.select do
211-
|p| (p.violations || []).inject([]) do |res, todo|
212-
res << todo.to_package_name if options.only_todo_types.include?(EdgeTodoTypes.deserialize(todo.type))
213-
res
214-
end.any? { |v| match_packs?(v, focus_pack) }
215-
end.map { |pack| pack.name }
213+
focus_pack_name = packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_pack) }
214+
result += focus_pack_name
215+
216+
dependents = options.show_dependencies ? dependents_on(packages, focus_pack_name) : []
217+
dependencies = options.show_dependencies ? dependencies_of(packages, focus_pack_name) : []
218+
todos_out = options.show_todos ? todos_out(packages, focus_pack_name, options) : []
219+
todos_in = options.show_todos ? todos_in(packages, focus_pack_name, options) : []
220+
221+
case options.show_only_edges_to_focus_pack
222+
when FocusPackEdgeDirection::All, FocusPackEdgeDirection::InOut then
223+
result += dependents + dependencies + todos_out + todos_in
224+
when FocusPackEdgeDirection::In then
225+
result += dependents + todos_in
226+
when FocusPackEdgeDirection::Out then
227+
result += dependencies + todos_out
228+
when FocusPackEdgeDirection::None then
229+
# nothing to do
216230
end
217-
packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_pack) }.each do |p|
218-
if options.show_dependencies
219-
result += packages_by_name[p].dependencies
220-
end
221-
if options.show_todos && [FocusPackEdgeDirection::All, FocusPackEdgeDirection::Out, FocusPackEdgeDirection::InOut].include?(options.show_only_edges_to_focus_pack)
222-
result += (packages_by_name[p].violations || []).inject([]) do |res, todo|
223-
res << todo.to_package_name if options.only_todo_types.include?(EdgeTodoTypes.deserialize(todo.type))
224-
res
225-
end
226-
end
227-
end
228-
result = result.uniq
231+
229232
parent_packs = result.inject([]) do |res, package_name|
230233
res << nested_packages[package_name]
231234
res
232235
end
236+
233237
result = (result + parent_packs).uniq.compact
234238
end
235239

@@ -318,4 +322,39 @@ def self.remove_nested_packs(packages, options)
318322
def self.match_packs?(pack, packs_name_with_wildcards)
319323
!packs_name_with_wildcards || packs_name_with_wildcards.any? {|p| File.fnmatch(p, pack)}
320324
end
325+
326+
sig { params(all_packages: T::Array[ParsePackwerk::Package], focus_packs_names: T::Array[String]).returns(T::Array[String]) }
327+
def self.dependencies_of(all_packages, focus_packs_names)
328+
focus_packs = all_packages.select { focus_packs_names.include?(_1.name)}
329+
330+
focus_packs.inject([]) do |result, pack|
331+
result += pack.dependencies
332+
result
333+
end.uniq
334+
end
335+
336+
sig { params(all_packages: T::Array[ParsePackwerk::Package], focus_packs_names: T::Array[String]).returns(T::Array[String]) }
337+
def self.dependents_on(all_packages, focus_packs_names)
338+
all_packages.select { |pack| pack.dependencies.any? { focus_packs_names.include?(_1) }}.map &:name
339+
end
340+
341+
sig { params(all_packages: T::Array[ParsePackwerk::Package], focus_packs_names: T::Array[String], options: Options).returns(T::Array[String]) }
342+
def self.todos_in(all_packages, focus_packs_names, options)
343+
all_packages.select do |p|
344+
(p.violations || []).inject([]) do |res, todo|
345+
res << todo.to_package_name if options.only_todo_types.include?(EdgeTodoTypes.deserialize(todo.type))
346+
res
347+
end.any? { |v| focus_packs_names.include?(v) }
348+
end.map { |pack| pack.name }
349+
end
350+
351+
sig { params(all_packages: T::Array[ParsePackwerk::Package], focus_packs_names: T::Array[String], options: Options).returns(T::Array[String]) }
352+
def self.todos_out(all_packages, focus_packs_names, options)
353+
all_packages.inject([]) do |result, p|
354+
focus_packs_names.include?(p.name) && (p.violations || []).each do |todo|
355+
result << todo.to_package_name if options.only_todo_types.include?(EdgeTodoTypes.deserialize(todo.type))
356+
end
357+
result
358+
end
359+
end
321360
end

spec/diagram_generation_helper.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ test_names+=("only_layers"); test_params[${test_names[-1]}]="--no-dependency-arr
3131
test_names+=("no_to_all"); test_params[${test_names[-1]}]="--title='Hide everything' --no-layers --no-visibility --no-dependency-arrows --no-todo-arrows --no-privacy-boxes --no-teams --no-nesting-arrows"
3232
test_names+=("focussed_on_packs_ui"); test_params[${test_names[-1]}]="--focus-pack=packs/ui"
3333
test_names+=("focussed_on_packs_ui_focus_edges"); test_params[${test_names[-1]}]="--focus-pack=packs/ui --focus-pack-edge-mode=inout"
34+
test_names+=("focussed_on_packs_ui_focus_edges_in"); test_params[${test_names[-1]}]="--focus-pack=packs/ui --focus-pack-edge-mode=in"
3435

3536
# Debugging...
3637
# echo "test_names: ${test_names[@]}"

spec/sample_app1/Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: ../..
33
specs:
4-
visualize_packs (0.5.19)
4+
visualize_packs (0.5.20)
55
packs-specification
66
parse_packwerk (>= 0.20.0)
77
sorbet-runtime

spec/sample_app1/test_output/focussed_on_packs_ui.dot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ digraph package_diagram {
55
fontname="Helvetica,Arial,sans-serif"
66
dpi=100
77
layout=dot
8-
label=<<b>sample_app1: Focus on packs/ui (showing all edges between visible packs)</b><br/><font point-size='12'>Widest todo edge is 5 todos</font>>
8+
label=<<b>Focus on packs/ui (Edge mode: all)</b><br/><font point-size='12'>Widest todo edge is 5 todos</font>>
99
fontsize=18
1010
]
1111
node [

spec/sample_app1/test_output/focussed_on_packs_ui_focus_edges.dot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ digraph package_diagram {
55
fontname="Helvetica,Arial,sans-serif"
66
dpi=100
77
layout=dot
8-
label=<<b>sample_app1: Focus on packs/ui (showing only edges to/from focus pack)</b><br/><font point-size='12'>Widest todo edge is 5 todos</font>>
8+
label=<<b>Focus on packs/ui (Edge mode: inout)</b><br/><font point-size='12'>Widest todo edge is 5 todos</font>>
99
fontsize=18
1010
]
1111
node [
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
digraph package_diagram {
2+
rankdir=TD
3+
graph [
4+
labelloc="t"
5+
fontname="Helvetica,Arial,sans-serif"
6+
dpi=100
7+
layout=dot
8+
label=<<b>Focus on packs/ui (Edge mode: in)</b>>
9+
fontsize=18
10+
]
11+
node [
12+
fontname="Helvetica,Arial,sans-serif"
13+
fontsize=26.0
14+
fontcolor=black
15+
fillcolor=white
16+
color=black
17+
height=1.0
18+
style=filled
19+
shape=plain
20+
]
21+
subgraph app {
22+
shape=box
23+
color=darkgrey
24+
fillcolor=lightblue
25+
style=filled
26+
label="app"
27+
cluster=true
28+
rank = 0 "packs/ui" [
29+
fontsize=18.0
30+
URL="https://github.com/rubyatscale/visualize_packwerk/tree/main/spec/sample_app/packs/ui"
31+
32+
style=filled
33+
fillcolor="#FFA9C5"
34+
label= <
35+
<table border='0' cellborder='1' cellspacing='0' cellpadding='4'>
36+
<tr> <td align='left'> packs/ui </td> </tr>
37+
</table>
38+
>
39+
]
40+
41+
}
42+
subgraph utilities {
43+
shape=box
44+
color=darkgrey
45+
fillcolor=lightblue
46+
style=filled
47+
label="utilities"
48+
cluster=true
49+
rank = 1 }
50+
subgraph NotInLayer {
51+
shape=box
52+
color=darkgrey
53+
fillcolor=lightblue
54+
style=filled
55+
cluster=false
56+
}
57+
subgraph cluster_legend {
58+
fontsize=16
59+
label="Edges Styles and Arrow Heads"
60+
A [ fontsize=12 shape=box label="package"]
61+
B [ fontsize=12 shape=box label="package"]
62+
A -> B [label="accepted dependency" color=darkgreen]
63+
K [ fontsize=12 shape=box label="package"]
64+
L [ fontsize=12 shape=box label="package"]
65+
K -> L [label="nested package" color=purple]
66+
M [ fontsize=12 shape=box label="package"]
67+
N [ fontsize=12 shape=box label="package"]
68+
M -> N [label="visibile to" color=blue]
69+
C [ fontsize=12 shape=box label="package"]
70+
D [ fontsize=12 shape=box label="package"]
71+
C -> D [label="privacy todo" color=darkred style=dashed arrowhead=crow]
72+
E [ fontsize=12 shape=box label="package"]
73+
F [ fontsize=12 shape=box label="package"]
74+
E -> F [label="architecture todo" color=darkred style=dashed arrowhead=obox]
75+
G [ fontsize=12 shape=box label="package"]
76+
H [ fontsize=12 shape=box label="package"]
77+
G -> H [label="visibility todo" color=darkred style=dashed arrowhead=tee]
78+
I [ fontsize=12 shape=box label="package"]
79+
J [ fontsize=12 shape=box label="package"]
80+
I -> J [label="dependency todo" color=darkred style=dashed arrowhead=odiamond]
81+
LEGEND_NODE_1 [ label="" peripheries=0 height=0 width=0 style=invis ]
82+
LEGEND_NODE_2 [ label="" peripheries=0 height=0 width=0 style=invis ]
83+
LEGEND_NODE_1 -> LEGEND_NODE_2 [ style=invis ]
84+
}
85+
subgraph cluster_teams_legend {
86+
fontsize=16
87+
label="Team Colors"
88+
89+
"UI TeamUI Team" [
90+
label="UI Team"
91+
style=filled
92+
fillcolor="#FFA9C5"
93+
fontsize=12
94+
shape=box
95+
]
96+
97+
}
98+
LEGEND_NODE_2 -> "UI TeamUI Team" [style=invis]
99+
}

spec/sample_app1/test_output/no_dependencies.dot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ digraph package_diagram {
55
fontname="Helvetica,Arial,sans-serif"
66
dpi=100
77
layout=dot
8-
label=<<b>sample_app1: All packs - hiding dependencies</b><br/><font point-size='12'>Widest todo edge is 5 todos</font>>
8+
label=<<b>All packs. Hiding dependencies</b><br/><font point-size='12'>Widest todo edge is 5 todos</font>>
99
fontsize=18
1010
]
1111
node [

spec/sample_app1/test_output/no_layers.dot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ digraph package_diagram {
55
fontname="Helvetica,Arial,sans-serif"
66
dpi=100
77
layout=dot
8-
label=<<b>sample_app1: All packs - hiding layers</b><br/><font point-size='12'>Widest todo edge is 5 todos</font>>
8+
label=<<b>All packs. Hiding layers</b><br/><font point-size='12'>Widest todo edge is 5 todos</font>>
99
fontsize=18
1010
]
1111
node [

0 commit comments

Comments
 (0)