Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
modularization_statistics (1.37.0)
modularization_statistics (1.38.0)
code_ownership
code_teams
dogapi
Expand All @@ -12,7 +12,7 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activesupport (7.0.2.4)
activesupport (7.0.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
Expand All @@ -29,15 +29,17 @@ GEM
diff-lcs (1.5.0)
dogapi (1.45.0)
multi_json
i18n (1.10.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
json (2.6.2)
method_source (1.0.0)
minitest (5.15.0)
minitest (5.16.3)
multi_json (1.15.0)
package_protections (1.4.0)
package_protections (3.0.0)
activesupport
parse_packwerk
rubocop
rubocop-packs
rubocop-sorbet
sorbet-runtime
parallel (1.22.1)
Expand All @@ -55,7 +57,7 @@ GEM
parser (>= 2.6.4.0)
sorbet-runtime (>= 0.5.9204)
unparser
regexp_parser (2.2.1)
regexp_parser (2.6.0)
rexml (3.2.5)
rspec (3.11.0)
rspec-core (~> 3.11.0)
Expand All @@ -70,18 +72,25 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-support (3.11.0)
rubocop (1.26.1)
rubocop (1.33.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.16.0, < 2.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.19.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.16.0)
rubocop-ast (1.23.0)
parser (>= 3.1.1.0)
rubocop-sorbet (0.6.7)
rubocop-packs (0.0.13)
activesupport
parse_packwerk
rubocop
rubocop-sorbet
sorbet-runtime
rubocop-sorbet (0.6.11)
rubocop (>= 0.90.0)
ruby-progressbar (1.11.0)
sorbet (0.5.9924)
Expand Down Expand Up @@ -110,9 +119,9 @@ GEM
thor (>= 1.2.0)
yard-sorbet
thor (1.2.1)
tzinfo (2.0.4)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (2.1.0)
unicode-display_width (2.3.0)
unparser (0.6.4)
diff-lcs (~> 1.3)
parser (>= 3.1.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class ProtectionUsage
sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
def self.get_protections_metrics(prefix, packages, package_tags)
protected_packages = packages.map { |p| PackageProtections::ProtectedPackage.from(p) }

PackageProtections.all.flat_map do |protection|
PackageProtections::ViolationBehavior.each_value.map do |violation_behavior|
# https://github.com/Gusto/package_protections/pull/42 changed the public API of these violation behaviors.
Expand All @@ -24,7 +23,47 @@ def self.get_protections_metrics(prefix, packages, package_tags)
}
violation_behavior_name = violation_behavior_map[violation_behavior]
metric_name = "#{prefix}.#{protection.identifier}.#{violation_behavior_name}.count"
count_of_packages = protected_packages.count { |p| p.violation_behavior_for(protection.identifier) == violation_behavior }
count_of_packages = protected_packages.count do |p|
#
# This is temporarily in place until we migrate off of `package_protections` in favor of `rubocop-packs`.
# At that point, we want to delete this branch and instead it we'd probably have two separate branches.
# One branch would look at `enforce_x` and `metadata.strictly_enforce_x`.
# The other branch would look at `.pack_rubocop.yml`.
# Later on, we could generalize this so that it automatically incorporates new cops from `rubocop-packs`,
# or even new packwerk plugins.
#
# Regardless, we'll want to keep the way we are naming these behaviors for now to preserve historical trends in the data.
#
if p.metadata['protections']
p.violation_behavior_for(protection.identifier) == violation_behavior
else
case violation_behavior
when PackageProtections::ViolationBehavior::FailOnAny
# There is not yet an implementation for `FailOnAny` for systems that don't use package protections
false
when PackageProtections::ViolationBehavior::FailNever
if protection.identifier == 'prevent_this_package_from_violating_its_stated_dependencies'
!p.original_package.enforces_dependencies?
elsif protection.identifier == 'prevent_other_packages_from_using_this_packages_internals'
!p.original_package.enforces_privacy?
else
# This is not applicable if you're not using package protections
true
end
when PackageProtections::ViolationBehavior::FailOnNew
if protection.identifier == 'prevent_this_package_from_violating_its_stated_dependencies'
p.original_package.enforces_dependencies?
elsif protection.identifier == 'prevent_other_packages_from_using_this_packages_internals'
p.original_package.enforces_privacy?
else
# This is not applicable if you're not using package protections
false
end
else
T.absurd(violation_behavior)
end
end
end
GaugeMetric.for(metric_name, count_of_packages, package_tags)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def self.get_rubocop_exclusions(prefix, packages, package_tags)
end
end

# TODO: `rubocop-packs` may want to expose API for this
sig { params(package: ParsePackwerk::Package, protection: PackageProtections::RubocopProtectionInterface).returns(Integer) }
def self.exclude_count_for_package_and_protection(package, protection)
rubocop_todo = package.directory.join('.rubocop_todo.yml')
Expand Down
2 changes: 1 addition & 1 deletion modularization_statistics.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = 'modularization_statistics'
spec.version = '1.37.0'
spec.version = '1.38.0'
spec.authors = ['Gusto Engineers']
spec.email = ['dev@gusto.com']

Expand Down
57 changes: 57 additions & 0 deletions spec/modularization_statistics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,63 @@ module ModularizationStatistics # rubocop:disable RSpec/DescribedClassModuleWrap
end
end

context 'in app that does not use package protectiosn with a simple package owned by one team' do
include_context 'only one team'

before do
write_file('empty_file.rb')
write_file('packs/only_package/app/some_package_file.rb')
write_file('packs/only_package/package.yml', <<~CONTENTS)
enforce_dependencies: false
enforce_privacy: false
CONTENTS

write_file('packs/only_package/spec/some_package_file_spec.rb')
end

it 'emits the right metrics' do
expect(metrics).to include_metric GaugeMetric.for('component_files.by_team', 0, Tags.for(['team:Some team', 'app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('packaged_files.by_team', 2, Tags.for(['team:Some team', 'app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_files.by_team', 3, Tags.for(['team:Some team', 'app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('component_files.totals', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('packaged_files.totals', 2, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_files.totals', 3, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.count', 1, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.dependencies.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.dependency_violations.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.privacy_violations.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.enforcing_dependencies.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.enforcing_privacy.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.with_violations.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.prevent_this_package_from_violating_its_stated_dependencies.fail_the_build_on_any_instances.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.prevent_this_package_from_violating_its_stated_dependencies.fail_the_build_if_new_instances_appear.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.prevent_other_packages_from_using_this_packages_internals.fail_the_build_on_any_instances.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.prevent_other_packages_from_using_this_packages_internals.fail_the_build_if_new_instances_appear.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.prevent_this_package_from_exposing_an_untyped_api.fail_the_build_on_any_instances.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.prevent_this_package_from_exposing_an_untyped_api.fail_the_build_if_new_instances_appear.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.prevent_this_package_from_creating_other_namespaces.fail_the_build_on_any_instances.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.package_based_file_ownership.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.using_public_directory.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('by_package.dependency_violations.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.privacy_violations.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.outbound_dependency_violations.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.inbound_dependency_violations.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.outbound_privacy_violations.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.inbound_privacy_violations.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.outbound_explicit_dependencies.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.inbound_explicit_dependencies.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.using_public_directory.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.all_files.count', 2, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('all_packages.public_files.count', 0, Tags.for(['app:MyApp']))
expect(metrics).to include_metric GaugeMetric.for('by_team.using_public_directory.count', 0, Tags.for(['app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_team.all_files.count', 2, Tags.for(['app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_team.public_files.count', 0, Tags.for(['app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.using_public_directory.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.all_files.count', 2, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
expect(metrics).to include_metric GaugeMetric.for('by_package.public_files.count', 0, Tags.for(['package:packs/only_package', 'app:MyApp', 'team:Unknown']))
end
end

context 'in app with two packages owned by different teams' do
include_context 'team names are based off of file names'
before do
Expand Down