visualize_packwerk 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +32 -0
- data/lib/visualize_packwerk/graph_interface.rb +17 -0
- data/lib/visualize_packwerk/node_interface.rb +29 -0
- data/lib/visualize_packwerk/package_graph.rb +48 -0
- data/lib/visualize_packwerk/package_node.rb +28 -0
- data/lib/visualize_packwerk/package_relationships.rb +158 -0
- data/lib/visualize_packwerk/railtie.rb +15 -0
- data/lib/visualize_packwerk/tasks/visualize_packwerk.rake +73 -0
- data/lib/visualize_packwerk/team_graph.rb +52 -0
- data/lib/visualize_packwerk/team_node.rb +27 -0
- data/lib/visualize_packwerk.rb +17 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/gems/activesupport@7.0.3.1.rbi +76 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +522 -0
- data/sorbet/rbi/gems/code_ownership@1.28.0.rbi +411 -0
- data/sorbet/rbi/gems/code_teams@1.0.0.rbi +138 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +8 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +8 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +866 -0
- data/sorbet/rbi/gems/i18n@1.12.0.rbi +8 -0
- data/sorbet/rbi/gems/json@2.6.2.rbi +1423 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +8 -0
- data/sorbet/rbi/gems/minitest@5.16.2.rbi +9 -0
- data/sorbet/rbi/gems/package_protections@1.3.0.rbi +654 -0
- data/sorbet/rbi/gems/parallel@1.22.1.rbi +163 -0
- data/sorbet/rbi/gems/parse_packwerk@0.11.0.rbi +148 -0
- data/sorbet/rbi/gems/parser@3.1.2.0.rbi +4261 -0
- data/sorbet/rbi/gems/pry@0.14.1.rbi +8 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +8 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +1854 -0
- data/sorbet/rbi/gems/rbi@0.0.15.rbi +2340 -0
- data/sorbet/rbi/gems/regexp_parser@2.5.0.rbi +8 -0
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +8 -0
- data/sorbet/rbi/gems/rspec-core@3.11.0.rbi +7698 -0
- data/sorbet/rbi/gems/rspec-expectations@3.11.0.rbi +6201 -0
- data/sorbet/rbi/gems/rspec-mocks@3.11.1.rbi +3625 -0
- data/sorbet/rbi/gems/rspec-support@3.11.0.rbi +1176 -0
- data/sorbet/rbi/gems/rspec@3.11.0.rbi +40 -0
- data/sorbet/rbi/gems/rubocop-ast@1.19.1.rbi +8 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +8 -0
- data/sorbet/rbi/gems/rubocop@1.33.0.rbi +8 -0
- data/sorbet/rbi/gems/ruby-graphviz@1.2.5.rbi +840 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +8 -0
- data/sorbet/rbi/gems/spoom@1.1.11.rbi +1600 -0
- data/sorbet/rbi/gems/tapioca@0.8.3.rbi +1978 -0
- data/sorbet/rbi/gems/thor@1.2.1.rbi +2921 -0
- data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +8 -0
- data/sorbet/rbi/gems/unicode-display_width@2.2.0.rbi +8 -0
- data/sorbet/rbi/gems/unparser@0.6.5.rbi +8 -0
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +1802 -0
- data/sorbet/rbi/gems/yard-sorbet@0.6.1.rbi +288 -0
- data/sorbet/rbi/gems/yard@0.9.28.rbi +12863 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +7 -0
- metadata +241 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e70be5b42437ab3ffacbbff1a3ec57c1116b8bc5fd1e4fc91163631414a35dae
|
4
|
+
data.tar.gz: 7cc7f753055f8f655d0787f3452f3b600eef10182689d637ac2a49eaf96706ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fe981d2228c1b9039020e5b715cb578b3c5a5a8f6bbade61513151d1a770d06eb327463563b96468e00e0359e7aa6fcadf991d10f03ddec1e923396266c008c0
|
7
|
+
data.tar.gz: 8de59866b806300164ccd476d9e481b7aba1ff328d746c246aec49fe3d65c353511dc2db8713807787b1e991c6fcf8040281fee7ff03766c821111950373b1c2
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# visualize_packwerk
|
2
|
+
|
3
|
+
This pack contains rake tasks to help visualize relationships between packwerk packs.
|
4
|
+
|
5
|
+
# Usage
|
6
|
+
## Building a package graph for a selection of packages (owned by 5 teams max)
|
7
|
+
```
|
8
|
+
bin/rails visualize_packwerk:package_relationships['packs/pack1','packs/pack2']
|
9
|
+
```
|
10
|
+
|
11
|
+
# Building a package graph for specific teams (5 teams max)
|
12
|
+
```
|
13
|
+
bin/rails visualize_packwerk:package_relationships_for_teams['Team1','Team2']
|
14
|
+
```
|
15
|
+
|
16
|
+
# Building a package graph for all packages (this is slow and produces a huge file)
|
17
|
+
```
|
18
|
+
bin/rails visualize_packwerk:package_relationships
|
19
|
+
```
|
20
|
+
|
21
|
+
# Building a TEAM graph for specific teams
|
22
|
+
```
|
23
|
+
bin/rails visualize_packwerk:team_relationships['Team1','Team2']
|
24
|
+
```
|
25
|
+
|
26
|
+
# Building a TEAM graph for all teams (this is slow and produces a huge file)
|
27
|
+
```
|
28
|
+
bin/rails visualize_packwerk:team_relationships
|
29
|
+
```
|
30
|
+
|
31
|
+
# Want to change something or add a feature?
|
32
|
+
Submit a PR or post an issue!
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
# This stores graphviz-independent views of our package graph.
|
5
|
+
# It should be optimized for fast lookup (leveraging internal indexes, which are stable due to the immutability of the package nodes)
|
6
|
+
# A `TeamGraph` should be able to consume this and basically just create a reduced version
|
7
|
+
# Lastly, each one should implement a common interface, and graphviz should use that interface and take in either types of graph via the interface
|
8
|
+
module GraphInterface
|
9
|
+
extend T::Sig
|
10
|
+
extend T::Helpers
|
11
|
+
interface!
|
12
|
+
|
13
|
+
sig { abstract.returns(T::Set[NodeInterface]) }
|
14
|
+
def nodes
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
module NodeInterface
|
5
|
+
extend T::Sig
|
6
|
+
extend T::Helpers
|
7
|
+
interface!
|
8
|
+
|
9
|
+
sig { abstract.returns(String) }
|
10
|
+
def name
|
11
|
+
end
|
12
|
+
|
13
|
+
sig { abstract.returns(String) }
|
14
|
+
def group_name
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { abstract.returns(T::Hash[String, Integer]) }
|
18
|
+
def violations_by_node_name
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { abstract.returns(T::Array[String]) }
|
22
|
+
def dependencies
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { abstract.params(node_name: String).returns(T::Boolean) }
|
26
|
+
def depends_on?(node_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
class PackageGraph
|
5
|
+
extend T::Sig
|
6
|
+
include GraphInterface
|
7
|
+
|
8
|
+
sig { returns(T::Set[PackageNode]) }
|
9
|
+
attr_reader :package_nodes
|
10
|
+
|
11
|
+
sig { override.returns(T::Set[NodeInterface]) }
|
12
|
+
def nodes
|
13
|
+
package_nodes
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { params(package_nodes: T::Set[PackageNode]).void }
|
17
|
+
def initialize(package_nodes:)
|
18
|
+
@package_nodes = package_nodes
|
19
|
+
@index_by_name = T.let({}, T::Hash[String, PackageNode])
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { returns(PackageGraph) }
|
23
|
+
def self.construct
|
24
|
+
package_nodes = Set.new
|
25
|
+
ParsePackwerk.all.each do |p|
|
26
|
+
# We could consider ignoring the root!
|
27
|
+
# We would also need to ignore it when parsing PackageNodes.
|
28
|
+
# next if p.name == ParsePackwerk::ROOT_PACKAGE_NAME
|
29
|
+
owner = CodeOwnership.for_package(p)
|
30
|
+
violations_by_package = PackageProtections::ProtectedPackage.from(p).violations.group_by(&:to_package_name).transform_values(&:count)
|
31
|
+
|
32
|
+
package_nodes << PackageNode.new(
|
33
|
+
name: p.name,
|
34
|
+
team_name: owner&.name || 'Unknown',
|
35
|
+
violations_by_package: violations_by_package,
|
36
|
+
dependencies: Set.new(p.dependencies)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
PackageGraph.new(package_nodes: package_nodes)
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { params(name: String).returns(PackageNode) }
|
44
|
+
def package_by_name(name)
|
45
|
+
@index_by_name[name] ||= T.must(package_nodes.find { |node| node.name == name })
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
class PackageNode < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
include NodeInterface
|
7
|
+
|
8
|
+
const :name, String
|
9
|
+
const :team_name, String
|
10
|
+
const :violations_by_package, T::Hash[String, Integer]
|
11
|
+
const :dependencies, T::Set[String]
|
12
|
+
|
13
|
+
sig { override.returns(T::Hash[String, Integer]) }
|
14
|
+
def violations_by_node_name
|
15
|
+
violations_by_package
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.returns(String) }
|
19
|
+
def group_name
|
20
|
+
team_name
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { override.params(node_name: String).returns(T::Boolean) }
|
24
|
+
def depends_on?(node_name)
|
25
|
+
dependencies.include?(node_name) || (violations_by_package[node_name] || 0) > 0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
class PackageRelationships
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
OUTPUT_FILENAME = T.let('packwerk.png'.freeze, String)
|
8
|
+
|
9
|
+
sig { void }
|
10
|
+
def initialize
|
11
|
+
@colors_by_team = T.let({}, T::Hash[String, String])
|
12
|
+
@remaining_colors = T.let(
|
13
|
+
[
|
14
|
+
# Found using https://htmlcolorcodes.com/color-picker/
|
15
|
+
'#77EE77', # green
|
16
|
+
'#DFEE77', # yellow
|
17
|
+
'#77EEE6', # teal
|
18
|
+
'#EEC077', # orange
|
19
|
+
'#EE77BF', # pink
|
20
|
+
'#EE6F6F', # red
|
21
|
+
'#ED6EDE', # magenta
|
22
|
+
'#8E8CFE', # blue
|
23
|
+
'#EEA877', # red-orange
|
24
|
+
], T::Array[String]
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
sig { params(teams: T::Array[CodeTeams::Team]).void }
|
29
|
+
def create_package_graph_for_teams!(teams)
|
30
|
+
packages = ParsePackwerk.all.select do |package|
|
31
|
+
teams.map(&:name).include?(CodeOwnership.for_package(package)&.name)
|
32
|
+
end
|
33
|
+
|
34
|
+
create_package_graph!(packages)
|
35
|
+
end
|
36
|
+
|
37
|
+
sig { params(teams: T::Array[CodeTeams::Team], show_all_teams: T::Boolean).void }
|
38
|
+
def create_team_graph!(teams, show_all_teams: false)
|
39
|
+
package_graph = PackageGraph.construct
|
40
|
+
team_graph = TeamGraph.from_package_graph(package_graph)
|
41
|
+
highlighted_node_names = teams.map(&:name)
|
42
|
+
|
43
|
+
draw_graph!(team_graph, highlighted_node_names, show_all_nodes: show_all_teams)
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { params(packages: T::Array[ParsePackwerk::Package], show_all_packs: T::Boolean).void }
|
47
|
+
def create_package_graph!(packages, show_all_packs: false)
|
48
|
+
graph = PackageGraph.construct
|
49
|
+
highlighted_node_names = packages.map(&:name)
|
50
|
+
draw_graph!(graph, highlighted_node_names, show_all_nodes: show_all_packs)
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { params(packages: T::Array[ParsePackwerk::Package], show_all_nodes: T::Boolean).void }
|
54
|
+
def create_graph!(packages, show_all_nodes: false)
|
55
|
+
graph = PackageGraph.construct
|
56
|
+
highlighted_node_names = packages.map(&:name)
|
57
|
+
draw_graph!(graph, highlighted_node_names, show_all_nodes: show_all_nodes)
|
58
|
+
end
|
59
|
+
|
60
|
+
sig { params(graph: GraphInterface, highlighted_node_names: T::Array[String], show_all_nodes: T::Boolean).void }
|
61
|
+
def draw_graph!(graph, highlighted_node_names, show_all_nodes: false)
|
62
|
+
# SFDP looks better than dot in some cases, but less good in other cases.
|
63
|
+
# If your visualization looks bad, change the layout to other_layout!
|
64
|
+
# https://graphviz.org/docs/layouts/
|
65
|
+
default_layout = :dot
|
66
|
+
# other_layout = :sfdp
|
67
|
+
graphviz_graph = GraphViz.new(:G, type: :digraph, dpi: 100, layout: default_layout)
|
68
|
+
|
69
|
+
# Create graph nodes
|
70
|
+
graphviz_nodes = T.let({}, T::Hash[String, GraphViz::Node])
|
71
|
+
|
72
|
+
nodes_to_draw = graph.nodes
|
73
|
+
|
74
|
+
nodes_to_draw.each do |node|
|
75
|
+
next unless highlighted_node_names.any? { |highlighted_node_name| node.depends_on?(highlighted_node_name) } || highlighted_node_names.include?(node.name)
|
76
|
+
|
77
|
+
highlight_node = highlighted_node_names.include?(node.name) && !show_all_nodes
|
78
|
+
graphviz_nodes[node.name] = add_node(node, graphviz_graph, highlight_node)
|
79
|
+
end
|
80
|
+
|
81
|
+
max_edge_width = 10
|
82
|
+
|
83
|
+
# Draw all edges
|
84
|
+
nodes_to_draw.each do |node|
|
85
|
+
node.dependencies.each do |to_node|
|
86
|
+
next unless highlighted_node_names.include?(to_node)
|
87
|
+
|
88
|
+
graphviz_graph.add_edges(
|
89
|
+
graphviz_nodes[node.name],
|
90
|
+
graphviz_nodes[to_node],
|
91
|
+
{ color: 'darkgreen' }
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
node.violations_by_node_name.each do |to_node_name, violation_count|
|
96
|
+
next unless highlighted_node_names.include?(to_node_name)
|
97
|
+
|
98
|
+
edge_width = [
|
99
|
+
[(violation_count / 5).to_i, 1].max, # rubocop:disable Lint/NumberConversion
|
100
|
+
max_edge_width,
|
101
|
+
].min
|
102
|
+
|
103
|
+
graphviz_graph.add_edges(
|
104
|
+
graphviz_nodes[node.name],
|
105
|
+
graphviz_nodes[to_node_name],
|
106
|
+
{ color: 'red', penwidth: edge_width }
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Save graph to filesystem
|
112
|
+
puts "Outputting to: #{OUTPUT_FILENAME}"
|
113
|
+
graphviz_graph.output(png: OUTPUT_FILENAME)
|
114
|
+
puts 'Finished!'
|
115
|
+
end
|
116
|
+
|
117
|
+
sig { params(node: NodeInterface, graph: GraphViz, highlight_node: T::Boolean).returns(GraphViz::Node) }
|
118
|
+
def add_node(node, graph, highlight_node)
|
119
|
+
default_node_options = {
|
120
|
+
fontsize: 26.0,
|
121
|
+
fontcolor: 'white',
|
122
|
+
fillcolor: 'black',
|
123
|
+
color: 'black',
|
124
|
+
height: 1.0,
|
125
|
+
style: 'filled, rounded',
|
126
|
+
shape: 'box',
|
127
|
+
}
|
128
|
+
|
129
|
+
node_options = if highlight_node
|
130
|
+
default_node_options.merge(
|
131
|
+
fillcolor: highlight_by_group(node),
|
132
|
+
color: highlight_by_group(node),
|
133
|
+
fontcolor: 'black'
|
134
|
+
)
|
135
|
+
else
|
136
|
+
default_node_options
|
137
|
+
end
|
138
|
+
|
139
|
+
graph.add_nodes(node.name, **node_options)
|
140
|
+
end
|
141
|
+
|
142
|
+
sig { params(node: NodeInterface).returns(String) }
|
143
|
+
def highlight_by_group(node)
|
144
|
+
highlighted_package_color = @colors_by_team[node.group_name]
|
145
|
+
if !highlighted_package_color
|
146
|
+
highlighted_package_color = @remaining_colors.first
|
147
|
+
raise 'Can only color nodes a max of 5 unique colors for now' if highlighted_package_color.nil?
|
148
|
+
|
149
|
+
@remaining_colors.delete(highlighted_package_color)
|
150
|
+
@colors_by_team[node.group_name] = highlighted_package_color
|
151
|
+
end
|
152
|
+
|
153
|
+
highlighted_package_color
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
private_constant :PackageRelationships
|
158
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# typed: ignore
|
2
|
+
|
3
|
+
require 'visualize_packwerk'
|
4
|
+
require 'rails'
|
5
|
+
|
6
|
+
module VisualizePackwerk
|
7
|
+
class Railtie < Rails::Railtie
|
8
|
+
railtie_name :visualize_packwerk
|
9
|
+
|
10
|
+
rake_tasks do
|
11
|
+
path = File.expand_path(__dir__)
|
12
|
+
Dir.glob("#{path}/tasks/visualize_packwerk.rake").each { |f| load f }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
class TaskLoader
|
5
|
+
include Rake::DSL
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { void }
|
9
|
+
def create_tasks!
|
10
|
+
namespace(:visualize_packwerk) do
|
11
|
+
# This creates the array of symbols that are needed to declare an argument to a rake task
|
12
|
+
package_args = (1..100).map { |i| "package#{i}".to_sym }
|
13
|
+
|
14
|
+
desc('Graph packages')
|
15
|
+
task(:package_relationships, package_args => :environment) do |_task, args|
|
16
|
+
show_all_packs = args.to_hash.values.none?
|
17
|
+
packages = if show_all_packs
|
18
|
+
ParsePackwerk.all
|
19
|
+
else
|
20
|
+
args.to_hash.values.map do |pack_name|
|
21
|
+
found_package = ParsePackwerk.all.find { |p| p.name == pack_name }
|
22
|
+
if found_package.nil?
|
23
|
+
abort "Could not find pack with name: #{pack_name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
found_package
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
PackageRelationships.new.create_package_graph!(packages, show_all_packs: show_all_packs)
|
31
|
+
end
|
32
|
+
|
33
|
+
# This creates the array of symbols that are needed to declare an argument to a rake task
|
34
|
+
team_args = (1..5).map { |i| "team#{i}".to_sym }
|
35
|
+
|
36
|
+
desc('Graph packages for teams')
|
37
|
+
task(:package_relationships_for_teams, team_args => :environment) do |_task, args|
|
38
|
+
teams = args.to_hash.values.map do |team_name|
|
39
|
+
team = CodeTeams.find(team_name)
|
40
|
+
if team.nil?
|
41
|
+
abort("Could not find team with name: #{team_name}. Check your config/teams/subdirectory/team.yml for correct team spelling, e.g. `Product Infrastructure`")
|
42
|
+
end
|
43
|
+
|
44
|
+
team
|
45
|
+
end
|
46
|
+
|
47
|
+
PackageRelationships.new.create_package_graph_for_teams!(teams)
|
48
|
+
end
|
49
|
+
|
50
|
+
desc('Graph team relationships')
|
51
|
+
task(:team_relationships, team_args => :environment) do |_task, args|
|
52
|
+
show_all_teams = args.to_hash.values.none?
|
53
|
+
teams = if show_all_teams
|
54
|
+
CodeTeams.all
|
55
|
+
else
|
56
|
+
args.to_hash.values.map do |team_name|
|
57
|
+
team = CodeTeams.find(team_name)
|
58
|
+
if team.nil?
|
59
|
+
abort("Could not find team with name: #{team_name}. Check your config/teams/subdirectory/team.yml for correct team spelling, e.g. `Product Infrastructure`")
|
60
|
+
end
|
61
|
+
|
62
|
+
team
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
PackageRelationships.new.create_team_graph!(teams, show_all_teams: show_all_teams)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
VisualizePackwerk::TaskLoader.new.create_tasks!
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
#
|
5
|
+
# A team graph reduces a PackageGraph by aggregating over packages owned by teams
|
6
|
+
#
|
7
|
+
class TeamGraph
|
8
|
+
extend T::Sig
|
9
|
+
include GraphInterface
|
10
|
+
|
11
|
+
sig { override.returns(T::Set[NodeInterface]) }
|
12
|
+
def nodes
|
13
|
+
@team_nodes
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { params(team_nodes: T::Set[TeamNode]).void }
|
17
|
+
def initialize(team_nodes:)
|
18
|
+
@team_nodes = team_nodes
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(package_graph: PackageGraph).returns(TeamGraph) }
|
22
|
+
def self.from_package_graph(package_graph)
|
23
|
+
team_nodes = T.let(Set.new, T::Set[TeamNode])
|
24
|
+
package_graph.package_nodes.group_by(&:team_name).each do |team, package_nodes_for_team|
|
25
|
+
violations_by_team = {}
|
26
|
+
package_nodes_for_team.map(&:violations_by_package).each do |new_violations_by_package|
|
27
|
+
new_violations_by_package.each do |pack_name, count|
|
28
|
+
# We first get the pack owner of the violated package
|
29
|
+
other_team = package_graph.package_by_name(pack_name).team_name
|
30
|
+
violations_by_team[other_team] ||= 0
|
31
|
+
# Then we add the violations on that team together
|
32
|
+
# TODO: We may want to ignore this if team == other_team to avoid arrows pointing to self, but maybe not!
|
33
|
+
violations_by_team[other_team] += count
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
dependencies = Set.new
|
38
|
+
package_nodes_for_team.map(&:dependencies).reduce(Set.new, :+).each do |dependency|
|
39
|
+
dependencies << package_graph.package_by_name(dependency).team_name
|
40
|
+
end
|
41
|
+
|
42
|
+
team_nodes << TeamNode.new(
|
43
|
+
name: team,
|
44
|
+
violations_by_team: violations_by_team,
|
45
|
+
dependencies: dependencies
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
TeamGraph.new(team_nodes: team_nodes)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
class TeamNode < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
include NodeInterface
|
7
|
+
|
8
|
+
const :name, String
|
9
|
+
const :violations_by_team, T::Hash[String, Integer]
|
10
|
+
const :dependencies, T::Set[String]
|
11
|
+
|
12
|
+
sig { override.returns(T::Hash[String, Integer]) }
|
13
|
+
def violations_by_node_name
|
14
|
+
violations_by_team
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { override.returns(String) }
|
18
|
+
def group_name
|
19
|
+
name
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { override.params(node_name: String).returns(T::Boolean) }
|
23
|
+
def depends_on?(node_name)
|
24
|
+
dependencies.include?(node_name) || (violations_by_node_name[node_name] || 0) > 0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePackwerk
|
4
|
+
require 'visualize_packwerk/railtie' if defined?(Rails)
|
5
|
+
require 'parse_packwerk'
|
6
|
+
require 'code_ownership'
|
7
|
+
require 'package_protections'
|
8
|
+
require 'graphviz'
|
9
|
+
|
10
|
+
require 'visualize_packwerk/node_interface'
|
11
|
+
require 'visualize_packwerk/graph_interface'
|
12
|
+
require 'visualize_packwerk/team_node'
|
13
|
+
require 'visualize_packwerk/package_node'
|
14
|
+
require 'visualize_packwerk/team_graph'
|
15
|
+
require 'visualize_packwerk/package_graph'
|
16
|
+
require 'visualize_packwerk/package_relationships'
|
17
|
+
end
|
data/sorbet/config
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# typed: false
|
2
|
+
|
3
|
+
# DO NOT EDIT MANUALLY
|
4
|
+
# This is an autogenerated file for types exported from the `activesupport` gem.
|
5
|
+
# Please instead update this file by running `bin/tapioca gem activesupport`.
|
6
|
+
|
7
|
+
class FalseClass
|
8
|
+
include ::JSON::Ext::Generator::GeneratorMethods::FalseClass
|
9
|
+
end
|
10
|
+
|
11
|
+
class Float < ::Numeric
|
12
|
+
include ::JSON::Ext::Generator::GeneratorMethods::Float
|
13
|
+
end
|
14
|
+
|
15
|
+
class IO
|
16
|
+
include ::Enumerable
|
17
|
+
include ::File::Constants
|
18
|
+
end
|
19
|
+
|
20
|
+
class IO::ConsoleMode
|
21
|
+
def echo=(_arg0); end
|
22
|
+
def raw(*_arg0); end
|
23
|
+
def raw!(*_arg0); end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def initialize_copy(_arg0); end
|
28
|
+
end
|
29
|
+
|
30
|
+
class IO::EAGAINWaitReadable < ::Errno::EAGAIN
|
31
|
+
include ::IO::WaitReadable
|
32
|
+
end
|
33
|
+
|
34
|
+
class IO::EAGAINWaitWritable < ::Errno::EAGAIN
|
35
|
+
include ::IO::WaitWritable
|
36
|
+
end
|
37
|
+
|
38
|
+
class IO::EINPROGRESSWaitReadable < ::Errno::EINPROGRESS
|
39
|
+
include ::IO::WaitReadable
|
40
|
+
end
|
41
|
+
|
42
|
+
class IO::EINPROGRESSWaitWritable < ::Errno::EINPROGRESS
|
43
|
+
include ::IO::WaitWritable
|
44
|
+
end
|
45
|
+
|
46
|
+
IO::EWOULDBLOCKWaitReadable = IO::EAGAINWaitReadable
|
47
|
+
IO::EWOULDBLOCKWaitWritable = IO::EAGAINWaitWritable
|
48
|
+
|
49
|
+
class Integer < ::Numeric
|
50
|
+
include ::JSON::Ext::Generator::GeneratorMethods::Integer
|
51
|
+
end
|
52
|
+
|
53
|
+
class NameError < ::StandardError
|
54
|
+
include ::DidYouMean::Correctable
|
55
|
+
end
|
56
|
+
|
57
|
+
class NilClass
|
58
|
+
include ::JSON::Ext::Generator::GeneratorMethods::NilClass
|
59
|
+
end
|
60
|
+
|
61
|
+
class Numeric
|
62
|
+
include ::Comparable
|
63
|
+
end
|
64
|
+
|
65
|
+
class Symbol
|
66
|
+
include ::Comparable
|
67
|
+
end
|
68
|
+
|
69
|
+
class TrueClass
|
70
|
+
include ::JSON::Ext::Generator::GeneratorMethods::TrueClass
|
71
|
+
end
|
72
|
+
|
73
|
+
class URI::Generic
|
74
|
+
include ::URI::RFC2396_REGEXP
|
75
|
+
include ::URI
|
76
|
+
end
|