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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0d44cafa4c07aed6fb4248234f60f70e6a0f9124b7ad6184f515feb22ef972a
4
- data.tar.gz: 3a1ae5714c23a52c63c2cff8616a9f739e3bb68a44abd8e76e35b864e792c2c7
3
+ metadata.gz: 252db0bdd5ec50e555d34ce4c7468df4a5bf731274fafd137a319eaf1a1100b9
4
+ data.tar.gz: 8983a207179da6a93b34464f98c3b478a23087b34946f63d4c1cf95992d10f9e
5
5
  SHA512:
6
- metadata.gz: 750643b2ff0d4705f69c66f049be32d070e50189173e2b44dd3975fde11ed8052c182b24729831e5e4553bc5f23c8d88f76ee5a0b70a85f5f1842c855bbd0d7e
7
- data.tar.gz: 434a4e77901c9af6783de0db879daaaf1bb4918835f1a147a0cc5aa0ff7c4c35ba12e306e23db3a65a9b875b97c8503b6e49a2c93b2e2c4014c9c1d01845afd2
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
- all_metrics
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
@@ -1,3 +1,4 @@
1
1
  --dir
2
2
  .
3
3
  --ignore=/vendor/bundle
4
+ --enable-experimental-requires-ancestor
@@ -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
- abstract
564
- .params(
565
- packages: T::Array[::PackageProtections::ProtectedPackage]
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.34.0
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-06-14 00:00:00.000000000 Z
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@0.64.0.rbi
189
- - sorbet/rbi/gems/parse_packwerk@0.10.0.rbi
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