modularization_statistics 1.34.0 → 1.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/modularization_statistics/private/datadog_reporter.rb +14 -278
- data/lib/modularization_statistics/private/metrics/files.rb +47 -0
- data/lib/modularization_statistics/private/metrics/nested_packs.rb +137 -0
- data/lib/modularization_statistics/private/metrics/packages.rb +110 -0
- data/lib/modularization_statistics/private/metrics/packages_by_team.rb +73 -0
- data/lib/modularization_statistics/private/metrics/protection_usage.rb +33 -0
- data/lib/modularization_statistics/private/metrics/public_usage.rb +36 -0
- data/lib/modularization_statistics/private/metrics/rubocop_protections_exclusions.rb +34 -0
- data/lib/modularization_statistics/private/metrics.rb +49 -0
- data/sorbet/config +1 -0
- data/sorbet/rbi/gems/{package_protections@0.64.0.rbi → package_protections@1.4.0.rbi} +56 -107
- data/sorbet/rbi/gems/{parse_packwerk@0.10.0.rbi → parse_packwerk@0.12.0.rbi} +6 -0
- metadata +12 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 252db0bdd5ec50e555d34ce4c7468df4a5bf731274fafd137a319eaf1a1100b9
|
4
|
+
data.tar.gz: 8983a207179da6a93b34464f98c3b478a23087b34946f63d4c1cf95992d10f9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cebc018b3841416b6194e3cef53779f3529f2d56463bbd8c6d79a6fb322f9c1c1a81d0a357fb8140cd48853abe6d5d1de160e707414b2d71eccbdbbb36dc9993
|
7
|
+
data.tar.gz: d837d652fed0baca80bfc6e1613d1ab75e1ac5b5b7d798c7df544a53e3c27c5c20ab82d8d39891aa29034541086d0e6a6b3e806d22b5b575c836951a1f96cac7
|
@@ -2,14 +2,20 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'dogapi'
|
5
|
+
require 'modularization_statistics/private/metrics'
|
6
|
+
require 'modularization_statistics/private/metrics/files'
|
7
|
+
require 'modularization_statistics/private/metrics/public_usage'
|
8
|
+
require 'modularization_statistics/private/metrics/protection_usage'
|
9
|
+
require 'modularization_statistics/private/metrics/rubocop_protections_exclusions'
|
10
|
+
require 'modularization_statistics/private/metrics/packages'
|
11
|
+
require 'modularization_statistics/private/metrics/packages_by_team'
|
12
|
+
require 'modularization_statistics/private/metrics/nested_packs'
|
5
13
|
|
6
14
|
module ModularizationStatistics
|
7
15
|
module Private
|
8
16
|
class DatadogReporter
|
9
17
|
extend T::Sig
|
10
18
|
|
11
|
-
UNKNOWN_OWNER = T.let('Unknown', String)
|
12
|
-
|
13
19
|
sig do
|
14
20
|
params(
|
15
21
|
source_code_files: T::Array[SourceCodeFile],
|
@@ -17,22 +23,14 @@ module ModularizationStatistics
|
|
17
23
|
).returns(T::Array[GaugeMetric])
|
18
24
|
end
|
19
25
|
def self.get_metrics(source_code_files:, app_name:)
|
20
|
-
all_metrics = T.let([], T::Array[GaugeMetric])
|
21
|
-
app_level_tag = Tag.for('app', app_name)
|
22
|
-
|
23
|
-
source_code_files.group_by { |file| file.team_owner&.name }.each do |team_name, files_for_team|
|
24
|
-
file_tags = tags_for_team(team_name) + [app_level_tag]
|
25
|
-
all_metrics += get_file_metrics('by_team', file_tags, files_for_team)
|
26
|
-
end
|
27
|
-
|
28
|
-
file_tags = [app_level_tag]
|
29
|
-
all_metrics += get_file_metrics('totals', file_tags, source_code_files)
|
30
|
-
|
31
26
|
packages = ParsePackwerk.all
|
32
|
-
all_metrics += get_package_metrics(packages, app_name)
|
33
|
-
all_metrics += get_package_metrics_by_team(packages, app_name)
|
34
27
|
|
35
|
-
|
28
|
+
[
|
29
|
+
*Metrics::Files.get_metrics(source_code_files, app_name),
|
30
|
+
*Metrics::Packages.get_package_metrics(packages, app_name),
|
31
|
+
*Metrics::PackagesByTeam.get_package_metrics_by_team(packages, app_name),
|
32
|
+
*Metrics::NestedPacks.get_nested_package_metrics(packages, app_name)
|
33
|
+
]
|
36
34
|
end
|
37
35
|
|
38
36
|
sig do
|
@@ -58,268 +56,6 @@ module ModularizationStatistics
|
|
58
56
|
end
|
59
57
|
end
|
60
58
|
end
|
61
|
-
|
62
|
-
sig { params(package: ParsePackwerk::Package, app_name: String).returns(T::Array[Tag]) }
|
63
|
-
def self.tags_for_package(package, app_name)
|
64
|
-
[
|
65
|
-
Tag.new(key: 'package', value: humanized_package_name(package.name)),
|
66
|
-
Tag.new(key: 'app', value: app_name),
|
67
|
-
*tags_for_team(CodeOwnership.for_package(package)&.name),
|
68
|
-
]
|
69
|
-
end
|
70
|
-
|
71
|
-
sig { params(team_name: T.nilable(String)).returns(T::Array[Tag]) }
|
72
|
-
def self.tags_for_team(team_name)
|
73
|
-
[Tag.for('team', team_name || UNKNOWN_OWNER)]
|
74
|
-
end
|
75
|
-
|
76
|
-
sig { params(team_name: T.nilable(String)).returns(T::Array[Tag]) }
|
77
|
-
def self.tags_for_to_team(team_name)
|
78
|
-
[Tag.for('to_team', team_name || UNKNOWN_OWNER)]
|
79
|
-
end
|
80
|
-
|
81
|
-
private_class_method :tags_for_package
|
82
|
-
|
83
|
-
sig do
|
84
|
-
params(
|
85
|
-
packages: T::Array[ParsePackwerk::Package],
|
86
|
-
app_name: String
|
87
|
-
).returns(T::Array[GaugeMetric])
|
88
|
-
end
|
89
|
-
def self.get_package_metrics(packages, app_name)
|
90
|
-
all_metrics = []
|
91
|
-
app_level_tag = Tag.for('app', app_name)
|
92
|
-
package_tags = T.let([app_level_tag], T::Array[Tag])
|
93
|
-
protected_packages = packages.map { |p| PackageProtections::ProtectedPackage.from(p) }
|
94
|
-
|
95
|
-
all_metrics << GaugeMetric.for('all_packages.count', packages.count, package_tags)
|
96
|
-
all_metrics << GaugeMetric.for('all_packages.dependencies.count', packages.sum { |package| package.dependencies.count }, package_tags)
|
97
|
-
all_metrics << GaugeMetric.for('all_packages.dependency_violations.count', protected_packages.sum { |package| file_count(package.violations.select(&:dependency?)) }, package_tags)
|
98
|
-
all_metrics << GaugeMetric.for('all_packages.privacy_violations.count', protected_packages.sum { |package| file_count(package.violations.select(&:privacy?)) }, package_tags)
|
99
|
-
all_metrics << GaugeMetric.for('all_packages.enforcing_dependencies.count', packages.count(&:enforces_dependencies?), package_tags)
|
100
|
-
all_metrics << GaugeMetric.for('all_packages.enforcing_privacy.count', packages.count(&:enforces_privacy?), package_tags)
|
101
|
-
|
102
|
-
all_metrics << GaugeMetric.for('all_packages.notify_on_package_yml_changes.count', packages.count { |p| p.metadata['notify_on_package_yml_changes'] }, package_tags)
|
103
|
-
all_metrics << GaugeMetric.for('all_packages.notify_on_new_violations.count', packages.count { |p| p.metadata['notify_on_new_violations'] }, package_tags)
|
104
|
-
|
105
|
-
all_metrics << GaugeMetric.for('all_packages.with_violations.count', protected_packages.count { |package| package.violations.any? }, package_tags)
|
106
|
-
all_metrics += self.get_public_usage_metrics('all_packages', packages, package_tags)
|
107
|
-
all_metrics << GaugeMetric.for('all_packages.has_readme.count', packages.count { |package| has_readme?(package) }, package_tags)
|
108
|
-
|
109
|
-
all_metrics += self.get_protections_metrics('all_packages', protected_packages, package_tags)
|
110
|
-
all_metrics << GaugeMetric.for('all_packages.package_based_file_ownership.count', packages.count { |package| !package.metadata['owner'].nil? }, package_tags)
|
111
|
-
|
112
|
-
inbound_violations_by_package = protected_packages.flat_map(&:violations).group_by(&:to_package_name)
|
113
|
-
|
114
|
-
protected_packages.each do |protected_package|
|
115
|
-
package = protected_package.original_package
|
116
|
-
package_tags = tags_for_package(package, app_name)
|
117
|
-
|
118
|
-
#
|
119
|
-
# VIOLATIONS (implicit dependencies)
|
120
|
-
#
|
121
|
-
outbound_violations = protected_package.violations
|
122
|
-
inbound_violations = inbound_violations_by_package[package.name] || []
|
123
|
-
all_dependency_violations = (outbound_violations + inbound_violations).select(&:dependency?)
|
124
|
-
all_privacy_violations = (outbound_violations + inbound_violations).select(&:privacy?)
|
125
|
-
|
126
|
-
all_metrics << GaugeMetric.for('by_package.dependency_violations.count', file_count(all_dependency_violations), package_tags)
|
127
|
-
all_metrics << GaugeMetric.for('by_package.privacy_violations.count', file_count(all_privacy_violations), package_tags)
|
128
|
-
|
129
|
-
all_metrics << GaugeMetric.for('by_package.outbound_dependency_violations.count', file_count(outbound_violations.select(&:dependency?)), package_tags)
|
130
|
-
all_metrics << GaugeMetric.for('by_package.inbound_dependency_violations.count', file_count(inbound_violations.select(&:dependency?)), package_tags)
|
131
|
-
|
132
|
-
all_metrics << GaugeMetric.for('by_package.outbound_privacy_violations.count', file_count(outbound_violations.select(&:privacy?)), package_tags)
|
133
|
-
all_metrics << GaugeMetric.for('by_package.inbound_privacy_violations.count', file_count(inbound_violations.select(&:privacy?)), package_tags)
|
134
|
-
|
135
|
-
all_metrics += self.get_public_usage_metrics('by_package', [package], package_tags)
|
136
|
-
|
137
|
-
protected_package.violations.group_by(&:to_package_name).each do |to_package_name, violations|
|
138
|
-
to_package = ParsePackwerk.find(to_package_name)
|
139
|
-
if to_package.nil?
|
140
|
-
raise StandardError, "Could not find matching package #{to_package_name}"
|
141
|
-
end
|
142
|
-
|
143
|
-
tags = package_tags + [Tag.for('to_package', humanized_package_name(to_package_name))] + tags_for_to_team(CodeOwnership.for_package(to_package)&.name)
|
144
|
-
all_metrics << GaugeMetric.for('by_package.outbound_dependency_violations.per_package.count', file_count(violations.select(&:dependency?)), tags)
|
145
|
-
all_metrics << GaugeMetric.for('by_package.outbound_privacy_violations.per_package.count', file_count(violations.select(&:privacy?)), tags)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
inbound_explicit_dependency_by_package = {}
|
150
|
-
packages.each do |package|
|
151
|
-
package.dependencies.each do |explicit_dependency|
|
152
|
-
inbound_explicit_dependency_by_package[explicit_dependency] ||= []
|
153
|
-
inbound_explicit_dependency_by_package[explicit_dependency] << package.name
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
packages.each do |package| # rubocop:disable Style/CombinableLoops
|
158
|
-
package_tags = tags_for_package(package, app_name)
|
159
|
-
|
160
|
-
#
|
161
|
-
# EXPLICIT DEPENDENCIES
|
162
|
-
#
|
163
|
-
package.dependencies.each do |explicit_dependency|
|
164
|
-
to_package = ParsePackwerk.find(explicit_dependency)
|
165
|
-
if to_package.nil?
|
166
|
-
raise StandardError, "Could not find matching package #{explicit_dependency}"
|
167
|
-
end
|
168
|
-
|
169
|
-
tags = package_tags + [Tag.for('to_package', humanized_package_name(explicit_dependency))] + tags_for_to_team(CodeOwnership.for_package(to_package)&.name)
|
170
|
-
all_metrics << GaugeMetric.for('by_package.outbound_explicit_dependencies.per_package.count', 1, tags)
|
171
|
-
end
|
172
|
-
|
173
|
-
all_metrics << GaugeMetric.for('by_package.outbound_explicit_dependencies.count', package.dependencies.count, package_tags)
|
174
|
-
all_metrics << GaugeMetric.for('by_package.inbound_explicit_dependencies.count', inbound_explicit_dependency_by_package[package.name]&.count || 0, package_tags)
|
175
|
-
end
|
176
|
-
|
177
|
-
all_metrics
|
178
|
-
end
|
179
|
-
|
180
|
-
sig do
|
181
|
-
params(
|
182
|
-
all_packages: T::Array[ParsePackwerk::Package],
|
183
|
-
app_name: String
|
184
|
-
).returns(T::Array[GaugeMetric])
|
185
|
-
end
|
186
|
-
def self.get_package_metrics_by_team(all_packages, app_name)
|
187
|
-
all_metrics = T.let([], T::Array[GaugeMetric])
|
188
|
-
app_level_tag = Tag.for('app', app_name)
|
189
|
-
all_protected_packages = all_packages.map { |p| PackageProtections::ProtectedPackage.from(p) }
|
190
|
-
all_protected_packages.group_by { |protected_package| CodeOwnership.for_package(protected_package.original_package)&.name }.each do |team_name, protected_packages_by_team|
|
191
|
-
# We look at `all_packages` because we care about ALL inbound violations across all teams
|
192
|
-
inbound_violations_by_package = all_protected_packages.flat_map(&:violations).group_by(&:to_package_name)
|
193
|
-
|
194
|
-
team_tags = tags_for_team(team_name) + [app_level_tag]
|
195
|
-
all_metrics << GaugeMetric.for('by_team.all_packages.count', protected_packages_by_team.count, team_tags)
|
196
|
-
all_metrics += self.get_protections_metrics('by_team', protected_packages_by_team, team_tags)
|
197
|
-
all_metrics += self.get_public_usage_metrics('by_team', protected_packages_by_team.map(&:original_package), team_tags)
|
198
|
-
|
199
|
-
all_metrics << GaugeMetric.for('by_team.notify_on_package_yml_changes.count', protected_packages_by_team.count { |p| p.metadata['notify_on_package_yml_changes'] }, team_tags)
|
200
|
-
all_metrics << GaugeMetric.for('by_team.notify_on_new_violations.count', protected_packages_by_team.count { |p| p.metadata['notify_on_new_violations'] }, team_tags)
|
201
|
-
|
202
|
-
#
|
203
|
-
# VIOLATIONS (implicit dependencies)
|
204
|
-
#
|
205
|
-
outbound_violations = protected_packages_by_team.flat_map(&:violations)
|
206
|
-
# Here we only look at packages_by_team because we only care about inbound violations onto packages for this team
|
207
|
-
inbound_violations = protected_packages_by_team.flat_map { |package| inbound_violations_by_package[package.name] || [] }
|
208
|
-
all_dependency_violations = (outbound_violations + inbound_violations).select(&:dependency?)
|
209
|
-
all_privacy_violations = (outbound_violations + inbound_violations).select(&:privacy?)
|
210
|
-
|
211
|
-
all_metrics << GaugeMetric.for('by_team.dependency_violations.count', file_count(all_dependency_violations), team_tags)
|
212
|
-
all_metrics << GaugeMetric.for('by_team.privacy_violations.count', file_count(all_privacy_violations), team_tags)
|
213
|
-
|
214
|
-
all_metrics << GaugeMetric.for('by_team.outbound_dependency_violations.count', file_count(outbound_violations.select(&:dependency?)), team_tags)
|
215
|
-
all_metrics << GaugeMetric.for('by_team.inbound_dependency_violations.count', file_count(inbound_violations.select(&:dependency?)), team_tags)
|
216
|
-
|
217
|
-
all_metrics << GaugeMetric.for('by_team.outbound_privacy_violations.count', file_count(outbound_violations.select(&:privacy?)), team_tags)
|
218
|
-
all_metrics << GaugeMetric.for('by_team.inbound_privacy_violations.count', file_count(inbound_violations.select(&:privacy?)), team_tags)
|
219
|
-
|
220
|
-
all_metrics << GaugeMetric.for('by_team.has_readme.count', protected_packages_by_team.count { |protected_package| has_readme?(protected_package.original_package) }, team_tags)
|
221
|
-
|
222
|
-
grouped_outbound_violations = outbound_violations.group_by do |violation|
|
223
|
-
to_package = ParsePackwerk.find(violation.to_package_name)
|
224
|
-
if to_package.nil?
|
225
|
-
raise StandardError, "Could not find matching package #{violation.to_package_name}"
|
226
|
-
end
|
227
|
-
|
228
|
-
CodeOwnership.for_package(to_package)&.name
|
229
|
-
end
|
230
|
-
|
231
|
-
grouped_outbound_violations.each do |to_team_name, violations|
|
232
|
-
tags = team_tags + tags_for_to_team(to_team_name)
|
233
|
-
all_metrics << GaugeMetric.for('by_team.outbound_dependency_violations.per_team.count', file_count(violations.select(&:dependency?)), tags)
|
234
|
-
all_metrics << GaugeMetric.for('by_team.outbound_privacy_violations.per_team.count', file_count(violations.select(&:privacy?)), tags)
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
all_metrics
|
239
|
-
end
|
240
|
-
|
241
|
-
private_class_method :get_package_metrics
|
242
|
-
|
243
|
-
sig do
|
244
|
-
params(
|
245
|
-
metric_name_suffix: String,
|
246
|
-
tags: T::Array[Tag],
|
247
|
-
files: T::Array[SourceCodeFile]
|
248
|
-
).returns(T::Array[GaugeMetric])
|
249
|
-
end
|
250
|
-
def self.get_file_metrics(metric_name_suffix, tags, files)
|
251
|
-
[
|
252
|
-
GaugeMetric.for("component_files.#{metric_name_suffix}", files.count(&:componentized_file?), tags),
|
253
|
-
GaugeMetric.for("packaged_files.#{metric_name_suffix}", files.count(&:packaged_file?), tags),
|
254
|
-
GaugeMetric.for("all_files.#{metric_name_suffix}", files.count, tags),
|
255
|
-
]
|
256
|
-
end
|
257
|
-
|
258
|
-
private_class_method :get_file_metrics
|
259
|
-
|
260
|
-
sig { params(prefix: String, protected_packages: T::Array[PackageProtections::ProtectedPackage], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
|
261
|
-
def self.get_protections_metrics(prefix, protected_packages, package_tags)
|
262
|
-
PackageProtections.all.flat_map do |protection|
|
263
|
-
PackageProtections::ViolationBehavior.each_value.map do |violation_behavior|
|
264
|
-
# https://github.com/Gusto/package_protections/pull/42 changed the public API of these violation behaviors.
|
265
|
-
# To preserve our ability to understand historical trends, we map to the old values.
|
266
|
-
# This allows our dashboards to continue to operate as expected.
|
267
|
-
# Note if we ever open source mod stats, we should probably inject this behavior so that new clients can see the new keys in their metrics.
|
268
|
-
violation_behavior_map = {
|
269
|
-
PackageProtections::ViolationBehavior::FailOnAny => 'fail_the_build_on_any_instances',
|
270
|
-
PackageProtections::ViolationBehavior::FailNever => 'no',
|
271
|
-
PackageProtections::ViolationBehavior::FailOnNew => 'fail_the_build_if_new_instances_appear',
|
272
|
-
}
|
273
|
-
violation_behavior_name = violation_behavior_map[violation_behavior]
|
274
|
-
metric_name = "#{prefix}.#{protection.identifier}.#{violation_behavior_name}.count"
|
275
|
-
count_of_packages = protected_packages.count { |p| p.violation_behavior_for(protection.identifier) == violation_behavior }
|
276
|
-
GaugeMetric.for(metric_name, count_of_packages, package_tags)
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
|
282
|
-
def self.get_public_usage_metrics(prefix, packages, package_tags)
|
283
|
-
packages_except_for_root = packages.reject { |package| package.name == ParsePackwerk::ROOT_PACKAGE_NAME }
|
284
|
-
all_files = packages_except_for_root.flat_map do |package|
|
285
|
-
package.directory.glob('**/**.rb')
|
286
|
-
end
|
287
|
-
|
288
|
-
all_public_files = T.let([], T::Array[Pathname])
|
289
|
-
is_using_public_directory = 0
|
290
|
-
packages_except_for_root.each do |package|
|
291
|
-
public_files = package.directory.glob('app/public/**/**.rb')
|
292
|
-
all_public_files += public_files
|
293
|
-
is_using_public_directory += 1 if public_files.any?
|
294
|
-
end
|
295
|
-
|
296
|
-
# In Datadog, can divide public files by all files to get the ratio.
|
297
|
-
# This is not a metric that we are targeting -- its for observability and reflection only.
|
298
|
-
[
|
299
|
-
GaugeMetric.for("#{prefix}.all_files.count", all_files.count, package_tags),
|
300
|
-
GaugeMetric.for("#{prefix}.public_files.count", all_public_files.count, package_tags),
|
301
|
-
GaugeMetric.for("#{prefix}.using_public_directory.count", is_using_public_directory, package_tags),
|
302
|
-
]
|
303
|
-
end
|
304
|
-
|
305
|
-
sig { params(package: ParsePackwerk::Package).returns(T::Boolean) }
|
306
|
-
def self.has_readme?(package)
|
307
|
-
package.directory.join('README.md').exist?
|
308
|
-
end
|
309
|
-
|
310
|
-
sig { params(violations: T::Array[ParsePackwerk::Violation]).returns(Integer) }
|
311
|
-
def self.file_count(violations)
|
312
|
-
violations.sum { |v| v.files.count }
|
313
|
-
end
|
314
|
-
|
315
|
-
sig { params(name: String).returns(String) }
|
316
|
-
def self.humanized_package_name(name)
|
317
|
-
if name == ParsePackwerk::ROOT_PACKAGE_NAME
|
318
|
-
'root'
|
319
|
-
else
|
320
|
-
name
|
321
|
-
end
|
322
|
-
end
|
323
59
|
end
|
324
60
|
end
|
325
61
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModularizationStatistics
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
class Files
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig do
|
11
|
+
params(
|
12
|
+
source_code_files: T::Array[SourceCodeFile],
|
13
|
+
app_name: String
|
14
|
+
).returns(T::Array[GaugeMetric])
|
15
|
+
end
|
16
|
+
def self.get_metrics(source_code_files, app_name)
|
17
|
+
all_metrics = T.let([], T::Array[GaugeMetric])
|
18
|
+
app_level_tag = Tag.for('app', app_name)
|
19
|
+
|
20
|
+
source_code_files.group_by { |file| file.team_owner&.name }.each do |team_name, files_for_team|
|
21
|
+
file_tags = Metrics.tags_for_team(team_name) + [app_level_tag]
|
22
|
+
all_metrics += get_file_metrics('by_team', file_tags, files_for_team)
|
23
|
+
end
|
24
|
+
|
25
|
+
file_tags = [app_level_tag]
|
26
|
+
all_metrics += get_file_metrics('totals', file_tags, source_code_files)
|
27
|
+
all_metrics
|
28
|
+
end
|
29
|
+
|
30
|
+
sig do
|
31
|
+
params(
|
32
|
+
metric_name_suffix: String,
|
33
|
+
tags: T::Array[Tag],
|
34
|
+
files: T::Array[SourceCodeFile]
|
35
|
+
).returns(T::Array[GaugeMetric])
|
36
|
+
end
|
37
|
+
def self.get_file_metrics(metric_name_suffix, tags, files)
|
38
|
+
[
|
39
|
+
GaugeMetric.for("component_files.#{metric_name_suffix}", files.count(&:componentized_file?), tags),
|
40
|
+
GaugeMetric.for("packaged_files.#{metric_name_suffix}", files.count(&:packaged_file?), tags),
|
41
|
+
GaugeMetric.for("all_files.#{metric_name_suffix}", files.count, tags),
|
42
|
+
]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModularizationStatistics
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
class NestedPacks
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
class PackGroup < T::Struct
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
const :name, String
|
14
|
+
const :root, ParsePackwerk::Package
|
15
|
+
const :members, T::Array[ParsePackwerk::Package]
|
16
|
+
|
17
|
+
sig { params(packages: T::Array[ParsePackwerk::Package]).returns(T::Array[PackGroup]) }
|
18
|
+
def self.all_from(packages)
|
19
|
+
packs_by_group = {}
|
20
|
+
|
21
|
+
packages.each do |package|
|
22
|
+
# For a child pack, package.directory is `packs/fruits/apples` (i.e. the directory of the package.yml file).
|
23
|
+
# The package.directory.dirname is therefore `packs/fruits`.
|
24
|
+
# For a standalone pack, package.directory.dirname is `packs`
|
25
|
+
# A pack with no parent is in a pack group of its own name
|
26
|
+
root = ParsePackwerk.find(package.directory.dirname.to_s) || package
|
27
|
+
# Mark the parent pack and child pack as being in the pack group of the parent
|
28
|
+
packs_by_group[root.name] ||= { root: root, members: [] }
|
29
|
+
packs_by_group[root.name][:members] << package
|
30
|
+
end
|
31
|
+
|
32
|
+
packs_by_group.map do |name, pack_data|
|
33
|
+
PackGroup.new(
|
34
|
+
name: name,
|
35
|
+
root: pack_data[:root],
|
36
|
+
members: pack_data[:members],
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { returns(Integer) }
|
42
|
+
def children_pack_count
|
43
|
+
members.count do |package|
|
44
|
+
package.name != root.name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { returns(T::Boolean) }
|
49
|
+
def has_parent?
|
50
|
+
children_pack_count > 0
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { returns(T::Array[ParsePackwerk::Violation]) }
|
54
|
+
def cross_group_violations
|
55
|
+
all_violations = members.flat_map do |member|
|
56
|
+
ParsePackwerk::DeprecatedReferences.for(member).violations
|
57
|
+
end
|
58
|
+
|
59
|
+
all_violations.select do |violation|
|
60
|
+
!members.map(&:name).include?(violation.to_package_name)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
sig do
|
66
|
+
params(
|
67
|
+
packages: T::Array[ParsePackwerk::Package],
|
68
|
+
app_name: String
|
69
|
+
).returns(T::Array[GaugeMetric])
|
70
|
+
end
|
71
|
+
def self.get_nested_package_metrics(packages, app_name)
|
72
|
+
all_metrics = []
|
73
|
+
app_level_tag = Tag.for('app', app_name)
|
74
|
+
package_tags = T.let([app_level_tag], T::Array[Tag])
|
75
|
+
|
76
|
+
pack_groups = PackGroup.all_from(packages)
|
77
|
+
all_pack_groups_count = pack_groups.count
|
78
|
+
child_pack_count = pack_groups.sum(&:children_pack_count)
|
79
|
+
parent_pack_count = pack_groups.count(&:has_parent?)
|
80
|
+
all_cross_pack_group_violations = pack_groups.flat_map(&:cross_group_violations)
|
81
|
+
|
82
|
+
all_metrics << GaugeMetric.for('all_pack_groups.count', all_pack_groups_count, package_tags)
|
83
|
+
all_metrics << GaugeMetric.for('child_packs.count', child_pack_count, package_tags)
|
84
|
+
all_metrics << GaugeMetric.for('parent_packs.count', parent_pack_count, package_tags)
|
85
|
+
all_metrics << GaugeMetric.for('all_pack_groups.privacy_violations.count', Metrics.file_count(all_cross_pack_group_violations.select(&:privacy?)), package_tags)
|
86
|
+
all_metrics << GaugeMetric.for('all_pack_groups.dependency_violations.count', Metrics.file_count(all_cross_pack_group_violations.select(&:dependency?)), package_tags)\
|
87
|
+
|
88
|
+
packs_by_group = {}
|
89
|
+
pack_groups.each do |pack_group|
|
90
|
+
pack_group.members.each do |member|
|
91
|
+
packs_by_group[member.name] = pack_group.name
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
inbound_violations_by_pack_group = {}
|
96
|
+
all_cross_pack_group_violations.group_by(&:to_package_name).each do |to_package_name, violations|
|
97
|
+
violations.each do |violation|
|
98
|
+
pack_group_for_violation = packs_by_group[violation.to_package_name]
|
99
|
+
inbound_violations_by_pack_group[pack_group_for_violation] ||= []
|
100
|
+
inbound_violations_by_pack_group[pack_group_for_violation] << violation
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
pack_groups.each do |pack_group|
|
105
|
+
tags = [
|
106
|
+
*package_tags,
|
107
|
+
Tag.for('pack_group', Metrics.humanized_package_name(pack_group.name)),
|
108
|
+
]
|
109
|
+
|
110
|
+
outbound_dependency_violations = pack_group.cross_group_violations.select(&:dependency?)
|
111
|
+
inbound_privacy_violations = inbound_violations_by_pack_group.fetch(pack_group.name, []).select(&:privacy?)
|
112
|
+
all_metrics << GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', Metrics.file_count(outbound_dependency_violations), tags)
|
113
|
+
all_metrics << GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', Metrics.file_count(inbound_privacy_violations), tags)
|
114
|
+
end
|
115
|
+
|
116
|
+
pack_groups.each do |from_pack_group|
|
117
|
+
violations_by_to_pack_group = from_pack_group.cross_group_violations.group_by do |violation|
|
118
|
+
packs_by_group[violation.to_package_name]
|
119
|
+
end
|
120
|
+
violations_by_to_pack_group.each do |to_pack_group_name, violations|
|
121
|
+
tags = [
|
122
|
+
*package_tags,
|
123
|
+
Tag.for('pack_group', Metrics.humanized_package_name(from_pack_group.name)),
|
124
|
+
Tag.for('to_pack_group', Metrics.humanized_package_name(to_pack_group_name)),
|
125
|
+
]
|
126
|
+
|
127
|
+
all_metrics << GaugeMetric.for('by_pack_group.outbound_dependency_violations.per_pack_group.count', Metrics.file_count(violations.select(&:dependency?)), tags)
|
128
|
+
all_metrics << GaugeMetric.for('by_pack_group.outbound_privacy_violations.per_pack_group.count', Metrics.file_count(violations.select(&:privacy?)), tags)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
all_metrics
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModularizationStatistics
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
class Packages
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig do
|
11
|
+
params(
|
12
|
+
packages: T::Array[ParsePackwerk::Package],
|
13
|
+
app_name: String
|
14
|
+
).returns(T::Array[GaugeMetric])
|
15
|
+
end
|
16
|
+
def self.get_package_metrics(packages, app_name)
|
17
|
+
all_metrics = []
|
18
|
+
app_level_tag = Tag.for('app', app_name)
|
19
|
+
package_tags = T.let([app_level_tag], T::Array[Tag])
|
20
|
+
protected_packages = packages.map { |p| PackageProtections::ProtectedPackage.from(p) }
|
21
|
+
|
22
|
+
all_metrics << GaugeMetric.for('all_packages.count', packages.count, package_tags)
|
23
|
+
all_metrics << GaugeMetric.for('all_packages.dependencies.count', packages.sum { |package| package.dependencies.count }, package_tags)
|
24
|
+
all_metrics << GaugeMetric.for('all_packages.dependency_violations.count', protected_packages.sum { |package| Metrics.file_count(package.violations.select(&:dependency?)) }, package_tags)
|
25
|
+
all_metrics << GaugeMetric.for('all_packages.privacy_violations.count', protected_packages.sum { |package| Metrics.file_count(package.violations.select(&:privacy?)) }, package_tags)
|
26
|
+
all_metrics << GaugeMetric.for('all_packages.enforcing_dependencies.count', packages.count(&:enforces_dependencies?), package_tags)
|
27
|
+
all_metrics << GaugeMetric.for('all_packages.enforcing_privacy.count', packages.count(&:enforces_privacy?), package_tags)
|
28
|
+
|
29
|
+
all_metrics << GaugeMetric.for('all_packages.notify_on_package_yml_changes.count', packages.count { |p| p.metadata['notify_on_package_yml_changes'] }, package_tags)
|
30
|
+
all_metrics << GaugeMetric.for('all_packages.notify_on_new_violations.count', packages.count { |p| p.metadata['notify_on_new_violations'] }, package_tags)
|
31
|
+
|
32
|
+
all_metrics << GaugeMetric.for('all_packages.with_violations.count', protected_packages.count { |package| package.violations.any? }, package_tags)
|
33
|
+
all_metrics += Metrics::PublicUsage.get_public_usage_metrics('all_packages', packages, package_tags)
|
34
|
+
all_metrics << GaugeMetric.for('all_packages.has_readme.count', packages.count { |package| Metrics.has_readme?(package) }, package_tags)
|
35
|
+
|
36
|
+
all_metrics += Metrics::ProtectionUsage.get_protections_metrics('all_packages', protected_packages, package_tags)
|
37
|
+
all_metrics += Metrics::RubocopProtectionsExclusions.get_rubocop_exclusions('all_packages', protected_packages, package_tags)
|
38
|
+
all_metrics << GaugeMetric.for('all_packages.package_based_file_ownership.count', packages.count { |package| !package.metadata['owner'].nil? }, package_tags)
|
39
|
+
|
40
|
+
inbound_violations_by_package = protected_packages.flat_map(&:violations).group_by(&:to_package_name)
|
41
|
+
|
42
|
+
protected_packages.each do |protected_package|
|
43
|
+
package = protected_package.original_package
|
44
|
+
package_tags = Metrics.tags_for_package(package, app_name)
|
45
|
+
|
46
|
+
#
|
47
|
+
# VIOLATIONS (implicit dependencies)
|
48
|
+
#
|
49
|
+
outbound_violations = protected_package.violations
|
50
|
+
inbound_violations = inbound_violations_by_package[package.name] || []
|
51
|
+
all_dependency_violations = (outbound_violations + inbound_violations).select(&:dependency?)
|
52
|
+
all_privacy_violations = (outbound_violations + inbound_violations).select(&:privacy?)
|
53
|
+
|
54
|
+
all_metrics << GaugeMetric.for('by_package.dependency_violations.count', Metrics.file_count(all_dependency_violations), package_tags)
|
55
|
+
all_metrics << GaugeMetric.for('by_package.privacy_violations.count', Metrics.file_count(all_privacy_violations), package_tags)
|
56
|
+
|
57
|
+
all_metrics << GaugeMetric.for('by_package.outbound_dependency_violations.count', Metrics.file_count(outbound_violations.select(&:dependency?)), package_tags)
|
58
|
+
all_metrics << GaugeMetric.for('by_package.inbound_dependency_violations.count', Metrics.file_count(inbound_violations.select(&:dependency?)), package_tags)
|
59
|
+
|
60
|
+
all_metrics << GaugeMetric.for('by_package.outbound_privacy_violations.count', Metrics.file_count(outbound_violations.select(&:privacy?)), package_tags)
|
61
|
+
all_metrics << GaugeMetric.for('by_package.inbound_privacy_violations.count', Metrics.file_count(inbound_violations.select(&:privacy?)), package_tags)
|
62
|
+
|
63
|
+
all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_package', [package], package_tags)
|
64
|
+
|
65
|
+
protected_package.violations.group_by(&:to_package_name).each do |to_package_name, violations|
|
66
|
+
to_package = ParsePackwerk.find(to_package_name)
|
67
|
+
if to_package.nil?
|
68
|
+
raise StandardError, "Could not find matching package #{to_package_name}"
|
69
|
+
end
|
70
|
+
|
71
|
+
tags = package_tags + [Tag.for('to_package', Metrics.humanized_package_name(to_package_name))] + Metrics.tags_for_to_team(CodeOwnership.for_package(to_package)&.name)
|
72
|
+
all_metrics << GaugeMetric.for('by_package.outbound_dependency_violations.per_package.count', Metrics.file_count(violations.select(&:dependency?)), tags)
|
73
|
+
all_metrics << GaugeMetric.for('by_package.outbound_privacy_violations.per_package.count', Metrics.file_count(violations.select(&:privacy?)), tags)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
inbound_explicit_dependency_by_package = {}
|
78
|
+
packages.each do |package|
|
79
|
+
package.dependencies.each do |explicit_dependency|
|
80
|
+
inbound_explicit_dependency_by_package[explicit_dependency] ||= []
|
81
|
+
inbound_explicit_dependency_by_package[explicit_dependency] << package.name
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
packages.each do |package| # rubocop:disable Style/CombinableLoops
|
86
|
+
package_tags = Metrics.tags_for_package(package, app_name)
|
87
|
+
|
88
|
+
#
|
89
|
+
# EXPLICIT DEPENDENCIES
|
90
|
+
#
|
91
|
+
package.dependencies.each do |explicit_dependency|
|
92
|
+
to_package = ParsePackwerk.find(explicit_dependency)
|
93
|
+
if to_package.nil?
|
94
|
+
raise StandardError, "Could not find matching package #{explicit_dependency}"
|
95
|
+
end
|
96
|
+
|
97
|
+
tags = package_tags + [Tag.for('to_package', Metrics.humanized_package_name(explicit_dependency))] + Metrics.tags_for_to_team(CodeOwnership.for_package(to_package)&.name)
|
98
|
+
all_metrics << GaugeMetric.for('by_package.outbound_explicit_dependencies.per_package.count', 1, tags)
|
99
|
+
end
|
100
|
+
|
101
|
+
all_metrics << GaugeMetric.for('by_package.outbound_explicit_dependencies.count', package.dependencies.count, package_tags)
|
102
|
+
all_metrics << GaugeMetric.for('by_package.inbound_explicit_dependencies.count', inbound_explicit_dependency_by_package[package.name]&.count || 0, package_tags)
|
103
|
+
end
|
104
|
+
|
105
|
+
all_metrics
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModularizationStatistics
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
class PackagesByTeam
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig do
|
11
|
+
params(
|
12
|
+
all_packages: T::Array[ParsePackwerk::Package],
|
13
|
+
app_name: String
|
14
|
+
).returns(T::Array[GaugeMetric])
|
15
|
+
end
|
16
|
+
def self.get_package_metrics_by_team(all_packages, app_name)
|
17
|
+
all_metrics = T.let([], T::Array[GaugeMetric])
|
18
|
+
app_level_tag = Tag.for('app', app_name)
|
19
|
+
all_protected_packages = all_packages.map { |p| PackageProtections::ProtectedPackage.from(p) }
|
20
|
+
all_protected_packages.group_by { |protected_package| CodeOwnership.for_package(protected_package.original_package)&.name }.each do |team_name, protected_packages_by_team|
|
21
|
+
# We look at `all_packages` because we care about ALL inbound violations across all teams
|
22
|
+
inbound_violations_by_package = all_protected_packages.flat_map(&:violations).group_by(&:to_package_name)
|
23
|
+
|
24
|
+
team_tags = Metrics.tags_for_team(team_name) + [app_level_tag]
|
25
|
+
all_metrics << GaugeMetric.for('by_team.all_packages.count', protected_packages_by_team.count, team_tags)
|
26
|
+
all_metrics += Metrics::ProtectionUsage.get_protections_metrics('by_team', protected_packages_by_team, team_tags)
|
27
|
+
all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_team', protected_packages_by_team.map(&:original_package), team_tags)
|
28
|
+
|
29
|
+
all_metrics << GaugeMetric.for('by_team.notify_on_package_yml_changes.count', protected_packages_by_team.count { |p| p.metadata['notify_on_package_yml_changes'] }, team_tags)
|
30
|
+
all_metrics << GaugeMetric.for('by_team.notify_on_new_violations.count', protected_packages_by_team.count { |p| p.metadata['notify_on_new_violations'] }, team_tags)
|
31
|
+
|
32
|
+
#
|
33
|
+
# VIOLATIONS (implicit dependencies)
|
34
|
+
#
|
35
|
+
outbound_violations = protected_packages_by_team.flat_map(&:violations)
|
36
|
+
# Here we only look at packages_by_team because we only care about inbound violations onto packages for this team
|
37
|
+
inbound_violations = protected_packages_by_team.flat_map { |package| inbound_violations_by_package[package.name] || [] }
|
38
|
+
all_dependency_violations = (outbound_violations + inbound_violations).select(&:dependency?)
|
39
|
+
all_privacy_violations = (outbound_violations + inbound_violations).select(&:privacy?)
|
40
|
+
|
41
|
+
all_metrics << GaugeMetric.for('by_team.dependency_violations.count', Metrics.file_count(all_dependency_violations), team_tags)
|
42
|
+
all_metrics << GaugeMetric.for('by_team.privacy_violations.count', Metrics.file_count(all_privacy_violations), team_tags)
|
43
|
+
|
44
|
+
all_metrics << GaugeMetric.for('by_team.outbound_dependency_violations.count', Metrics.file_count(outbound_violations.select(&:dependency?)), team_tags)
|
45
|
+
all_metrics << GaugeMetric.for('by_team.inbound_dependency_violations.count', Metrics.file_count(inbound_violations.select(&:dependency?)), team_tags)
|
46
|
+
|
47
|
+
all_metrics << GaugeMetric.for('by_team.outbound_privacy_violations.count', Metrics.file_count(outbound_violations.select(&:privacy?)), team_tags)
|
48
|
+
all_metrics << GaugeMetric.for('by_team.inbound_privacy_violations.count', Metrics.file_count(inbound_violations.select(&:privacy?)), team_tags)
|
49
|
+
|
50
|
+
all_metrics << GaugeMetric.for('by_team.has_readme.count', protected_packages_by_team.count { |protected_package| Metrics.has_readme?(protected_package.original_package) }, team_tags)
|
51
|
+
|
52
|
+
grouped_outbound_violations = outbound_violations.group_by do |violation|
|
53
|
+
to_package = ParsePackwerk.find(violation.to_package_name)
|
54
|
+
if to_package.nil?
|
55
|
+
raise StandardError, "Could not find matching package #{violation.to_package_name}"
|
56
|
+
end
|
57
|
+
|
58
|
+
CodeOwnership.for_package(to_package)&.name
|
59
|
+
end
|
60
|
+
|
61
|
+
grouped_outbound_violations.each do |to_team_name, violations|
|
62
|
+
tags = team_tags + Metrics.tags_for_to_team(to_team_name)
|
63
|
+
all_metrics << GaugeMetric.for('by_team.outbound_dependency_violations.per_team.count', Metrics.file_count(violations.select(&:dependency?)), tags)
|
64
|
+
all_metrics << GaugeMetric.for('by_team.outbound_privacy_violations.per_team.count', Metrics.file_count(violations.select(&:privacy?)), tags)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
all_metrics
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModularizationStatistics
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
class ProtectionUsage
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(prefix: String, protected_packages: T::Array[PackageProtections::ProtectedPackage], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
|
11
|
+
def self.get_protections_metrics(prefix, protected_packages, package_tags)
|
12
|
+
PackageProtections.all.flat_map do |protection|
|
13
|
+
PackageProtections::ViolationBehavior.each_value.map do |violation_behavior|
|
14
|
+
# https://github.com/Gusto/package_protections/pull/42 changed the public API of these violation behaviors.
|
15
|
+
# To preserve our ability to understand historical trends, we map to the old values.
|
16
|
+
# This allows our dashboards to continue to operate as expected.
|
17
|
+
# Note if we ever open source mod stats, we should probably inject this behavior so that new clients can see the new keys in their metrics.
|
18
|
+
violation_behavior_map = {
|
19
|
+
PackageProtections::ViolationBehavior::FailOnAny => 'fail_the_build_on_any_instances',
|
20
|
+
PackageProtections::ViolationBehavior::FailNever => 'no',
|
21
|
+
PackageProtections::ViolationBehavior::FailOnNew => 'fail_the_build_if_new_instances_appear',
|
22
|
+
}
|
23
|
+
violation_behavior_name = violation_behavior_map[violation_behavior]
|
24
|
+
metric_name = "#{prefix}.#{protection.identifier}.#{violation_behavior_name}.count"
|
25
|
+
count_of_packages = protected_packages.count { |p| p.violation_behavior_for(protection.identifier) == violation_behavior }
|
26
|
+
GaugeMetric.for(metric_name, count_of_packages, package_tags)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModularizationStatistics
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
class PublicUsage
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
|
11
|
+
def self.get_public_usage_metrics(prefix, packages, package_tags)
|
12
|
+
packages_except_for_root = packages.reject { |package| package.name == ParsePackwerk::ROOT_PACKAGE_NAME }
|
13
|
+
all_files = packages_except_for_root.flat_map do |package|
|
14
|
+
package.directory.glob('**/**.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
all_public_files = T.let([], T::Array[Pathname])
|
18
|
+
is_using_public_directory = 0
|
19
|
+
packages_except_for_root.each do |package|
|
20
|
+
public_files = package.directory.glob('app/public/**/**.rb')
|
21
|
+
all_public_files += public_files
|
22
|
+
is_using_public_directory += 1 if public_files.any?
|
23
|
+
end
|
24
|
+
|
25
|
+
# In Datadog, we can divide public files by all files to get the ratio.
|
26
|
+
# This is not a metric that we are targeting -- its for observability and reflection only.
|
27
|
+
[
|
28
|
+
GaugeMetric.for("#{prefix}.all_files.count", all_files.count, package_tags),
|
29
|
+
GaugeMetric.for("#{prefix}.public_files.count", all_public_files.count, package_tags),
|
30
|
+
GaugeMetric.for("#{prefix}.using_public_directory.count", is_using_public_directory, package_tags),
|
31
|
+
]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModularizationStatistics
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
class RubocopProtectionsExclusions
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(prefix: String, protected_packages: T::Array[PackageProtections::ProtectedPackage], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
|
11
|
+
def self.get_rubocop_exclusions(prefix, protected_packages, package_tags)
|
12
|
+
rubocop_based_package_protections = T.cast(PackageProtections.all.select { |p| p.is_a?(PackageProtections::RubocopProtectionInterface) }, T::Array[PackageProtections::RubocopProtectionInterface])
|
13
|
+
rubocop_based_package_protections.flat_map do |rubocop_based_package_protection|
|
14
|
+
metric_name = "#{prefix}.#{rubocop_based_package_protection.identifier}.rubocop_exclusions.count"
|
15
|
+
all_exclusions_count = ParsePackwerk.all.sum { |package| exclude_count_for_package_and_protection(package, rubocop_based_package_protection)}
|
16
|
+
GaugeMetric.for(metric_name, all_exclusions_count, package_tags)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { params(package: ParsePackwerk::Package, protection: PackageProtections::RubocopProtectionInterface).returns(Integer) }
|
21
|
+
def self.exclude_count_for_package_and_protection(package, protection)
|
22
|
+
rubocop_todo = package.directory.join('.rubocop_todo.yml')
|
23
|
+
if rubocop_todo.exist?
|
24
|
+
loaded_rubocop_todo = YAML.load_file(rubocop_todo)
|
25
|
+
cop_config = loaded_rubocop_todo.fetch(protection.cop_name, {})
|
26
|
+
cop_config.fetch('Exclude', []).count
|
27
|
+
else
|
28
|
+
0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module ModularizationStatistics
|
5
|
+
module Private
|
6
|
+
module Metrics
|
7
|
+
extend T::Sig
|
8
|
+
UNKNOWN_OWNER = T.let('Unknown', String)
|
9
|
+
|
10
|
+
sig { params(team_name: T.nilable(String)).returns(T::Array[Tag]) }
|
11
|
+
def self.tags_for_team(team_name)
|
12
|
+
[Tag.for('team', team_name || UNKNOWN_OWNER)]
|
13
|
+
end
|
14
|
+
|
15
|
+
sig { params(package: ParsePackwerk::Package, app_name: String).returns(T::Array[Tag]) }
|
16
|
+
def self.tags_for_package(package, app_name)
|
17
|
+
[
|
18
|
+
Tag.new(key: 'package', value: humanized_package_name(package.name)),
|
19
|
+
Tag.new(key: 'app', value: app_name),
|
20
|
+
*Metrics.tags_for_team(CodeOwnership.for_package(package)&.name),
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { params(team_name: T.nilable(String)).returns(T::Array[Tag]) }
|
25
|
+
def self.tags_for_to_team(team_name)
|
26
|
+
[Tag.for('to_team', team_name || Metrics::UNKNOWN_OWNER)]
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { params(name: String).returns(String) }
|
30
|
+
def self.humanized_package_name(name)
|
31
|
+
if name == ParsePackwerk::ROOT_PACKAGE_NAME
|
32
|
+
'root'
|
33
|
+
else
|
34
|
+
name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { params(violations: T::Array[ParsePackwerk::Violation]).returns(Integer) }
|
39
|
+
def self.file_count(violations)
|
40
|
+
violations.sum { |v| v.files.count }
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { params(package: ParsePackwerk::Package).returns(T::Boolean) }
|
44
|
+
def self.has_readme?(package)
|
45
|
+
package.directory.join('README.md').exist?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/sorbet/config
CHANGED
@@ -12,6 +12,9 @@ module PackageProtections
|
|
12
12
|
sig { void }
|
13
13
|
def bust_cache!; end
|
14
14
|
|
15
|
+
sig { params(blk: T.proc.params(arg0: ::PackageProtections::Private::Configuration).void).void }
|
16
|
+
def configure(&blk); end
|
17
|
+
|
15
18
|
sig do
|
16
19
|
params(
|
17
20
|
packages: T::Array[::ParsePackwerk::Package],
|
@@ -20,14 +23,6 @@ module PackageProtections
|
|
20
23
|
end
|
21
24
|
def get_offenses(packages:, new_violations:); end
|
22
25
|
|
23
|
-
sig do
|
24
|
-
params(
|
25
|
-
package_names: T::Array[::String],
|
26
|
-
all_packages: T::Array[::ParsePackwerk::Package]
|
27
|
-
).returns(T::Array[::ParsePackwerk::Package])
|
28
|
-
end
|
29
|
-
def packages_for_names(package_names, all_packages); end
|
30
|
-
|
31
26
|
sig { params(identifier: ::String).returns(T::Hash[T.untyped, T.untyped]) }
|
32
27
|
def private_cop_config(identifier); end
|
33
28
|
|
@@ -102,6 +97,9 @@ module PackageProtections::Private
|
|
102
97
|
sig { void }
|
103
98
|
def bust_cache!; end
|
104
99
|
|
100
|
+
sig { returns(::PackageProtections::Private::Configuration) }
|
101
|
+
def config; end
|
102
|
+
|
105
103
|
sig do
|
106
104
|
params(
|
107
105
|
packages: T::Array[::ParsePackwerk::Package],
|
@@ -113,14 +111,6 @@ module PackageProtections::Private
|
|
113
111
|
sig { params(name: ::String).returns(::PackageProtections::ProtectedPackage) }
|
114
112
|
def get_package_with_name(name); end
|
115
113
|
|
116
|
-
sig do
|
117
|
-
params(
|
118
|
-
package_names: T::Array[::String],
|
119
|
-
all_packages: T::Array[::ParsePackwerk::Package]
|
120
|
-
).returns(T::Array[::ParsePackwerk::Package])
|
121
|
-
end
|
122
|
-
def packages_for_names(package_names, all_packages); end
|
123
|
-
|
124
114
|
sig { params(identifier: ::String).returns(T::Hash[T.untyped, T.untyped]) }
|
125
115
|
def private_cop_config(identifier); end
|
126
116
|
|
@@ -195,6 +185,23 @@ class PackageProtections::Private::ColorizedString::Color < ::T::Enum
|
|
195
185
|
end
|
196
186
|
end
|
197
187
|
|
188
|
+
class PackageProtections::Private::Configuration
|
189
|
+
sig { void }
|
190
|
+
def initialize; end
|
191
|
+
|
192
|
+
sig { void }
|
193
|
+
def bust_cache!; end
|
194
|
+
|
195
|
+
sig { returns(T::Array[::PackageProtections::ProtectionInterface]) }
|
196
|
+
def default_protections; end
|
197
|
+
|
198
|
+
sig { returns(T::Array[::PackageProtections::ProtectionInterface]) }
|
199
|
+
def protections; end
|
200
|
+
|
201
|
+
sig { params(protections: T::Array[::PackageProtections::ProtectionInterface]).void }
|
202
|
+
def protections=(protections); end
|
203
|
+
end
|
204
|
+
|
198
205
|
class PackageProtections::Private::IncomingPrivacyProtection
|
199
206
|
include ::PackageProtections::ProtectionInterface
|
200
207
|
|
@@ -256,51 +263,6 @@ class PackageProtections::Private::MetadataModifiers
|
|
256
263
|
end
|
257
264
|
end
|
258
265
|
|
259
|
-
class PackageProtections::Private::MultipleNamespacesProtection
|
260
|
-
include ::PackageProtections::ProtectionInterface
|
261
|
-
include ::PackageProtections::RubocopProtectionInterface
|
262
|
-
|
263
|
-
sig do
|
264
|
-
override
|
265
|
-
.params(
|
266
|
-
packages: T::Array[::PackageProtections::ProtectedPackage]
|
267
|
-
).returns(T::Array[::PackageProtections::RubocopProtectionInterface::CopConfig])
|
268
|
-
end
|
269
|
-
def cop_configs(packages); end
|
270
|
-
|
271
|
-
sig { params(package: ::PackageProtections::ProtectedPackage).returns(T::Hash[T.untyped, T.untyped]) }
|
272
|
-
def custom_cop_config(package); end
|
273
|
-
|
274
|
-
sig do
|
275
|
-
override
|
276
|
-
.params(
|
277
|
-
protected_packages: T::Array[::PackageProtections::ProtectedPackage]
|
278
|
-
).returns(T::Array[::PackageProtections::Offense])
|
279
|
-
end
|
280
|
-
def get_offenses_for_existing_violations(protected_packages); end
|
281
|
-
|
282
|
-
sig { override.returns(::String) }
|
283
|
-
def humanized_protection_description; end
|
284
|
-
|
285
|
-
sig { override.returns(::String) }
|
286
|
-
def humanized_protection_name; end
|
287
|
-
|
288
|
-
sig { override.returns(::String) }
|
289
|
-
def identifier; end
|
290
|
-
|
291
|
-
sig do
|
292
|
-
override
|
293
|
-
.params(
|
294
|
-
behavior: ::PackageProtections::ViolationBehavior,
|
295
|
-
package: ::ParsePackwerk::Package
|
296
|
-
).returns(T.nilable(::String))
|
297
|
-
end
|
298
|
-
def unmet_preconditions_for_behavior(behavior, package); end
|
299
|
-
end
|
300
|
-
|
301
|
-
PackageProtections::Private::MultipleNamespacesProtection::COP_NAME = T.let(T.unsafe(nil), String)
|
302
|
-
PackageProtections::Private::MultipleNamespacesProtection::IDENTIFIER = T.let(T.unsafe(nil), String)
|
303
|
-
|
304
266
|
class PackageProtections::Private::OutgoingDependencyProtection
|
305
267
|
include ::PackageProtections::ProtectionInterface
|
306
268
|
|
@@ -359,48 +321,6 @@ class PackageProtections::Private::Output
|
|
359
321
|
end
|
360
322
|
end
|
361
323
|
|
362
|
-
class PackageProtections::Private::TypedApiProtection
|
363
|
-
include ::PackageProtections::ProtectionInterface
|
364
|
-
include ::PackageProtections::RubocopProtectionInterface
|
365
|
-
|
366
|
-
sig do
|
367
|
-
override
|
368
|
-
.params(
|
369
|
-
packages: T::Array[::PackageProtections::ProtectedPackage]
|
370
|
-
).returns(T::Array[::PackageProtections::RubocopProtectionInterface::CopConfig])
|
371
|
-
end
|
372
|
-
def cop_configs(packages); end
|
373
|
-
|
374
|
-
sig do
|
375
|
-
override
|
376
|
-
.params(
|
377
|
-
protected_packages: T::Array[::PackageProtections::ProtectedPackage]
|
378
|
-
).returns(T::Array[::PackageProtections::Offense])
|
379
|
-
end
|
380
|
-
def get_offenses_for_existing_violations(protected_packages); end
|
381
|
-
|
382
|
-
sig { override.returns(::String) }
|
383
|
-
def humanized_protection_description; end
|
384
|
-
|
385
|
-
sig { override.returns(::String) }
|
386
|
-
def humanized_protection_name; end
|
387
|
-
|
388
|
-
sig { override.returns(::String) }
|
389
|
-
def identifier; end
|
390
|
-
|
391
|
-
sig do
|
392
|
-
override
|
393
|
-
.params(
|
394
|
-
behavior: ::PackageProtections::ViolationBehavior,
|
395
|
-
package: ::ParsePackwerk::Package
|
396
|
-
).returns(T.nilable(::String))
|
397
|
-
end
|
398
|
-
def unmet_preconditions_for_behavior(behavior, package); end
|
399
|
-
end
|
400
|
-
|
401
|
-
PackageProtections::Private::TypedApiProtection::COP_NAME = T.let(T.unsafe(nil), String)
|
402
|
-
PackageProtections::Private::TypedApiProtection::IDENTIFIER = T.let(T.unsafe(nil), String)
|
403
|
-
|
404
324
|
class PackageProtections::Private::VisibilityProtection
|
405
325
|
include ::PackageProtections::ProtectionInterface
|
406
326
|
|
@@ -560,16 +480,26 @@ module PackageProtections::RubocopProtectionInterface
|
|
560
480
|
abstract!
|
561
481
|
|
562
482
|
sig do
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
).returns(T::Array[::PackageProtections::RubocopProtectionInterface::CopConfig])
|
483
|
+
params(
|
484
|
+
packages: T::Array[::PackageProtections::ProtectedPackage]
|
485
|
+
).returns(T::Array[::PackageProtections::RubocopProtectionInterface::CopConfig])
|
567
486
|
end
|
568
487
|
def cop_configs(packages); end
|
569
488
|
|
489
|
+
sig { abstract.returns(::String) }
|
490
|
+
def cop_name; end
|
491
|
+
|
570
492
|
sig { params(package: ::PackageProtections::ProtectedPackage).returns(T::Hash[T.untyped, T.untyped]) }
|
571
493
|
def custom_cop_config(package); end
|
572
494
|
|
495
|
+
sig do
|
496
|
+
override
|
497
|
+
.params(
|
498
|
+
protected_packages: T::Array[::PackageProtections::ProtectedPackage]
|
499
|
+
).returns(T::Array[::PackageProtections::Offense])
|
500
|
+
end
|
501
|
+
def get_offenses_for_existing_violations(protected_packages); end
|
502
|
+
|
573
503
|
sig do
|
574
504
|
override
|
575
505
|
.params(
|
@@ -578,6 +508,21 @@ module PackageProtections::RubocopProtectionInterface
|
|
578
508
|
end
|
579
509
|
def get_offenses_for_new_violations(new_violations); end
|
580
510
|
|
511
|
+
sig { abstract.returns(T::Array[::String]) }
|
512
|
+
def included_globs_for_pack; end
|
513
|
+
|
514
|
+
sig { abstract.params(file: ::String).returns(::String) }
|
515
|
+
def message_for_fail_on_any(file); end
|
516
|
+
|
517
|
+
sig do
|
518
|
+
override
|
519
|
+
.params(
|
520
|
+
behavior: ::PackageProtections::ViolationBehavior,
|
521
|
+
package: ::ParsePackwerk::Package
|
522
|
+
).returns(T.nilable(::String))
|
523
|
+
end
|
524
|
+
def unmet_preconditions_for_behavior(behavior, package); end
|
525
|
+
|
581
526
|
private
|
582
527
|
|
583
528
|
sig { params(rule: ::String).returns(T::Set[::String]) }
|
@@ -630,3 +575,7 @@ class PackageProtections::ViolationBehavior < ::T::Enum
|
|
630
575
|
def from_raw_value(value); end
|
631
576
|
end
|
632
577
|
end
|
578
|
+
|
579
|
+
module RuboCop; end
|
580
|
+
module RuboCop::Cop; end
|
581
|
+
module RuboCop::Cop::PackageProtections; end
|
@@ -9,9 +9,15 @@ module ParsePackwerk
|
|
9
9
|
sig { returns(T::Array[::ParsePackwerk::Package]) }
|
10
10
|
def all; end
|
11
11
|
|
12
|
+
sig { void }
|
13
|
+
def bust_cache!; end
|
14
|
+
|
12
15
|
sig { params(name: ::String).returns(T.nilable(::ParsePackwerk::Package)) }
|
13
16
|
def find(name); end
|
14
17
|
|
18
|
+
sig { params(file_path: T.any(::Pathname, ::String)).returns(T.nilable(::ParsePackwerk::Package)) }
|
19
|
+
def package_from_path(file_path); end
|
20
|
+
|
15
21
|
sig { params(package: ::ParsePackwerk::Package).void }
|
16
22
|
def write_package_yml!(package); end
|
17
23
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modularization_statistics
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.37.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: code_teams
|
@@ -177,6 +177,14 @@ files:
|
|
177
177
|
- lib/modularization_statistics/gauge_metric.rb
|
178
178
|
- lib/modularization_statistics/private.rb
|
179
179
|
- lib/modularization_statistics/private/datadog_reporter.rb
|
180
|
+
- lib/modularization_statistics/private/metrics.rb
|
181
|
+
- lib/modularization_statistics/private/metrics/files.rb
|
182
|
+
- lib/modularization_statistics/private/metrics/nested_packs.rb
|
183
|
+
- lib/modularization_statistics/private/metrics/packages.rb
|
184
|
+
- lib/modularization_statistics/private/metrics/packages_by_team.rb
|
185
|
+
- lib/modularization_statistics/private/metrics/protection_usage.rb
|
186
|
+
- lib/modularization_statistics/private/metrics/public_usage.rb
|
187
|
+
- lib/modularization_statistics/private/metrics/rubocop_protections_exclusions.rb
|
180
188
|
- lib/modularization_statistics/private/source_code_file.rb
|
181
189
|
- lib/modularization_statistics/tag.rb
|
182
190
|
- lib/modularization_statistics/tags.rb
|
@@ -185,8 +193,8 @@ files:
|
|
185
193
|
- sorbet/rbi/gems/code_teams@1.0.0.rbi
|
186
194
|
- sorbet/rbi/gems/dogapi@1.45.0.rbi
|
187
195
|
- sorbet/rbi/gems/manual.rbi
|
188
|
-
- sorbet/rbi/gems/package_protections@
|
189
|
-
- sorbet/rbi/gems/parse_packwerk@0.
|
196
|
+
- sorbet/rbi/gems/package_protections@1.4.0.rbi
|
197
|
+
- sorbet/rbi/gems/parse_packwerk@0.12.0.rbi
|
190
198
|
- sorbet/rbi/gems/rspec@3.10.0.rbi
|
191
199
|
- sorbet/rbi/todo.rbi
|
192
200
|
homepage: https://github.com/rubyatscale/modularization_statistics
|