diff --git a/Gemfile.lock b/Gemfile.lock index 8422f64..1f66974 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - pack_stats (0.0.5) + pack_stats (0.0.6) code_ownership code_teams dogapi diff --git a/lib/pack_stats/private/datadog_reporter.rb b/lib/pack_stats/private/datadog_reporter.rb index 335dfd4..b979510 100644 --- a/lib/pack_stats/private/datadog_reporter.rb +++ b/lib/pack_stats/private/datadog_reporter.rb @@ -9,7 +9,6 @@ require 'pack_stats/private/metrics/rubocop_usage' require 'pack_stats/private/metrics/packages' require 'pack_stats/private/metrics/packages_by_team' -require 'pack_stats/private/metrics/nested_packs' module PackStats module Private @@ -29,7 +28,6 @@ def self.get_metrics(source_code_files:, app_name:) *Metrics::Files.get_metrics(source_code_files, app_name), *Metrics::Packages.get_package_metrics(packages, app_name), *Metrics::PackagesByTeam.get_package_metrics_by_team(packages, app_name), - *Metrics::NestedPacks.get_nested_package_metrics(packages, app_name) ] end diff --git a/lib/pack_stats/private/metrics/nested_packs.rb b/lib/pack_stats/private/metrics/nested_packs.rb deleted file mode 100644 index 9c8d8cd..0000000 --- a/lib/pack_stats/private/metrics/nested_packs.rb +++ /dev/null @@ -1,137 +0,0 @@ -# typed: strict -# frozen_string_literal: true - -module PackStats - module Private - module Metrics - class NestedPacks - extend T::Sig - - class PackGroup < T::Struct - extend T::Sig - - const :name, String - const :root, ParsePackwerk::Package - const :members, T::Array[ParsePackwerk::Package] - - sig { params(packages: T::Array[ParsePackwerk::Package]).returns(T::Array[PackGroup]) } - def self.all_from(packages) - packs_by_group = {} - - packages.each do |package| - # For a child pack, package.directory is `packs/fruits/apples` (i.e. the directory of the package.yml file). - # The package.directory.dirname is therefore `packs/fruits`. - # For a standalone pack, package.directory.dirname is `packs` - # A pack with no parent is in a pack group of its own name - root = ParsePackwerk.find(package.directory.dirname.to_s) || package - # Mark the parent pack and child pack as being in the pack group of the parent - packs_by_group[root.name] ||= { root: root, members: [] } - packs_by_group[root.name][:members] << package - end - - packs_by_group.map do |name, pack_data| - PackGroup.new( - name: name, - root: pack_data[:root], - members: pack_data[:members], - ) - end - end - - sig { returns(Integer) } - def children_pack_count - members.count do |package| - package.name != root.name - end - end - - sig { returns(T::Boolean) } - def has_parent? - children_pack_count > 0 - end - - sig { returns(T::Array[ParsePackwerk::Violation]) } - def cross_group_violations - all_violations = members.flat_map do |member| - ParsePackwerk::PackageTodo.for(member).violations - end - - all_violations.select do |violation| - !members.map(&:name).include?(violation.to_package_name) - end - end - end - - sig do - params( - packages: T::Array[ParsePackwerk::Package], - app_name: String - ).returns(T::Array[GaugeMetric]) - end - def self.get_nested_package_metrics(packages, app_name) - all_metrics = [] - app_level_tag = Tag.for('app', app_name) - package_tags = T.let([app_level_tag], T::Array[Tag]) - - pack_groups = PackGroup.all_from(packages) - all_pack_groups_count = pack_groups.count - child_pack_count = pack_groups.sum(&:children_pack_count) - parent_pack_count = pack_groups.count(&:has_parent?) - all_cross_pack_group_violations = pack_groups.flat_map(&:cross_group_violations) - - all_metrics << GaugeMetric.for('all_pack_groups.count', all_pack_groups_count, package_tags) - all_metrics << GaugeMetric.for('child_packs.count', child_pack_count, package_tags) - all_metrics << GaugeMetric.for('parent_packs.count', parent_pack_count, package_tags) - all_metrics << GaugeMetric.for('all_pack_groups.privacy_violations.count', Metrics.file_count(all_cross_pack_group_violations.select(&:privacy?)), package_tags) - all_metrics << GaugeMetric.for('all_pack_groups.dependency_violations.count', Metrics.file_count(all_cross_pack_group_violations.select(&:dependency?)), package_tags)\ - - packs_by_group = {} - pack_groups.each do |pack_group| - pack_group.members.each do |member| - packs_by_group[member.name] = pack_group.name - end - end - - inbound_violations_by_pack_group = {} - all_cross_pack_group_violations.group_by(&:to_package_name).each do |to_package_name, violations| - violations.each do |violation| - pack_group_for_violation = packs_by_group[violation.to_package_name] - inbound_violations_by_pack_group[pack_group_for_violation] ||= [] - inbound_violations_by_pack_group[pack_group_for_violation] << violation - end - end - - pack_groups.each do |pack_group| - tags = [ - *package_tags, - Tag.for('pack_group', Metrics.humanized_package_name(pack_group.name)), - ] - - outbound_dependency_violations = pack_group.cross_group_violations.select(&:dependency?) - inbound_privacy_violations = inbound_violations_by_pack_group.fetch(pack_group.name, []).select(&:privacy?) - all_metrics << GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', Metrics.file_count(outbound_dependency_violations), tags) - all_metrics << GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', Metrics.file_count(inbound_privacy_violations), tags) - end - - pack_groups.each do |from_pack_group| - violations_by_to_pack_group = from_pack_group.cross_group_violations.group_by do |violation| - packs_by_group[violation.to_package_name] - end - violations_by_to_pack_group.each do |to_pack_group_name, violations| - tags = [ - *package_tags, - Tag.for('pack_group', Metrics.humanized_package_name(from_pack_group.name)), - Tag.for('to_pack_group', Metrics.humanized_package_name(to_pack_group_name)), - ] - - all_metrics << GaugeMetric.for('by_pack_group.outbound_dependency_violations.per_pack_group.count', Metrics.file_count(violations.select(&:dependency?)), tags) - all_metrics << GaugeMetric.for('by_pack_group.outbound_privacy_violations.per_pack_group.count', Metrics.file_count(violations.select(&:privacy?)), tags) - end - end - - all_metrics - end - end - end - end -end diff --git a/pack_stats.gemspec b/pack_stats.gemspec index 8b3def2..d72370a 100644 --- a/pack_stats.gemspec +++ b/pack_stats.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = 'pack_stats' - spec.version = '0.0.5' + spec.version = '0.0.6' spec.authors = ['Gusto Engineers'] spec.email = ['dev@gusto.com'] diff --git a/spec/pack_stats_legacy_api_spec.rb b/spec/pack_stats_legacy_api_spec.rb index a9dbaef..aefb3f1 100644 --- a/spec/pack_stats_legacy_api_spec.rb +++ b/spec/pack_stats_legacy_api_spec.rb @@ -910,130 +910,6 @@ module PackStats # rubocop:disable RSpec/DescribedClassModuleWrapping end end - context 'in an app with nested packs' do - before do - write_package_yml('.') - write_package_yml('packs/fruits') - write_package_yml('packs/fruits/apples') - write_package_yml('packs/fruits/pears') - - write_package_yml('packs/vegetables') - write_package_yml('packs/vegetables/broccoli') - - write_package_yml('packs/peanuts') - write_package_yml('packs/cashews') - - # Represents TWO privacy and TWO dependency violations across pack groups - write_file('package_todo.yml', <<~CONTENTS) - # This file contains a list of dependencies that are not part of the long term plan for .. - # We should generally work to reduce this list, but not at the expense of actually getting work done. - # - # You can regenerate this file using the following command: - # - # bundle exec packwerk update-deprecations . - --- - packs/fruits: - "FruitsConstant": - violations: - - dependency - - privacy - files: - - some_file1.rb - - some_file2.rb - CONTENTS - - # Represents ONE privacy and ZERO dependency violations across pack groups - write_file('packs/fruits/package_todo.yml', <<~CONTENTS) - # This file contains a list of dependencies that are not part of the long term plan for .. - # We should generally work to reduce this list, but not at the expense of actually getting work done. - # - # You can regenerate this file using the following command: - # - # bundle exec packwerk update-deprecations . - --- - packs/fruits/apples: - "ApplesConstant": - violations: - - dependency - - privacy - files: - - some_file1.rb - - some_file2.rb - packs/peanuts: - "PeanutsConstant": - violations: - - privacy - files: - - some_file1.rb - CONTENTS - - # Represents ZERO violations across pack groups - write_file('packs/fruits/apples/package_todo.yml', <<~CONTENTS) - # This file contains a list of dependencies that are not part of the long term plan for .. - # We should generally work to reduce this list, but not at the expense of actually getting work done. - # - # You can regenerate this file using the following command: - # - # bundle exec packwerk update-deprecations . - --- - packs/fruits: - "FruitsConstant": - violations: - - dependency - - privacy - files: - - packs/fruits/apples/some_file1.rb - - packs/fruits/apples/some_file2.rb - - packs/fruits/apples/some_file3.rb - - packs/fruits/apples/some_file4.rb - packs/fruits/pears: - "PearsConstant": - violations: - - dependency - - privacy - files: - - packs/fruits/apples/some_file1.rb - - packs/fruits/apples/some_file2.rb - - packs/fruits/apples/some_file3.rb - - packs/fruits/apples/some_file4.rb - packs/peanuts: - "PeanutsConstant": - violations: - - dependency - files: - - packs/fruits/apples/some_file1.rb - CONTENTS - end - - it 'emits the right metrics' do - expect(metrics).to include_metric GaugeMetric.for('all_packages.count', 8, Tags.for(['app:MyApp'])) - expect(metrics).to include_metric GaugeMetric.for('all_pack_groups.count', 5, Tags.for(['app:MyApp'])) - expect(metrics).to include_metric GaugeMetric.for('child_packs.count', 3, Tags.for(['app:MyApp'])) - expect(metrics).to include_metric GaugeMetric.for('parent_packs.count', 2, Tags.for(['app:MyApp'])) - - # Notice that this does not use a tag to specify the pack group -- the metric itself only sends information about cross-pack group violations - expect(metrics).to include_metric GaugeMetric.for('all_pack_groups.privacy_violations.count', 3, Tags.for(['app:MyApp'])) - expect(metrics).to include_metric GaugeMetric.for('all_pack_groups.dependency_violations.count', 3, Tags.for(['app:MyApp'])) - - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 2, Tags.for(['app:MyApp', 'pack_group:root'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 1, Tags.for(['app:MyApp', 'pack_group:packs/fruits'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/vegetables'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/peanuts'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/cashews'])) - - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:root'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 2, Tags.for(['app:MyApp', 'pack_group:packs/fruits'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/vegetables'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 1, Tags.for(['app:MyApp', 'pack_group:packs/peanuts'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/cashews'])) - - # This does have a tag for pack group, but the metric itself also only sends information about cross-pack group violations. - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.per_pack_group.count', 2, Tags.for(['app:MyApp', 'pack_group:root', 'to_pack_group:packs/fruits'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_privacy_violations.per_pack_group.count', 2, Tags.for(['app:MyApp', 'pack_group:root', 'to_pack_group:packs/fruits'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.per_pack_group.count', 1, Tags.for(['app:MyApp', 'pack_group:packs/fruits', 'to_pack_group:packs/peanuts'])) - end - end - context 'in an app with exclusions for rubocop based protections' do before do write_package_yml('.') diff --git a/spec/pack_stats_spec.rb b/spec/pack_stats_spec.rb index 479db0e..0e9f02c 100644 --- a/spec/pack_stats_spec.rb +++ b/spec/pack_stats_spec.rb @@ -904,130 +904,6 @@ module PackStats # rubocop:disable RSpec/DescribedClassModuleWrapping end end - context 'in an app with nested packs' do - before do - write_package_yml('.') - write_package_yml('packs/fruits') - write_package_yml('packs/fruits/apples') - write_package_yml('packs/fruits/pears') - - write_package_yml('packs/vegetables') - write_package_yml('packs/vegetables/broccoli') - - write_package_yml('packs/peanuts') - write_package_yml('packs/cashews') - - # Represents TWO privacy and TWO dependency violations across pack groups - write_file('package_todo.yml', <<~CONTENTS) - # This file contains a list of dependencies that are not part of the long term plan for .. - # We should generally work to reduce this list, but not at the expense of actually getting work done. - # - # You can regenerate this file using the following command: - # - # bundle exec packwerk update-deprecations . - --- - packs/fruits: - "FruitsConstant": - violations: - - dependency - - privacy - files: - - some_file1.rb - - some_file2.rb - CONTENTS - - # Represents ONE privacy and ZERO dependency violations across pack groups - write_file('packs/fruits/package_todo.yml', <<~CONTENTS) - # This file contains a list of dependencies that are not part of the long term plan for .. - # We should generally work to reduce this list, but not at the expense of actually getting work done. - # - # You can regenerate this file using the following command: - # - # bundle exec packwerk update-deprecations . - --- - packs/fruits/apples: - "ApplesConstant": - violations: - - dependency - - privacy - files: - - some_file1.rb - - some_file2.rb - packs/peanuts: - "PeanutsConstant": - violations: - - privacy - files: - - some_file1.rb - CONTENTS - - # Represents ZERO violations across pack groups - write_file('packs/fruits/apples/package_todo.yml', <<~CONTENTS) - # This file contains a list of dependencies that are not part of the long term plan for .. - # We should generally work to reduce this list, but not at the expense of actually getting work done. - # - # You can regenerate this file using the following command: - # - # bundle exec packwerk update-deprecations . - --- - packs/fruits: - "FruitsConstant": - violations: - - dependency - - privacy - files: - - packs/fruits/apples/some_file1.rb - - packs/fruits/apples/some_file2.rb - - packs/fruits/apples/some_file3.rb - - packs/fruits/apples/some_file4.rb - packs/fruits/pears: - "PearsConstant": - violations: - - dependency - - privacy - files: - - packs/fruits/apples/some_file1.rb - - packs/fruits/apples/some_file2.rb - - packs/fruits/apples/some_file3.rb - - packs/fruits/apples/some_file4.rb - packs/peanuts: - "PeanutsConstant": - violations: - - dependency - files: - - packs/fruits/apples/some_file1.rb - CONTENTS - end - - it 'emits the right metrics' do - expect(metrics).to include_metric GaugeMetric.for('all_packages.count', 8, Tags.for(['app:MyApp'])) - expect(metrics).to include_metric GaugeMetric.for('all_pack_groups.count', 5, Tags.for(['app:MyApp'])) - expect(metrics).to include_metric GaugeMetric.for('child_packs.count', 3, Tags.for(['app:MyApp'])) - expect(metrics).to include_metric GaugeMetric.for('parent_packs.count', 2, Tags.for(['app:MyApp'])) - - # Notice that this does not use a tag to specify the pack group -- the metric itself only sends information about cross-pack group violations - expect(metrics).to include_metric GaugeMetric.for('all_pack_groups.privacy_violations.count', 3, Tags.for(['app:MyApp'])) - expect(metrics).to include_metric GaugeMetric.for('all_pack_groups.dependency_violations.count', 3, Tags.for(['app:MyApp'])) - - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 2, Tags.for(['app:MyApp', 'pack_group:root'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 1, Tags.for(['app:MyApp', 'pack_group:packs/fruits'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/vegetables'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/peanuts'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/cashews'])) - - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:root'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 2, Tags.for(['app:MyApp', 'pack_group:packs/fruits'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/vegetables'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 1, Tags.for(['app:MyApp', 'pack_group:packs/peanuts'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.inbound_privacy_violations.count', 0, Tags.for(['app:MyApp', 'pack_group:packs/cashews'])) - - # This does have a tag for pack group, but the metric itself also only sends information about cross-pack group violations. - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.per_pack_group.count', 2, Tags.for(['app:MyApp', 'pack_group:root', 'to_pack_group:packs/fruits'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_privacy_violations.per_pack_group.count', 2, Tags.for(['app:MyApp', 'pack_group:root', 'to_pack_group:packs/fruits'])) - expect(metrics).to include_metric GaugeMetric.for('by_pack_group.outbound_dependency_violations.per_pack_group.count', 1, Tags.for(['app:MyApp', 'pack_group:packs/fruits', 'to_pack_group:packs/peanuts'])) - end - end - context 'in an app with exclusions for rubocop based protections' do before do write_package_yml('.')