# typed: strict require 'sorbet-runtime' require 'json' require 'yaml' require 'benchmark' require 'code_teams' require 'code_ownership' require 'pathname' require 'packs' require 'pack_stats/private' require 'pack_stats/private/source_code_file' require 'pack_stats/private/datadog_reporter' require 'parse_packwerk' require 'pack_stats/tag' require 'pack_stats/tags' require 'pack_stats/gauge_metric' module PackStats extend T::Sig ROOT_PACKAGE_NAME = T.let('root'.freeze, String) DEFAULT_COMPONENTIZED_SOURCE_CODE_LOCATIONS = T.let( [ Pathname.new('components'), Pathname.new('gems'), ].freeze, T::Array[Pathname] ) sig do params( datadog_client: Dogapi::Client, app_name: String, source_code_pathnames: T::Array[Pathname], componentized_source_code_locations: T::Array[Pathname], report_time: Time, verbose: T::Boolean, # See note on get_metrics packaged_source_code_locations: T.nilable(T::Array[Pathname]), # See note on get_metrics max_enforcements_tag_value: T::Boolean ).void end def self.report_to_datadog!( datadog_client:, app_name:, source_code_pathnames:, componentized_source_code_locations: DEFAULT_COMPONENTIZED_SOURCE_CODE_LOCATIONS, report_time: Time.now, # rubocop:disable Rails/TimeZone verbose: false, packaged_source_code_locations: [], max_enforcements_tag_value: false ) all_metrics = self.get_metrics( source_code_pathnames: source_code_pathnames, componentized_source_code_locations: componentized_source_code_locations, app_name: app_name, max_enforcements_tag_value: max_enforcements_tag_value, ) # This helps us debug what metrics are being sent if verbose all_metrics.each do |metric| puts "Sending metric: #{metric}" end end Private::DatadogReporter.report!( datadog_client: datadog_client, report_time: report_time, metrics: all_metrics ) end sig do params( source_code_pathnames: T::Array[Pathname], componentized_source_code_locations: T::Array[Pathname], app_name: String, # This field is deprecated packaged_source_code_locations: T.nilable(T::Array[Pathname]), # You can set this to `true` to tag all metrics with `max_enforcements:true`. # This is useful if you want to submit two sets of metrics: # Once with the violation counts as configured in the app # Another time with the violation counts after turning on all enforcements and running `bin/packwerk update`. max_enforcements_tag_value: T::Boolean ).returns(T::Array[GaugeMetric]) end def self.get_metrics( source_code_pathnames:, componentized_source_code_locations:, app_name:, packaged_source_code_locations: [], max_enforcements_tag_value: false ) GaugeMetric.set_max_enforcements_tag(max_enforcements_tag_value) Private::DatadogReporter.get_metrics( source_code_files: source_code_files( source_code_pathnames: source_code_pathnames, componentized_source_code_locations: componentized_source_code_locations, ), app_name: app_name ) end sig do params( source_code_pathnames: T::Array[Pathname], componentized_source_code_locations: T::Array[Pathname], ).returns(T::Array[Private::SourceCodeFile]) end def self.source_code_files( source_code_pathnames:, componentized_source_code_locations: ) # Sorbet has the wrong signatures for `Pathname#find`, whoops! componentized_file_set = Set.new(componentized_source_code_locations.select(&:exist?).flat_map { |pathname| T.unsafe(pathname).find.to_a }) packaged_file_set = Packs.all.flat_map do |pack| pack.relative_path.find.to_a end packaged_file_set = Set.new(packaged_file_set) source_code_pathnames.map do |pathname| componentized_file = componentized_file_set.include?(pathname) packaged_file = packaged_file_set.include?(pathname) Private::SourceCodeFile.new( pathname: pathname, team_owner: CodeOwnership.for_file(pathname.to_s), is_componentized_file: componentized_file, is_packaged_file: packaged_file ) end end private_class_method :source_code_files end