Skip to content

Commit

Permalink
Support CSV format for get_info and allow including any violation t…
Browse files Browse the repository at this point in the history
…ypes (#110)

* Refactor to separate stats-collection from output (no visible change)

* Use data instead of methods to distinguish violation type

* Use hashes and arrays allow easy support for new types (no visible change)

* Add support for reporting packs info in CSV format

* Include architecture violations on get_info

* Allow specifying what types of violations to output on get_info

* Allow including a Date in the get_info report (useful for snapshots)

Including the date as part of the report makes it easy to store the output as snapshots over time to compare progress.

* Prefer some more code readability over DRYness

This is to satisfy some feedback I got on the PR.
  • Loading branch information
oboxodo authored Oct 13, 2023
1 parent f5164fb commit 43d7a91
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 30 deletions.
14 changes: 13 additions & 1 deletion lib/packs/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,21 @@ def update
end

desc 'get_info [ packs/my_pack packs/my_other_pack ]', 'Get info about size and violations for packs'
option :include_date, type: :boolean, default: false, aliases: :d, banner: "Include today's date as part of the data (useful to take snapshots over time)"
option :format, type: :string, default: 'detail', aliases: :f, banner: 'Specify the output format (detail, csv)'
option :types, type: :string, default: 'privacy,dependency', aliases: :t, banner: 'List of validation types to include (privacy,dependency,architecture)'
sig { params(pack_names: String).void }
def get_info(*pack_names)
Private.get_info(packs: parse_pack_names(pack_names))
selected_types = options[:types].to_s.downcase.split(',')
invalid_types = selected_types - POSIBLE_TYPES
raise StandardError, "Invalid type(s): #{invalid_types.join(', ')}. Possible types are: #{POSIBLE_TYPES.join(', ')}" unless invalid_types.empty?

Private.get_info(
packs: parse_pack_names(pack_names),
format: options[:format].to_sym,
types: selected_types.map(&:to_sym),
include_date: options[:include_date]
)
exit_successfully
end

Expand Down
110 changes: 82 additions & 28 deletions lib/packs/private.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
require 'packs/private/pack_relationship_analyzer'
require 'packs/private/interactive_cli'

require 'date'

module Packs
module Private
extend T::Sig
Expand Down Expand Up @@ -479,45 +481,97 @@ def self.packwerk_package_to_pack(package)
Packs.find(package.name)
end

sig { params(packs: T::Array[Packs::Pack]).void }
def self.get_info(packs: Packs.all)
inbound_violations = {}
outbound_violations = {}
sig do
params(
packs: T::Array[Packs::Pack],
format: Symbol,
types: T::Array[Symbol],
include_date: T::Boolean
).void
end
def self.get_info(packs: Packs.all, format: :detail, types: %i[privacy dependency architecture], include_date: false)
require 'csv' if format == :csv

today = Date.today.iso8601
violations = {
inbound: {},
outbound: {}
}

ParsePackwerk.all.each do |p|
p.violations.each do |violation|
outbound_violations[p.name] ||= []
outbound_violations[p.name] << violation
inbound_violations[violation.to_package_name] ||= []
inbound_violations[violation.to_package_name] << violation
violations[:outbound][p.name] ||= []
violations[:outbound][p.name] << violation
violations[:inbound][violation.to_package_name] ||= []
violations[:inbound][violation.to_package_name] << violation
end
end

all_inbound = T.let([], T::Array[ParsePackwerk::Violation])
all_outbound = T.let([], T::Array[ParsePackwerk::Violation])
all = {
inbound: T.let([], T::Array[ParsePackwerk::Violation]),
outbound: T.let([], T::Array[ParsePackwerk::Violation])
}
packs.each do |pack|
all_inbound += inbound_violations[pack.name] || []
all_outbound += outbound_violations[pack.name] || []
all[:inbound] += violations[:inbound][pack.name] || []
all[:outbound] += violations[:outbound][pack.name] || []
end

puts "There are #{all_inbound.select(&:privacy?).sum { |v| v.files.count }} total inbound privacy violations"
puts "There are #{all_inbound.select(&:dependency?).sum { |v| v.files.count }} total inbound dependency violations"
puts "There are #{all_outbound.select(&:privacy?).sum { |v| v.files.count }} total outbound privacy violations"
puts "There are #{all_outbound.select(&:dependency?).sum { |v| v.files.count }} total outbound dependency violations"
case format
when :csv
headers = ['Date', 'Pack name', 'Owned by', 'Size', 'Public API']
headers.delete('Date') unless include_date
types.each do |type|
headers << "Inbound #{type} violations"
headers << "Outbound #{type} violations"
end
puts CSV.generate_line(headers)
else # :detail
puts "Date: #{today}" if include_date
types.each do |type|
inbound_count = all[:inbound].select { _1.type.to_sym == type }.sum { |v| v.files.count }
outbound_count = all[:outbound].select { _1.type.to_sym == type }.sum { |v| v.files.count }
puts "There are #{inbound_count} total inbound #{type} violations"
puts "There are #{outbound_count} total outbound #{type} violations"
end
end

packs.sort_by { |p| -p.relative_path.glob('**/*.rb').count }.each do |pack|
puts "\n=========== Info about: #{pack.name}"

owner = CodeOwnership.for_package(pack)
puts "Owned by: #{owner.nil? ? 'No one' : owner.name}"
puts "Size: #{pack.relative_path.glob('**/*.rb').count} ruby files"
puts "Public API: #{pack.relative_path.join('app/public')}"

inbound_for_pack = inbound_violations[pack.name] || []
outbound_for_pack = outbound_violations[pack.name] || []
puts "There are #{inbound_for_pack.select(&:privacy?).sum { |v| v.files.count }} inbound privacy violations"
puts "There are #{inbound_for_pack.flatten.select(&:dependency?).sum { |v| v.files.count }} inbound dependency violations"
puts "There are #{outbound_for_pack.select(&:privacy?).sum { |v| v.files.count }} outbound privacy violations"
puts "There are #{outbound_for_pack.flatten.select(&:dependency?).sum { |v| v.files.count }} outbound dependency violations"

row = {
date: today,
pack_name: pack.name,
owner: owner.nil? ? 'No one' : owner.name,
size: pack.relative_path.glob('**/*.rb').count,
public_api: pack.relative_path.join('app/public')
}

row.delete(:date) unless include_date

types.each do |type|
key = ['inbound', type, 'violations'].join('_').to_sym
row[key] = (violations[:inbound][pack.name] || []).select { _1.type.to_sym == type }.sum { |v| v.files.count }
key = ['outbound', type, 'violations'].join('_').to_sym
row[key] = (violations[:outbound][pack.name] || []).select { _1.type.to_sym == type }.sum { |v| v.files.count }
end

case format
when :csv
puts CSV.generate_line(row.values)
else # :detail
puts "\n=========== Info about: #{row[:pack_name]}"

puts "Date: #{row[:date]}" if include_date
puts "Owned by: #{row[:owner]}"
puts "Size: #{row[:size]} ruby files"
puts "Public API: #{row[:public_api]}"
types.each do |type|
key = ['inbound', type, 'violations'].join('_').to_sym
puts "There are #{row[key]} inbound #{type} violations"
key = ['outbound', type, 'violations'].join('_').to_sym
puts "There are #{row[key]} outbound #{type} violations"
end
end
end
end

Expand Down
16 changes: 15 additions & 1 deletion lib/packs/private/interactive_cli/use_cases/get_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,23 @@ def perform!(prompt)
selected_packs = PackSelector.single_or_all_pack_multi_select(prompt, question_text: 'What pack(s) would you like info on?')
end

format = prompt.select('What output format do you want?', %w[Detail CSV])

types = prompt.multi_select(
'What violation types do you want stats for?',
%w[Privacy Dependency Architecture]
)

include_date = !prompt.no?('Should the current date be included in the report?')

puts "You've selected #{selected_packs.count} packs. Wow! Here's all the info."

Private.get_info(packs: selected_packs)
Private.get_info(
packs: selected_packs,
format: format.downcase.to_sym,
types: types.map(&:downcase).map(&:to_sym),
include_date: include_date
)
end
end
end
Expand Down

0 comments on commit 43d7a91

Please sign in to comment.