├── .ruby-version ├── .rspec ├── CHANGELOG.md ├── sorbet ├── rbi │ ├── gems │ │ ├── manual.rbi │ │ ├── rspec@3.10.0.rbi │ │ ├── code_teams@1.0.0.rbi │ │ ├── packs@0.0.5.rbi │ │ ├── parse_packwerk@0.19.1.rbi │ │ ├── ast@2.4.2.rbi │ │ ├── code_ownership@1.29.2.rbi │ │ └── dogapi@1.45.0.rbi │ └── todo.rbi └── config ├── docs ├── screen-1.png ├── screen-2.png ├── screenshots.md └── dashboard.json ├── Gemfile ├── Rakefile ├── .gitignore ├── .github └── workflows │ ├── stale.yml │ ├── triage.yml │ ├── cd.yml │ └── ci.yml ├── lib ├── pack_stats │ ├── private.rb │ ├── tags.rb │ ├── private │ │ ├── source_code_file.rb │ │ ├── metrics │ │ │ ├── public_usage.rb │ │ │ ├── files.rb │ │ │ ├── dependencies.rb │ │ │ ├── packwerk_checker_usage.rb │ │ │ ├── packages_by_team.rb │ │ │ └── packages.rb │ │ ├── metrics.rb │ │ └── datadog_reporter.rb │ ├── tag.rb │ └── gauge_metric.rb └── pack_stats.rb ├── spec ├── lib │ └── gauge_metric_spec.rb └── spec_helper.rb ├── bin └── tapioca ├── LICENSE ├── pack_stats.gemspec ├── Gemfile.lock ├── README.md └── CODE_OF_CONDUCT.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | See https://github.com/rubyatscale/pack_stats/releases 2 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/manual.rbi: -------------------------------------------------------------------------------- 1 | class Pathname 2 | def glob(glob) 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /docs/screen-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyatscale/pack_stats/HEAD/docs/screen-1.png -------------------------------------------------------------------------------- /docs/screen-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyatscale/pack_stats/HEAD/docs/screen-2.png -------------------------------------------------------------------------------- /sorbet/config: -------------------------------------------------------------------------------- 1 | --dir 2 | . 3 | --ignore=/vendor/bundle 4 | --enable-experimental-requires-ancestor 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in pack_stats.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # typed: ignore 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /sorbet/rbi/todo.rbi: -------------------------------------------------------------------------------- 1 | # This file is autogenerated. Do not edit it by hand. Regenerate it with: 2 | # srb rbi todo 3 | 4 | # typed: strong 5 | module ::RSpecTempfiles; end 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | 10 | # rspec failure tracking 11 | .rspec_status 12 | -------------------------------------------------------------------------------- /docs/screenshots.md: -------------------------------------------------------------------------------- 1 | # Screenshots 2 | Below are the datadog screenshots produces from the sample [dashboard.json](./dashboard.json) 3 | 4 | 5 | ![alt screenshot](./screen-1.png) 6 | ![alt screenshot](./screen-2.png) -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | jobs: 7 | call-workflow-from-shared-config: 8 | uses: rubyatscale/shared-config/.github/workflows/stale.yml@main 9 | -------------------------------------------------------------------------------- /.github/workflows/triage.yml: -------------------------------------------------------------------------------- 1 | name: Label issues as "triage" 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | jobs: 8 | call-workflow-from-shared-config: 9 | uses: rubyatscale/shared-config/.github/workflows/triage.yml@main 10 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | workflow_run: 5 | workflows: [CI] 6 | types: [completed] 7 | branches: [main] 8 | 9 | jobs: 10 | call-workflow-from-shared-config: 11 | uses: rubyatscale/shared-config/.github/workflows/cd.yml@main 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/rspec@3.10.0.rbi: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT MANUALLY 2 | # This is an autogenerated file for types exported from the `rspec` gem. 3 | # Please instead update this file by running `bin/tapioca sync`. 4 | 5 | # typed: true 6 | 7 | module RSpec 8 | end 9 | 10 | module RSpec 11 | module Matchers 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/pack_stats/private.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module PackStats 4 | module Private 5 | extend T::Sig 6 | 7 | sig { params(package: ParsePackwerk::Package).returns(T.nilable(String) )} 8 | def self.package_owner(package) 9 | pack = Packs.find(package.name) 10 | return nil if pack.nil? 11 | CodeOwnership.for_package(pack)&.name 12 | end 13 | end 14 | 15 | private_constant :Private 16 | end 17 | -------------------------------------------------------------------------------- /lib/pack_stats/tags.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module PackStats 4 | module Tags 5 | extend T::Sig 6 | 7 | sig { params(colon_delimited_tag_strings: T::Array[String]).returns(T::Array[Tag]) } 8 | def self.for(colon_delimited_tag_strings) 9 | colon_delimited_tag_strings.map do |colon_delimited_tag_string| 10 | key, value = colon_delimited_tag_string.split(':') 11 | raise StandardError, "Improperly formatted tag `#{colon_delimited_tag_string}`" if key.nil? || value.nil? 12 | 13 | Tag.new( 14 | key: key, 15 | value: value 16 | ) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/pack_stats/private/source_code_file.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module PackStats 4 | module Private 5 | class SourceCodeFile < T::Struct 6 | extend T::Sig 7 | 8 | const :is_componentized_file, T::Boolean 9 | const :is_packaged_file, T::Boolean 10 | const :team_owner, T.nilable(CodeTeams::Team) 11 | const :pathname, Pathname 12 | 13 | sig { returns(T::Boolean) } 14 | def componentized_file? 15 | self.is_componentized_file 16 | end 17 | 18 | sig { returns(T::Boolean) } 19 | def packaged_file? 20 | self.is_packaged_file 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pack_stats/tag.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module PackStats 4 | class Tag < T::Struct 5 | extend T::Sig 6 | const :key, String 7 | const :value, String 8 | 9 | sig { returns(String) } 10 | def to_s 11 | "#{key}:#{value}" 12 | end 13 | 14 | sig { params(key: String, value: String).returns(Tag) } 15 | def self.for(key, value) 16 | new( 17 | key: key, 18 | value: value 19 | ) 20 | end 21 | 22 | sig { params(other: Tag).returns(T::Boolean) } 23 | def ==(other) 24 | other.key == self.key && 25 | other.value == self.value 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/lib/gauge_metric_spec.rb: -------------------------------------------------------------------------------- 1 | module PackStats 2 | RSpec.describe GaugeMetric do 3 | it 'errors when metric length is greater than 200' do 4 | expect { GaugeMetric.for('a' * 250, 0, []) }.to raise_error do |e| 5 | expect(e.message).to eq 'Metrics names must not exceed 200 characters: modularization.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' 6 | end 7 | end 8 | 9 | it 'does not error when metric is less than 100' do 10 | expect { GaugeMetric.for('a' * 150, 0, []) }.to_not raise_error 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/tapioca: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'tapioca' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "pathname" 12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 13 | Pathname.new(__FILE__).realpath) 14 | 15 | bundle_binstub = File.expand_path("../bundle", __FILE__) 16 | 17 | if File.file?(bundle_binstub) 18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 19 | load(bundle_binstub) 20 | else 21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 23 | end 24 | end 25 | 26 | require "rubygems" 27 | require "bundler/setup" 28 | 29 | load Gem.bin_path("tapioca", "tapioca") 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | rspec: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | ruby: 15 | - 3.1 16 | - 3.2 17 | - 3.3 18 | env: 19 | BUNDLE_GEMFILE: Gemfile 20 | name: "RSpec tests: Ruby ${{ matrix.ruby }}" 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Set up Ruby ${{ matrix.ruby }} 24 | uses: ruby/setup-ruby@v1 25 | with: 26 | bundler-cache: true 27 | ruby-version: ${{ matrix.ruby }} 28 | - name: Run tests 29 | run: bundle exec rspec 30 | static_type_check: 31 | name: "Type Check" 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v4 35 | - name: Set up Ruby 36 | uses: ruby/setup-ruby@v1 37 | with: 38 | bundler-cache: true 39 | ruby-version: 3.3 40 | - name: Run static type checks 41 | run: bundle exec srb tc 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Gusto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/pack_stats/private/metrics/public_usage.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | module PackStats 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 37 | -------------------------------------------------------------------------------- /lib/pack_stats/private/metrics/files.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | module PackStats 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 48 | -------------------------------------------------------------------------------- /lib/pack_stats/gauge_metric.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | module PackStats 4 | class GaugeMetric < T::Struct 5 | extend T::Sig 6 | 7 | const :name, String 8 | const :count, Integer 9 | const :tags, T::Array[Tag] 10 | 11 | sig { params(metric_name: String, count: Integer, tags: T::Array[Tag]).returns(GaugeMetric) } 12 | def self.for(metric_name, count, tags) 13 | name = "modularization.#{metric_name}" 14 | # https://docs.datadoghq.com/metrics/custom_metrics/#naming-custom-metrics 15 | # Metric names must not exceed 200 characters. Fewer than 100 is preferred from a UI perspective 16 | if name.length > 200 17 | raise StandardError.new("Metrics names must not exceed 200 characters: #{name}") # rubocop:disable Style/RaiseArgs 18 | end 19 | 20 | all_tags = [*tags, max_enforcements_tag] 21 | new( 22 | name: name, 23 | count: count, 24 | tags: all_tags 25 | ) 26 | end 27 | 28 | sig { returns(String) } 29 | def to_s 30 | "#{name} with count #{count}, with tags #{tags.map(&:to_s).join(', ')}" 31 | end 32 | 33 | sig { params(other: GaugeMetric).returns(T::Boolean) } 34 | def ==(other) 35 | other.name == self.name && 36 | other.count == self.count && 37 | other.tags == self.tags 38 | end 39 | 40 | sig { params(tag_value: T::Boolean).void } 41 | def self.set_max_enforcements_tag(tag_value) 42 | @max_enforcements_tag = T.let(@max_enforcements_tag, T.nilable(Tag)) 43 | @max_enforcements_tag = Tag.new(key: 'max_enforcements', value: tag_value ? 'true' : 'false') 44 | end 45 | 46 | sig { returns(Tag) } 47 | def self.max_enforcements_tag 48 | @max_enforcements_tag || Tag.new(key: 'max_enforcements', value: 'false') 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /pack_stats.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |spec| 2 | spec.name = 'pack_stats' 3 | spec.version = '0.2.1' 4 | spec.authors = ['Gusto Engineers'] 5 | spec.email = ['dev@gusto.com'] 6 | 7 | spec.summary = 'A gem to collect statistics about modularization progress in a Rails application using packwerk.' 8 | spec.description = 'A gem to collect statistics about modularization progress in a Rails application using packwerk.' 9 | spec.homepage = 'https://github.com/rubyatscale/pack_stats' 10 | spec.license = 'MIT' 11 | 12 | if spec.respond_to?(:metadata) 13 | spec.metadata['homepage_uri'] = spec.homepage 14 | spec.metadata['source_code_uri'] = 'https://github.com/rubyatscale/pack_stats' 15 | spec.metadata['changelog_uri'] = 'https://github.com/rubyatscale/pack_stats/releases' 16 | spec.metadata['allowed_push_host'] = 'https://rubygems.org' 17 | else 18 | raise 'RubyGems 2.0 or newer is required to protect against ' \ 19 | 'public gem pushes.' 20 | end 21 | 22 | spec.required_ruby_version = Gem::Requirement.new('>= 2.6.0') 23 | 24 | # Specify which files should be added to the gem when it is released. 25 | spec.files = Dir['README.md', 'lib/**/*'] 26 | 27 | spec.require_paths = ['lib'] 28 | 29 | spec.add_dependency 'code_teams' 30 | spec.add_dependency 'code_ownership' 31 | spec.add_dependency 'dogapi' 32 | spec.add_dependency 'packs' 33 | spec.add_dependency 'parse_packwerk' 34 | spec.add_dependency 'sorbet-runtime' 35 | spec.add_dependency 'rubocop-packs' 36 | 37 | spec.add_development_dependency 'base64' 38 | spec.add_development_dependency 'pry' 39 | spec.add_development_dependency 'rake' 40 | spec.add_development_dependency 'rspec', '~> 3.0' 41 | spec.add_development_dependency 'sorbet' 42 | spec.add_development_dependency 'tapioca' 43 | end 44 | -------------------------------------------------------------------------------- /lib/pack_stats/private/metrics.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | module PackStats 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 | tags = [ 19 | Tag.new(key: 'package', value: humanized_package_name(package.name)), 20 | Tag.new(key: 'app', value: app_name), 21 | *Metrics.tags_for_team(Private.package_owner(package)), 22 | ] 23 | 24 | layer = package.config['layer'] 25 | if layer 26 | tags << Tag.new(key: 'layer', value: layer) 27 | end 28 | 29 | tags 30 | end 31 | 32 | sig { params(team_name: T.nilable(String)).returns(T::Array[Tag]) } 33 | def self.tags_for_other_team(team_name) 34 | [Tag.for('other_team', team_name || Metrics::UNKNOWN_OWNER)] 35 | end 36 | 37 | sig { params(name: String).returns(String) } 38 | def self.humanized_package_name(name) 39 | if name == ParsePackwerk::ROOT_PACKAGE_NAME 40 | 'root' 41 | else 42 | name 43 | end 44 | end 45 | 46 | sig { params(violations: T::Array[ParsePackwerk::Violation]).returns(Integer) } 47 | def self.file_count(violations) 48 | violations.sum { |v| v.files.count } 49 | end 50 | 51 | sig { params(package: ParsePackwerk::Package).returns(T::Boolean) } 52 | def self.has_readme?(package) 53 | package.directory.join('README.md').exist? 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/pack_stats/private/metrics/dependencies.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | module PackStats 5 | module Private 6 | module Metrics 7 | class Dependencies 8 | extend T::Sig 9 | 10 | sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], app_name: String).returns(T::Array[GaugeMetric]) } 11 | def self.get_metrics(prefix, packages, app_name) 12 | all_metrics = T.let([], T::Array[GaugeMetric]) 13 | inbound_explicit_dependency_by_package = {} 14 | packages.each do |package| 15 | package.dependencies.each do |explicit_dependency| 16 | inbound_explicit_dependency_by_package[explicit_dependency] ||= [] 17 | inbound_explicit_dependency_by_package[explicit_dependency] << package.name 18 | end 19 | end 20 | 21 | packages.each do |package| # rubocop:disable Style/CombinableLoops 22 | package_tags = Metrics.tags_for_package(package, app_name) 23 | 24 | # 25 | # EXPLICIT DEPENDENCIES 26 | # 27 | package.dependencies.each do |explicit_dependency| 28 | to_package = ParsePackwerk.find(explicit_dependency) 29 | if to_package.nil? 30 | raise StandardError, "Could not find matching package #{explicit_dependency}" 31 | end 32 | 33 | owner = Private.package_owner(to_package) 34 | tags = package_tags + [Tag.for('other_package', Metrics.humanized_package_name(explicit_dependency))] + Metrics.tags_for_other_team(owner) 35 | all_metrics << GaugeMetric.for('by_package.dependencies.by_other_package.count', 1, tags) 36 | end 37 | 38 | all_metrics << GaugeMetric.for('by_package.dependencies.count', package.dependencies.count, package_tags) 39 | all_metrics << GaugeMetric.for('by_package.depended_on.count', inbound_explicit_dependency_by_package[package.name]&.count || 0, package_tags) 40 | end 41 | 42 | all_metrics 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/pack_stats/private/datadog_reporter.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | require 'dogapi' 5 | require 'pack_stats/private/metrics' 6 | require 'pack_stats/private/metrics/files' 7 | require 'pack_stats/private/metrics/public_usage' 8 | require 'pack_stats/private/metrics/packwerk_checker_usage' 9 | require 'pack_stats/private/metrics/dependencies' 10 | require 'pack_stats/private/metrics/packages' 11 | require 'pack_stats/private/metrics/packages_by_team' 12 | 13 | module PackStats 14 | module Private 15 | class DatadogReporter 16 | extend T::Sig 17 | 18 | sig do 19 | params( 20 | source_code_files: T::Array[SourceCodeFile], 21 | app_name: String 22 | ).returns(T::Array[GaugeMetric]) 23 | end 24 | def self.get_metrics(source_code_files:, app_name:) 25 | packages = ParsePackwerk.all 26 | 27 | [ 28 | *Metrics::Files.get_metrics(source_code_files, app_name), 29 | *Metrics::Packages.get_package_metrics(packages, app_name), 30 | *Metrics::PackagesByTeam.get_package_metrics_by_team(packages, app_name), 31 | ] 32 | end 33 | 34 | sig do 35 | params( 36 | datadog_client: Dogapi::Client, 37 | # Since `gauge` buckets data points we need to use the same time for all API calls 38 | # to ensure they fall into the same bucket. 39 | report_time: Time, 40 | metrics: T::Array[GaugeMetric] 41 | ).void 42 | end 43 | def self.report!(datadog_client:, report_time:, metrics:) 44 | # 45 | # Batching the metrics sends a post request to DD 46 | # we want to split this up into chunks of 1000 so that as we add more metrics, 47 | # our payload is not rejected for being too large 48 | # 49 | metrics.each_slice(1000).each do |metric_slice| 50 | datadog_client.batch_metrics do 51 | metric_slice.each do |metric| 52 | datadog_client.emit_points(metric.name, [[report_time, metric.count]], type: 'gauge', tags: metric.tags.map(&:to_s)) 53 | end 54 | end 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/pack_stats/private/metrics/packwerk_checker_usage.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | require 'rubocop-packs' 5 | 6 | module PackStats 7 | module Private 8 | module Metrics 9 | class PackwerkCheckerUsage 10 | extend T::Sig 11 | 12 | # Some violations (e.g. dependency, visibility, layer) matter for the referencing (outbound) package. 13 | # Other violations (e.g. privacy) matter for the referenced (inbound) package. 14 | class Direction < T::Enum 15 | enums do 16 | Inbound = new 17 | Outbound = new 18 | end 19 | end 20 | 21 | # Later, we might find a way we can get this directly from `packwerk` 22 | class PackwerkChecker < T::Struct 23 | extend T::Sig 24 | 25 | const :key, String 26 | const :violation_type, String 27 | const :direction, Direction 28 | 29 | sig { returns(Tag) } 30 | def violation_type_tag 31 | Tag.new( 32 | key: 'violation_type', 33 | value: violation_type 34 | ) 35 | end 36 | end 37 | 38 | CHECKERS = T.let([ 39 | PackwerkChecker.new(key: 'enforce_dependencies', violation_type: 'dependency', direction: Direction::Outbound), 40 | PackwerkChecker.new(key: 'enforce_privacy', violation_type: 'privacy', direction: Direction::Inbound), 41 | PackwerkChecker.new(key: 'enforce_folder_privacy', violation_type: 'folder_privacy', direction: Direction::Inbound), 42 | PackwerkChecker.new(key: 'enforce_layers', violation_type: 'layer', direction: Direction::Outbound), 43 | PackwerkChecker.new(key: 'enforce_visibility', violation_type: 'visibility', direction: Direction::Outbound), 44 | ], T::Array[PackwerkChecker]) 45 | 46 | sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) } 47 | def self.get_checker_metrics(prefix, packages, package_tags) 48 | metrics = T.let([], T::Array[GaugeMetric]) 49 | 50 | CHECKERS.each do |checker| 51 | checker_values = packages.map do |package| 52 | YAML.load_file(package.yml)[checker.key] 53 | end 54 | 55 | checker_values_tally = checker_values.map(&:to_s).tally 56 | 57 | ['false', 'true', 'strict'].each do |possible_value| 58 | count = checker_values_tally.fetch(possible_value, 0) 59 | metric_name = "#{prefix}.packwerk_checkers.#{possible_value}.count" 60 | tags = package_tags + [checker.violation_type_tag] 61 | metrics << GaugeMetric.for(metric_name, count, tags) 62 | end 63 | end 64 | 65 | metrics 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/setup' 4 | require 'pack_stats' 5 | require 'pry' 6 | require 'packs/rspec/support' 7 | 8 | RSpec.configure do |config| 9 | # Enable flags like --only-failures and --next-failure 10 | config.example_status_persistence_file_path = '.rspec_status' 11 | 12 | # Disable RSpec exposing methods globally on `Module` and `main` 13 | config.disable_monkey_patching! 14 | 15 | config.expect_with :rspec do |c| 16 | c.syntax = :expect 17 | end 18 | end 19 | 20 | def sorbet_double(stubbed_class, attr_map = {}) 21 | instance_double(stubbed_class, attr_map).tap do |dbl| 22 | allow(dbl).to receive(:is_a?) { |tested_class| stubbed_class.ancestors.include?(tested_class) } 23 | end 24 | end 25 | 26 | RSpec.shared_context 'team names are based off of file names' do 27 | before do 28 | allow(CodeOwnership).to receive(:for_file) do |filename| 29 | match = filename.match(/_(\d+)/) 30 | if match 31 | team_number = match[1] 32 | sorbet_double(CodeTeams::Team, name: "Team #{team_number}") 33 | end 34 | end 35 | end 36 | end 37 | 38 | RSpec.shared_context 'only one team' do 39 | before do 40 | allow(CodeOwnership).to receive(:for_file).and_return(sorbet_double(CodeTeams::Team, name: 'Some team')) 41 | end 42 | end 43 | 44 | RSpec::Matchers.define(:include_metric) do |expected_metric| 45 | match do |actual_metrics| 46 | @actual_metrics = actual_metrics 47 | @expected_metric = expected_metric 48 | @metrics_with_same_name = actual_metrics.select { |actual_metric| actual_metric.name == expected_metric.name } 49 | @matching_metric = @metrics_with_same_name.find { |matching_metric| matching_metric.count == expected_metric.count && expected_metric.tags.sort_by(&:key) == matching_metric.tags.sort_by(&:key) } 50 | @metrics_with_same_name.any? && !@matching_metric.nil? 51 | end 52 | 53 | description do 54 | "to have a metric named `#{expected_metric.name}` with count of #{expected_metric.count} and tags of #{expected_metric.tags.map(&:to_s)}" 55 | end 56 | 57 | failure_message do 58 | if @metrics_with_same_name.none? 59 | "Could not find metric:\n\n#{expected_metric}\n\nCould only find metrics with names: \n\n#{@actual_metrics.sort_by(&:name).uniq.join("\n")}" 60 | else 61 | 62 | # We colorize each part of the output red (if there is no match) or green (if there is a partial match) 63 | colorized_metrics = @metrics_with_same_name.sort_by(&:name).map do |actual_metric| 64 | count_equal = actual_metric.count == expected_metric.count 65 | with_count = "with count #{actual_metric.count}" 66 | colorized_with_count = count_equal ? Rainbow(with_count).green : Rainbow(with_count).red 67 | 68 | tags = actual_metric.tags.sort_by(&:key).map do |tag| 69 | if expected_metric.tags.include?(tag) 70 | Rainbow(tag.to_s).green 71 | else 72 | Rainbow(tag.to_s).red 73 | end 74 | end 75 | 76 | "#{Rainbow(actual_metric.name).green} #{colorized_with_count}, with tags #{tags.join(', ')}" 77 | end 78 | 79 | "Could not find metric:\n\n#{expected_metric}\n\nCould only find metrics: \n\n#{colorized_metrics.join("\n")}" 80 | end 81 | end 82 | end 83 | 84 | def write_package_yml( 85 | name 86 | ) 87 | write_pack(name, { 88 | 'enforce_dependencies' => true, 89 | 'enforce_privacy' => true, 90 | }) 91 | end 92 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/code_teams@1.0.0.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `code_teams` gem. 5 | # Please instead update this file by running `bin/tapioca gem code_teams`. 6 | 7 | module CodeTeams 8 | class << self 9 | sig { returns(T::Array[::CodeTeams::Team]) } 10 | def all; end 11 | 12 | sig { void } 13 | def bust_caches!; end 14 | 15 | sig { params(name: ::String).returns(T.nilable(::CodeTeams::Team)) } 16 | def find(name); end 17 | 18 | sig { params(dir: ::String).returns(T::Array[::CodeTeams::Team]) } 19 | def for_directory(dir); end 20 | 21 | sig { params(string: ::String).returns(::String) } 22 | def tag_value_for(string); end 23 | 24 | sig { params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) } 25 | def validation_errors(teams); end 26 | end 27 | end 28 | 29 | class CodeTeams::IncorrectPublicApiUsageError < ::StandardError; end 30 | 31 | class CodeTeams::Plugin 32 | abstract! 33 | 34 | sig { params(team: ::CodeTeams::Team).void } 35 | def initialize(team); end 36 | 37 | class << self 38 | sig { returns(T::Array[T.class_of(CodeTeams::Plugin)]) } 39 | def all_plugins; end 40 | 41 | sig { params(team: ::CodeTeams::Team).returns(T.attached_class) } 42 | def for(team); end 43 | 44 | sig { params(base: T.untyped).void } 45 | def inherited(base); end 46 | 47 | sig { params(team: ::CodeTeams::Team, key: ::String).returns(::String) } 48 | def missing_key_error_message(team, key); end 49 | 50 | sig { params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) } 51 | def validation_errors(teams); end 52 | 53 | private 54 | 55 | sig { params(team: ::CodeTeams::Team).returns(T.attached_class) } 56 | def register_team(team); end 57 | 58 | sig { returns(T::Hash[T.nilable(::String), T::Hash[::Class, ::CodeTeams::Plugin]]) } 59 | def registry; end 60 | end 61 | end 62 | 63 | module CodeTeams::Plugins; end 64 | 65 | class CodeTeams::Plugins::Identity < ::CodeTeams::Plugin 66 | sig { returns(::CodeTeams::Plugins::Identity::IdentityStruct) } 67 | def identity; end 68 | 69 | class << self 70 | sig { override.params(teams: T::Array[::CodeTeams::Team]).returns(T::Array[::String]) } 71 | def validation_errors(teams); end 72 | end 73 | end 74 | 75 | class CodeTeams::Plugins::Identity::IdentityStruct < ::Struct 76 | def name; end 77 | def name=(_); end 78 | 79 | class << self 80 | def [](*_arg0); end 81 | def inspect; end 82 | def members; end 83 | def new(*_arg0); end 84 | end 85 | end 86 | 87 | class CodeTeams::Team 88 | sig { params(config_yml: T.nilable(::String), raw_hash: T::Hash[T.untyped, T.untyped]).void } 89 | def initialize(config_yml:, raw_hash:); end 90 | 91 | sig { params(other: ::Object).returns(T::Boolean) } 92 | def ==(other); end 93 | 94 | sig { returns(T.nilable(::String)) } 95 | def config_yml; end 96 | 97 | def eql?(*args, &blk); end 98 | 99 | sig { returns(::Integer) } 100 | def hash; end 101 | 102 | sig { returns(::String) } 103 | def name; end 104 | 105 | sig { returns(T::Hash[T.untyped, T.untyped]) } 106 | def raw_hash; end 107 | 108 | sig { returns(::String) } 109 | def to_tag; end 110 | 111 | class << self 112 | sig { params(raw_hash: T::Hash[T.untyped, T.untyped]).returns(::CodeTeams::Team) } 113 | def from_hash(raw_hash); end 114 | 115 | sig { params(config_yml: ::String).returns(::CodeTeams::Team) } 116 | def from_yml(config_yml); end 117 | end 118 | end 119 | 120 | CodeTeams::UNKNOWN_TEAM_STRING = T.let(T.unsafe(nil), String) 121 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/packs@0.0.5.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `packs` gem. 5 | # Please instead update this file by running `bin/tapioca gem packs`. 6 | 7 | # source://packs//lib/packs/pack.rb#3 8 | module Packs 9 | class << self 10 | # source://packs//lib/packs.rb#16 11 | sig { returns(T::Array[::Packs::Pack]) } 12 | def all; end 13 | 14 | # source://packs//lib/packs.rb#34 15 | sig { void } 16 | def bust_cache!; end 17 | 18 | # source://packs//lib/packs.rb#41 19 | sig { returns(::Packs::Private::Configuration) } 20 | def config; end 21 | 22 | # @yield [config] 23 | # 24 | # source://packs//lib/packs.rb#47 25 | sig { params(blk: T.proc.params(arg0: ::Packs::Private::Configuration).void).void } 26 | def configure(&blk); end 27 | 28 | # source://packs//lib/packs.rb#21 29 | sig { params(name: ::String).returns(T.nilable(::Packs::Pack)) } 30 | def find(name); end 31 | 32 | # source://packs//lib/packs.rb#26 33 | sig { params(file_path: T.any(::Pathname, ::String)).returns(T.nilable(::Packs::Pack)) } 34 | def for_file(file_path); end 35 | 36 | private 37 | 38 | # source://packs//lib/packs.rb#73 39 | sig { returns(T::Array[::Pathname]) } 40 | def package_glob_patterns; end 41 | 42 | # source://packs//lib/packs.rb#59 43 | sig { returns(T::Hash[::String, ::Packs::Pack]) } 44 | def packs_by_name; end 45 | end 46 | end 47 | 48 | # source://packs//lib/packs.rb#10 49 | Packs::PACKAGE_FILE = T.let(T.unsafe(nil), String) 50 | 51 | # source://packs//lib/packs/pack.rb#4 52 | class Packs::Pack < ::T::Struct 53 | const :name, ::String 54 | const :path, ::Pathname 55 | const :raw_hash, T::Hash[T.untyped, T.untyped] 56 | const :relative_path, ::Pathname 57 | 58 | # source://packs//lib/packs/pack.rb#34 59 | sig { returns(::String) } 60 | def last_name; end 61 | 62 | # source://packs//lib/packs/pack.rb#39 63 | sig { returns(T::Hash[T.untyped, T.untyped]) } 64 | def metadata; end 65 | 66 | # source://packs//lib/packs/pack.rb#28 67 | sig { params(relative: T::Boolean).returns(::Pathname) } 68 | def yml(relative: T.unsafe(nil)); end 69 | 70 | class << self 71 | # source://packs//lib/packs/pack.rb#13 72 | sig { params(package_yml_absolute_path: ::Pathname).returns(::Packs::Pack) } 73 | def from(package_yml_absolute_path); end 74 | 75 | # source://sorbet-runtime/0.5.9924/lib/types/struct.rb#13 76 | def inherited(s); end 77 | end 78 | end 79 | 80 | # source://packs//lib/packs/private/configuration.rb#4 81 | module Packs::Private 82 | class << self 83 | # source://packs//lib/packs/private.rb#10 84 | sig { returns(::Pathname) } 85 | def root; end 86 | end 87 | end 88 | 89 | # source://packs//lib/packs/private/configuration.rb#5 90 | class Packs::Private::Configuration < ::T::Struct 91 | prop :pack_paths, T::Array[::String] 92 | 93 | class << self 94 | # source://packs//lib/packs/private/configuration.rb#17 95 | sig { returns(::Packs::Private::Configuration) } 96 | def fetch; end 97 | 98 | # source://sorbet-runtime/0.5.9924/lib/types/struct.rb#13 99 | def inherited(s); end 100 | 101 | # source://packs//lib/packs/private/configuration.rb#26 102 | sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[::String]) } 103 | def pack_paths(config_hash); end 104 | end 105 | end 106 | 107 | # source://packs//lib/packs/private/configuration.rb#7 108 | Packs::Private::Configuration::CONFIGURATION_PATHNAME = T.let(T.unsafe(nil), Pathname) 109 | 110 | # source://packs//lib/packs/private/configuration.rb#9 111 | Packs::Private::Configuration::DEFAULT_PACK_PATHS = T.let(T.unsafe(nil), Array) 112 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | pack_stats (0.2.1) 5 | code_ownership 6 | code_teams 7 | dogapi 8 | packs 9 | parse_packwerk 10 | rubocop-packs 11 | sorbet-runtime 12 | 13 | GEM 14 | remote: https://rubygems.org/ 15 | specs: 16 | activesupport (7.0.4.3) 17 | concurrent-ruby (~> 1.0, >= 1.0.2) 18 | i18n (>= 1.6, < 2) 19 | minitest (>= 5.1) 20 | tzinfo (~> 2.0) 21 | ast (2.4.2) 22 | base64 (0.2.0) 23 | code_ownership (1.32.17) 24 | code_teams (~> 1.0) 25 | packs 26 | sorbet-runtime 27 | code_teams (1.0.1) 28 | sorbet-runtime 29 | coderay (1.1.3) 30 | concurrent-ruby (1.2.2) 31 | diff-lcs (1.5.0) 32 | dogapi (1.45.0) 33 | multi_json 34 | i18n (1.13.0) 35 | concurrent-ruby (~> 1.0) 36 | json (2.6.3) 37 | method_source (1.0.0) 38 | minitest (5.18.0) 39 | multi_json (1.15.0) 40 | packs (0.0.6) 41 | sorbet-runtime 42 | parallel (1.23.0) 43 | parse_packwerk (0.19.1) 44 | sorbet-runtime 45 | parser (3.2.2.3) 46 | ast (~> 2.4.1) 47 | racc 48 | pry (0.14.2) 49 | coderay (~> 1.1) 50 | method_source (~> 1.0) 51 | racc (1.7.1) 52 | rainbow (3.1.1) 53 | rake (13.0.6) 54 | rbi (0.0.17) 55 | ast 56 | parser (>= 3.0.0) 57 | sorbet-runtime (>= 0.5.9204) 58 | unparser (>= 0.5.6) 59 | regexp_parser (2.8.0) 60 | rexml (3.2.5) 61 | rspec (3.12.0) 62 | rspec-core (~> 3.12.0) 63 | rspec-expectations (~> 3.12.0) 64 | rspec-mocks (~> 3.12.0) 65 | rspec-core (3.12.1) 66 | rspec-support (~> 3.12.0) 67 | rspec-expectations (3.12.2) 68 | diff-lcs (>= 1.2.0, < 2.0) 69 | rspec-support (~> 3.12.0) 70 | rspec-mocks (3.12.5) 71 | diff-lcs (>= 1.2.0, < 2.0) 72 | rspec-support (~> 3.12.0) 73 | rspec-support (3.12.0) 74 | rubocop (1.50.2) 75 | json (~> 2.3) 76 | parallel (~> 1.10) 77 | parser (>= 3.2.0.0) 78 | rainbow (>= 2.2.2, < 4.0) 79 | regexp_parser (>= 1.8, < 3.0) 80 | rexml (>= 3.2.5, < 4.0) 81 | rubocop-ast (>= 1.28.0, < 2.0) 82 | ruby-progressbar (~> 1.7) 83 | unicode-display_width (>= 2.4.0, < 3.0) 84 | rubocop-ast (1.28.0) 85 | parser (>= 3.2.1.0) 86 | rubocop-packs (0.0.41) 87 | activesupport 88 | packs 89 | parse_packwerk 90 | rubocop 91 | rubocop-sorbet 92 | sorbet-runtime 93 | rubocop-sorbet (0.7.0) 94 | rubocop (>= 0.90.0) 95 | ruby-progressbar (1.13.0) 96 | sorbet (0.5.11370) 97 | sorbet-static (= 0.5.11370) 98 | sorbet-runtime (0.5.10796) 99 | sorbet-static (0.5.11370-universal-darwin) 100 | sorbet-static (0.5.11370-x86_64-linux) 101 | spoom (1.1.16) 102 | sorbet (>= 0.5.10187) 103 | sorbet-runtime (>= 0.5.9204) 104 | thor (>= 0.19.2) 105 | tapioca (0.7.3) 106 | bundler (>= 1.17.3) 107 | pry (>= 0.12.2) 108 | rbi (~> 0.0.0, >= 0.0.14) 109 | sorbet-runtime (>= 0.5.9204) 110 | sorbet-static (>= 0.5.9204) 111 | spoom (~> 1.1.0, >= 1.1.11) 112 | thor (>= 1.2.0) 113 | yard-sorbet 114 | thor (1.3.1) 115 | tzinfo (2.0.6) 116 | concurrent-ruby (~> 1.0) 117 | unicode-display_width (2.4.2) 118 | unparser (0.6.8) 119 | diff-lcs (~> 1.3) 120 | parser (>= 3.2.0) 121 | yard (0.9.36) 122 | yard-sorbet (0.8.1) 123 | sorbet-runtime (>= 0.5) 124 | yard (>= 0.9) 125 | 126 | PLATFORMS 127 | universal-darwin 128 | x86_64-linux 129 | 130 | DEPENDENCIES 131 | base64 132 | pack_stats! 133 | pry 134 | rake 135 | rspec (~> 3.0) 136 | sorbet 137 | tapioca 138 | 139 | BUNDLED WITH 140 | 2.4.12 141 | -------------------------------------------------------------------------------- /lib/pack_stats/private/metrics/packages_by_team.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | module PackStats 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 | 20 | 21 | all_packages.group_by { |package| Private.package_owner(package) }.each do |team_name, packages_for_team| 22 | team_tags = Metrics.tags_for_team(team_name) + [app_level_tag] 23 | all_metrics << GaugeMetric.for('by_team.all_packages.count', packages_for_team.count, team_tags) 24 | all_metrics += Metrics::PackwerkCheckerUsage.get_checker_metrics('by_team', packages_for_team, team_tags) 25 | all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_team', packages_for_team, team_tags) 26 | all_metrics << GaugeMetric.for('by_team.has_readme.count', packages_for_team.count { |package| Metrics.has_readme?(package) }, team_tags) 27 | 28 | outbound_violations = packages_for_team.flat_map(&:violations) 29 | # We look at `all_packages` because we care about ALL inbound violations across all teams 30 | inbound_violations_by_package = all_packages.flat_map(&:violations).group_by(&:to_package_name) 31 | # Here we only look at packages_for_team because we only care about inbound violations onto packages for this team 32 | inbound_violations = packages_for_team.flat_map { |package| inbound_violations_by_package[package.name] || [] } 33 | 34 | PackwerkCheckerUsage::CHECKERS.each do |checker| 35 | direction = checker.direction 36 | case direction 37 | when PackwerkCheckerUsage::Direction::Outbound 38 | all_violations_of_type = outbound_violations.select { |v| v.type == checker.violation_type } 39 | 40 | violation_count = packages_for_team.sum { |package| Metrics.file_count(package.violations.select{|v| v.type == checker.violation_type}) } 41 | tags = team_tags + [checker.violation_type_tag] 42 | all_metrics << GaugeMetric.for("by_team.violations.count", violation_count, tags) 43 | 44 | all_packages.group_by { |package| Private.package_owner(package) }.each do |other_team_name, other_teams_packages| 45 | violations = outbound_violations.select{|v| other_teams_packages.map(&:name).include?(v.to_package_name) && v.type == checker.violation_type} 46 | tags = team_tags + Metrics.tags_for_other_team(other_team_name) + [checker.violation_type_tag] 47 | count = Metrics.file_count(violations) 48 | if count > 0 49 | all_metrics << GaugeMetric.for("by_team.violations.by_other_team.count", count, tags) 50 | end 51 | end 52 | when PackwerkCheckerUsage::Direction::Inbound 53 | all_violations_of_type = inbound_violations.select { |v| v.type == checker.violation_type } 54 | 55 | violation_count = packages_for_team.sum { |package| Metrics.file_count(package.violations.select{|v| v.type == checker.violation_type}) } 56 | tags = team_tags + [checker.violation_type_tag] 57 | all_metrics << GaugeMetric.for("by_team.violations.count", violation_count, tags) 58 | 59 | all_packages.group_by { |package| Private.package_owner(package) }.each do |other_team_name, other_teams_packages| 60 | violations = other_teams_packages.flat_map(&:violations).select{|v| packages_for_team.map(&:name).include?(v.to_package_name) && v.type == checker.violation_type} 61 | tags = team_tags + Metrics.tags_for_other_team(other_team_name) + [checker.violation_type_tag] 62 | count = Metrics.file_count(violations) 63 | if count > 0 64 | all_metrics << GaugeMetric.for("by_team.violations.by_other_team.count", count, tags) 65 | end 66 | end 67 | else 68 | T.absurd(direction) 69 | end 70 | end 71 | end 72 | 73 | all_metrics 74 | end 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PackStats 2 | 3 | This gem is used to report opinionated statistics about modularization to DataDog and other observability systems. 4 | 5 | # Configuring Packs 6 | This gem assumes you've correctly configured the [`packs`](https://github.com/rubyatscale/packs#configuration) gem so that `pack_stats` knows where to find your code's packs. 7 | 8 | # Configuring Ownership 9 | The gem reports metrics per-team, where each team is configured based on metadata included in Packwerk package.yml files. 10 | 11 | Define your teams as described in the [Code Team - Package Based Ownership](https://github.com/rubyatscale/code_ownership#package-based-ownership) documentation. 12 | 13 | # Usage 14 | The main method to this gem is `PackStats#report_to_datadog!`. Refer to the Sorbet signature for this method for the exact types to be passed in. 15 | 16 | This is an example of how to use this API: 17 | 18 | ```ruby 19 | PackStats.report_to_datadog!( 20 | # 21 | # A properly initialized `Dogapi::Client` 22 | # Example: Dogapi::Client.new(ENV.fetch('DATADOG_API_KEY') 23 | # 24 | datadog_client: datadog_client, 25 | # 26 | # Time attached to the metrics 27 | # Example: Time.now 28 | # 29 | report_time: report_time 30 | # 31 | # This is used to determine what files to look at for building statistics about what types of files are packaged, componentized, or unpackaged. 32 | # This is an array of `Pathname`. `Pathname` can be relative or absolute paths. 33 | # 34 | # Example: source_code_pathnames = Pathname.glob('./**/**.rb') 35 | # 36 | source_code_pathnames: source_code_pathnames, 37 | # 38 | # A file is determined to be componentized if it exists in any of these directories. 39 | # This is an array of `Pathname`. `Pathname` can be relative or absolute paths. 40 | # 41 | # Example: [Pathname.new("./gems")] 42 | # 43 | componentized_source_code_locations: componentized_source_code_locations, 44 | ) 45 | ``` 46 | 47 | It's recommended to run this in CI on the main/development branch so each new commit has metrics emitted for it. 48 | 49 | ```ruby 50 | require 'pack_stats' 51 | 52 | def report(verbose:, max_enforcements: false) 53 | ignored_paths = Pathname.glob('spec/fixtures/**/**') 54 | source_code_pathnames = Pathname.glob('{app,components,lib,packs,spec}/**/**').select(&:file?) - ignored_paths 55 | 56 | PackStats.report_to_datadog!( 57 | datadog_client: Dogapi::Client.new(ENV.fetch('DATADOG_API_KEY')), 58 | app_name: Rails.application.class.module_parent_name, 59 | source_code_pathnames: source_code_pathnames, 60 | verbose: verbose, 61 | max_enforcements: max_enforcements 62 | ) 63 | end 64 | 65 | namespace(:pack_stats) do 66 | desc( 67 | 'Publish pack_stats to datadog. ' \ 68 | 'Example: bin/rails "pack_stats:upload"' 69 | ) 70 | task(:upload, [:verbose] => :environment) do |_, args| 71 | verbose = args[:verbose] == 'true' || false 72 | 73 | # First send without any changes, tagging metrics with max_enforcements:false 74 | report(verbose: verbose, max_enforcements: false) 75 | 76 | # At Gusto, it's useful to be able to view the dashboard as if all enforce_x were set to true. 77 | # To do this, we rewrite all `package.yml` files with `enforce_dependencies` and `enforce_privacy` 78 | # set to true, then bin/packwerk update-todo. 79 | old_packages = ParsePackwerk.all 80 | old_packages.each do |package| 81 | new_package = package.with(enforce_dependencies: true, enforce_privacy: true) 82 | ParsePackwerk.write_package_yml!(new_package) 83 | end 84 | 85 | Packwerk::Cli.new.execute_command(['update-todo']) 86 | 87 | # Now we reset it back so that the protection values are the same as the native packwerk configuration 88 | old_packages.each do |package| 89 | ParsePackwerk.write_package_yml!(package) 90 | end 91 | 92 | # Then send after maxing out enforcements, tagging metrics with max_enforcements:true 93 | report(verbose: verbose, max_enforcements: true) 94 | end 95 | end 96 | ``` 97 | 98 | # Using Other Observability Tools 99 | 100 | Right now this tool sends metrics to DataDog only. However, if you want to use this with other tools, you can call `PackStats.get_metrics(...)` to get generic metrics that you can then send to whatever observability provider you use. 101 | 102 | # Setting Up Your Dashboards 103 | 104 | We recommend the use of the dashboard that is shipped with this gem. You can create a new dashboard and then click "import dashboard JSON" to get a jump start on tracking your metrics. Note you may want to make some tweaks to these dashboards to better fit your organization's circumstances and goals. 105 | 106 | - [Dashboard JSON](docs/dashboard.json) 107 | - [Screenshots](docs/screenshots.md) 108 | -------------------------------------------------------------------------------- /lib/pack_stats/private/metrics/packages.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | # frozen_string_literal: true 3 | 4 | module PackStats 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 | 21 | all_metrics << GaugeMetric.for('all_packages.count', packages.count, package_tags) 22 | all_metrics << GaugeMetric.for('all_packages.dependencies.count', packages.sum { |package| package.dependencies.count }, package_tags) 23 | 24 | PackwerkCheckerUsage::CHECKERS.each do |checker| 25 | violation_count = packages.sum { |package| Metrics.file_count(package.violations.select{|v| v.type == checker.violation_type}) } 26 | tags = package_tags + [checker.violation_type_tag] 27 | all_metrics << GaugeMetric.for("all_packages.violations.count", violation_count, tags) 28 | end 29 | 30 | all_metrics += Metrics::PublicUsage.get_public_usage_metrics('all_packages', packages, package_tags) 31 | all_metrics << GaugeMetric.for('all_packages.has_readme.count', packages.count { |package| Metrics.has_readme?(package) }, package_tags) 32 | 33 | all_metrics += Metrics::PackwerkCheckerUsage.get_checker_metrics('all_packages', packages, package_tags) 34 | all_metrics << GaugeMetric.for('all_packages.package_based_file_ownership.count', packages.count { |package| !package.metadata['owner'].nil? }, package_tags) 35 | 36 | inbound_violations_by_package = packages.flat_map(&:violations).group_by(&:to_package_name) 37 | 38 | packages.each do |package| 39 | package_tags = Metrics.tags_for_package(package, app_name) 40 | all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_package', [package], package_tags) 41 | 42 | outbound_violations = package.violations 43 | inbound_violations = inbound_violations_by_package[package.name] || [] 44 | 45 | PackwerkCheckerUsage::CHECKERS.each do |checker| 46 | direction = checker.direction 47 | 48 | case direction 49 | when PackwerkCheckerUsage::Direction::Outbound 50 | all_violations_of_type = outbound_violations.select { |v| v.type == checker.violation_type } 51 | 52 | packages.each do |other_package| 53 | violations = package.violations.select{|v| v.to_package_name == other_package.name && v.type == checker.violation_type } 54 | 55 | tags = package_tags + [ 56 | Tag.for('other_package', Metrics.humanized_package_name(other_package.name)), 57 | *Metrics.tags_for_other_team(Private.package_owner(other_package)), 58 | checker.violation_type_tag 59 | ] 60 | 61 | count = Metrics.file_count(violations) 62 | if count > 0 63 | all_metrics << GaugeMetric.for("by_package.violations.by_other_package.count", Metrics.file_count(violations), tags) 64 | end 65 | end 66 | when PackwerkCheckerUsage::Direction::Inbound 67 | all_violations_of_type = inbound_violations.select { |v| v.type == checker.violation_type } 68 | 69 | packages.each do |other_package| 70 | violations = other_package.violations.select{|v| v.to_package_name == package.name && v.type == checker.violation_type } 71 | tags = package_tags + [ 72 | Tag.for('other_package', Metrics.humanized_package_name(other_package.name)), 73 | *Metrics.tags_for_other_team(Private.package_owner(other_package)), 74 | checker.violation_type_tag 75 | ] 76 | 77 | count = Metrics.file_count(violations) 78 | if count > 0 79 | all_metrics << GaugeMetric.for("by_package.violations.by_other_package.count", count, tags) 80 | end 81 | end 82 | else 83 | T.absurd(direction) 84 | end 85 | 86 | tags = package_tags + [checker.violation_type_tag] 87 | all_metrics << GaugeMetric.for("by_package.violations.count", Metrics.file_count(all_violations_of_type), tags) 88 | end 89 | end 90 | 91 | all_metrics += Metrics::Dependencies.get_metrics('by_package', packages, app_name) 92 | 93 | all_metrics 94 | end 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /lib/pack_stats.rb: -------------------------------------------------------------------------------- 1 | # typed: strict 2 | 3 | require 'sorbet-runtime' 4 | require 'json' 5 | require 'yaml' 6 | require 'benchmark' 7 | require 'code_teams' 8 | require 'code_ownership' 9 | require 'pathname' 10 | require 'packs' 11 | require 'pack_stats/private' 12 | require 'pack_stats/private/source_code_file' 13 | require 'pack_stats/private/datadog_reporter' 14 | require 'parse_packwerk' 15 | require 'pack_stats/tag' 16 | require 'pack_stats/tags' 17 | require 'pack_stats/gauge_metric' 18 | 19 | module PackStats 20 | extend T::Sig 21 | 22 | ROOT_PACKAGE_NAME = T.let('root'.freeze, String) 23 | 24 | DEFAULT_COMPONENTIZED_SOURCE_CODE_LOCATIONS = T.let( 25 | [ 26 | Pathname.new('components'), 27 | Pathname.new('gems'), 28 | ].freeze, T::Array[Pathname] 29 | ) 30 | 31 | sig do 32 | params( 33 | datadog_client: Dogapi::Client, 34 | app_name: String, 35 | source_code_pathnames: T::Array[Pathname], 36 | componentized_source_code_locations: T::Array[Pathname], 37 | report_time: Time, 38 | verbose: T::Boolean, 39 | # See note on get_metrics 40 | packaged_source_code_locations: T.nilable(T::Array[Pathname]), 41 | # See note on get_metrics 42 | max_enforcements_tag_value: T::Boolean 43 | ).void 44 | end 45 | def self.report_to_datadog!( 46 | datadog_client:, 47 | app_name:, 48 | source_code_pathnames:, 49 | componentized_source_code_locations: DEFAULT_COMPONENTIZED_SOURCE_CODE_LOCATIONS, 50 | report_time: Time.now, # rubocop:disable Rails/TimeZone 51 | verbose: false, 52 | packaged_source_code_locations: [], 53 | max_enforcements_tag_value: false 54 | ) 55 | 56 | all_metrics = self.get_metrics( 57 | source_code_pathnames: source_code_pathnames, 58 | componentized_source_code_locations: componentized_source_code_locations, 59 | app_name: app_name, 60 | max_enforcements_tag_value: max_enforcements_tag_value, 61 | ) 62 | 63 | # This helps us debug what metrics are being sent 64 | if verbose 65 | all_metrics.each do |metric| 66 | puts "Sending metric: #{metric}" 67 | end 68 | end 69 | 70 | Private::DatadogReporter.report!( 71 | datadog_client: datadog_client, 72 | report_time: report_time, 73 | metrics: all_metrics 74 | ) 75 | end 76 | 77 | sig do 78 | params( 79 | source_code_pathnames: T::Array[Pathname], 80 | componentized_source_code_locations: T::Array[Pathname], 81 | app_name: String, 82 | # This field is deprecated 83 | packaged_source_code_locations: T.nilable(T::Array[Pathname]), 84 | # You can set this to `true` to tag all metrics with `max_enforcements:true`. 85 | # This is useful if you want to submit two sets of metrics: 86 | # Once with the violation counts as configured in the app 87 | # Another time with the violation counts after turning on all enforcements and running `bin/packwerk update`. 88 | max_enforcements_tag_value: T::Boolean 89 | ).returns(T::Array[GaugeMetric]) 90 | end 91 | def self.get_metrics( 92 | source_code_pathnames:, 93 | componentized_source_code_locations:, 94 | app_name:, 95 | packaged_source_code_locations: [], 96 | max_enforcements_tag_value: false 97 | ) 98 | 99 | GaugeMetric.set_max_enforcements_tag(max_enforcements_tag_value) 100 | 101 | Private::DatadogReporter.get_metrics( 102 | source_code_files: source_code_files( 103 | source_code_pathnames: source_code_pathnames, 104 | componentized_source_code_locations: componentized_source_code_locations, 105 | ), 106 | app_name: app_name 107 | ) 108 | end 109 | 110 | sig do 111 | params( 112 | source_code_pathnames: T::Array[Pathname], 113 | componentized_source_code_locations: T::Array[Pathname], 114 | ).returns(T::Array[Private::SourceCodeFile]) 115 | end 116 | def self.source_code_files( 117 | source_code_pathnames:, 118 | componentized_source_code_locations: 119 | ) 120 | 121 | # Sorbet has the wrong signatures for `Pathname#find`, whoops! 122 | componentized_file_set = Set.new(componentized_source_code_locations.select(&:exist?).flat_map { |pathname| T.unsafe(pathname).find.to_a }) 123 | 124 | packaged_file_set = Packs.all.flat_map do |pack| 125 | pack.relative_path.find.to_a 126 | end 127 | 128 | packaged_file_set = Set.new(packaged_file_set) 129 | 130 | source_code_pathnames.map do |pathname| 131 | componentized_file = componentized_file_set.include?(pathname) 132 | packaged_file = packaged_file_set.include?(pathname) 133 | 134 | Private::SourceCodeFile.new( 135 | pathname: pathname, 136 | team_owner: CodeOwnership.for_file(pathname.to_s), 137 | is_componentized_file: componentized_file, 138 | is_packaged_file: packaged_file 139 | ) 140 | end 141 | end 142 | 143 | private_class_method :source_code_files 144 | end 145 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at rubyatscale@gusto.com. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 125 | [https://www.contributor-covenant.org/translations][translations]. 126 | 127 | [homepage]: https://www.contributor-covenant.org 128 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 129 | [Mozilla CoC]: https://github.com/mozilla/diversity 130 | [FAQ]: https://www.contributor-covenant.org/faq 131 | [translations]: https://www.contributor-covenant.org/translations 132 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/parse_packwerk@0.19.1.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `parse_packwerk` gem. 5 | # Please instead update this file by running `bin/tapioca gem parse_packwerk`. 6 | 7 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#3 8 | module ParsePackwerk 9 | class << self 10 | # source://parse_packwerk//lib/parse_packwerk.rb#32 11 | sig { returns(T::Array[::ParsePackwerk::Package]) } 12 | def all; end 13 | 14 | # source://parse_packwerk//lib/parse_packwerk.rb#117 15 | sig { void } 16 | def bust_cache!; end 17 | 18 | # source://parse_packwerk//lib/parse_packwerk.rb#37 19 | sig { params(name: ::String).returns(T.nilable(::ParsePackwerk::Package)) } 20 | def find(name); end 21 | 22 | # source://parse_packwerk//lib/parse_packwerk.rb#47 23 | sig { params(file_path: T.any(::Pathname, ::String)).returns(::ParsePackwerk::Package) } 24 | def package_from_path(file_path); end 25 | 26 | # source://parse_packwerk//lib/parse_packwerk.rb#58 27 | sig { params(package: ::ParsePackwerk::Package).void } 28 | def write_package_yml!(package); end 29 | 30 | # source://parse_packwerk//lib/parse_packwerk.rb#42 31 | sig { returns(::ParsePackwerk::Configuration) } 32 | def yml; end 33 | 34 | private 35 | 36 | # We memoize packages_by_name for fast lookup. 37 | # Since Graph is an immutable value object, we can create indexes and general caching mechanisms safely. 38 | # 39 | # source://parse_packwerk//lib/parse_packwerk.rb#105 40 | sig { returns(T::Hash[::String, ::ParsePackwerk::Package]) } 41 | def packages_by_name; end 42 | end 43 | end 44 | 45 | # source://parse_packwerk//lib/parse_packwerk/configuration.rb#4 46 | class ParsePackwerk::Configuration < ::T::Struct 47 | const :exclude, T::Array[::String] 48 | const :package_paths, T::Array[::String] 49 | const :requires, T::Array[::String] 50 | 51 | class << self 52 | # source://parse_packwerk//lib/parse_packwerk/configuration.rb#30 53 | sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[::String]) } 54 | def excludes(config_hash); end 55 | 56 | # source://parse_packwerk//lib/parse_packwerk/configuration.rb#12 57 | sig { returns(::ParsePackwerk::Configuration) } 58 | def fetch; end 59 | 60 | # source://sorbet-runtime/0.5.10796/lib/types/struct.rb#13 61 | def inherited(s); end 62 | 63 | # source://parse_packwerk//lib/parse_packwerk/configuration.rb#42 64 | sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[::String]) } 65 | def package_paths(config_hash); end 66 | end 67 | end 68 | 69 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#22 70 | ParsePackwerk::DEFAULT_EXCLUDE_GLOBS = T.let(T.unsafe(nil), Array) 71 | 72 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#23 73 | ParsePackwerk::DEFAULT_PACKAGE_PATHS = T.let(T.unsafe(nil), Array) 74 | 75 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#24 76 | ParsePackwerk::DEFAULT_PUBLIC_PATH = T.let(T.unsafe(nil), String) 77 | 78 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#14 79 | ParsePackwerk::DEPENDENCIES = T.let(T.unsafe(nil), String) 80 | 81 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#10 82 | ParsePackwerk::DEPENDENCY_VIOLATION_TYPE = T.let(T.unsafe(nil), String) 83 | 84 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#8 85 | ParsePackwerk::ENFORCE_DEPENDENCIES = T.let(T.unsafe(nil), String) 86 | 87 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#9 88 | ParsePackwerk::ENFORCE_PRIVACY = T.let(T.unsafe(nil), String) 89 | 90 | # source://parse_packwerk//lib/parse_packwerk/extensions.rb#4 91 | module ParsePackwerk::Extensions 92 | class << self 93 | # source://parse_packwerk//lib/parse_packwerk/extensions.rb#8 94 | sig { returns(T::Boolean) } 95 | def all_extensions_installed?; end 96 | 97 | # source://parse_packwerk//lib/parse_packwerk/extensions.rb#13 98 | sig { returns(T::Boolean) } 99 | def privacy_extension_installed?; end 100 | end 101 | end 102 | 103 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#13 104 | ParsePackwerk::METADATA = T.let(T.unsafe(nil), String) 105 | 106 | # Since this metadata is unstructured YAML, it could be any type. We leave it to clients of `ParsePackwerk::Package` 107 | # to add types based on their known usage of metadata. 108 | # 109 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#18 110 | ParsePackwerk::MetadataYmlType = T.type_alias { T::Hash[T.untyped, T.untyped] } 111 | 112 | # source://parse_packwerk//lib/parse_packwerk.rb#18 113 | class ParsePackwerk::MissingConfiguration < ::StandardError 114 | # source://parse_packwerk//lib/parse_packwerk.rb#22 115 | sig { params(packwerk_file_name: ::Pathname).void } 116 | def initialize(packwerk_file_name); end 117 | end 118 | 119 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#7 120 | ParsePackwerk::PACKAGE_TODO_YML_NAME = T.let(T.unsafe(nil), String) 121 | 122 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#5 123 | ParsePackwerk::PACKAGE_YML_NAME = T.let(T.unsafe(nil), String) 124 | 125 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#6 126 | ParsePackwerk::PACKWERK_YML_NAME = T.let(T.unsafe(nil), String) 127 | 128 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#11 129 | ParsePackwerk::PRIVACY_VIOLATION_TYPE = T.let(T.unsafe(nil), String) 130 | 131 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#12 132 | ParsePackwerk::PUBLIC_PATH = T.let(T.unsafe(nil), String) 133 | 134 | # source://parse_packwerk//lib/parse_packwerk/package.rb#4 135 | class ParsePackwerk::Package < ::T::Struct 136 | const :name, ::String 137 | const :enforce_dependencies, T.any(::String, T::Boolean) 138 | const :enforce_privacy, T.any(::String, T::Boolean), default: T.unsafe(nil) 139 | const :public_path, ::String, default: T.unsafe(nil) 140 | const :metadata, T::Hash[T.untyped, T.untyped] 141 | const :dependencies, T::Array[::String] 142 | const :config, T::Hash[T.untyped, T.untyped] 143 | 144 | # source://parse_packwerk//lib/parse_packwerk/package.rb#41 145 | sig { returns(::Pathname) } 146 | def directory; end 147 | 148 | # source://parse_packwerk//lib/parse_packwerk/package.rb#51 149 | sig { returns(T.any(::String, T::Boolean)) } 150 | def enforces_dependencies?; end 151 | 152 | # source://parse_packwerk//lib/parse_packwerk/package.rb#56 153 | sig { returns(T.any(::String, T::Boolean)) } 154 | def enforces_privacy?; end 155 | 156 | # source://parse_packwerk//lib/parse_packwerk/package.rb#46 157 | sig { returns(::Pathname) } 158 | def public_directory; end 159 | 160 | # source://parse_packwerk//lib/parse_packwerk/package.rb#61 161 | sig { returns(T::Array[::ParsePackwerk::Violation]) } 162 | def violations; end 163 | 164 | # source://parse_packwerk//lib/parse_packwerk/package.rb#36 165 | sig { returns(::Pathname) } 166 | def yml; end 167 | 168 | class << self 169 | # source://parse_packwerk//lib/parse_packwerk/package.rb#16 170 | sig { params(pathname: ::Pathname).returns(::ParsePackwerk::Package) } 171 | def from(pathname); end 172 | 173 | # source://sorbet-runtime/0.5.10796/lib/types/struct.rb#13 174 | def inherited(s); end 175 | end 176 | end 177 | 178 | # source://parse_packwerk//lib/parse_packwerk.rb#15 179 | class ParsePackwerk::PackageParseError < ::StandardError; end 180 | 181 | # source://parse_packwerk//lib/parse_packwerk/package_set.rb#8 182 | class ParsePackwerk::PackageSet 183 | class << self 184 | # source://parse_packwerk//lib/parse_packwerk/package_set.rb#12 185 | sig do 186 | params( 187 | package_pathspec: T::Array[::String], 188 | exclude_pathspec: T::Array[::String] 189 | ).returns(T::Array[::ParsePackwerk::Package]) 190 | end 191 | def from(package_pathspec:, exclude_pathspec:); end 192 | 193 | private 194 | 195 | # source://parse_packwerk//lib/parse_packwerk/package_set.rb#28 196 | sig { params(globs: T::Array[::String], path: ::Pathname).returns(T::Boolean) } 197 | def exclude_path?(globs, path); end 198 | end 199 | end 200 | 201 | # source://parse_packwerk//lib/parse_packwerk/package_todo.rb#4 202 | class ParsePackwerk::PackageTodo < ::T::Struct 203 | const :pathname, ::Pathname 204 | const :violations, T::Array[::ParsePackwerk::Violation] 205 | 206 | class << self 207 | # source://parse_packwerk//lib/parse_packwerk/package_todo.rb#11 208 | sig { params(package: ::ParsePackwerk::Package).returns(::ParsePackwerk::PackageTodo) } 209 | def for(package); end 210 | 211 | # source://parse_packwerk//lib/parse_packwerk/package_todo.rb#17 212 | sig { params(pathname: ::Pathname).returns(::ParsePackwerk::PackageTodo) } 213 | def from(pathname); end 214 | 215 | # source://sorbet-runtime/0.5.10796/lib/types/struct.rb#13 216 | def inherited(s); end 217 | end 218 | end 219 | 220 | # source://parse_packwerk//lib/parse_packwerk/constants.rb#4 221 | ParsePackwerk::ROOT_PACKAGE_NAME = T.let(T.unsafe(nil), String) 222 | 223 | # source://parse_packwerk//lib/parse_packwerk/violation.rb#4 224 | class ParsePackwerk::Violation < ::T::Struct 225 | const :type, ::String 226 | const :to_package_name, ::String 227 | const :class_name, ::String 228 | const :files, T::Array[::String] 229 | 230 | # source://parse_packwerk//lib/parse_packwerk/violation.rb#13 231 | sig { returns(T::Boolean) } 232 | def dependency?; end 233 | 234 | # source://parse_packwerk//lib/parse_packwerk/violation.rb#18 235 | sig { returns(T::Boolean) } 236 | def privacy?; end 237 | 238 | class << self 239 | # source://sorbet-runtime/0.5.10796/lib/types/struct.rb#13 240 | def inherited(s); end 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/ast@2.4.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `ast` gem. 5 | # Please instead update this file by running `bin/tapioca gem ast`. 6 | 7 | # {AST} is a library for manipulating abstract syntax trees. 8 | # 9 | # It embraces immutability; each AST node is inherently frozen at 10 | # creation, and updating a child node requires recreating that node 11 | # and its every parent, recursively. 12 | # This is a design choice. It does create some pressure on 13 | # garbage collector, but completely eliminates all concurrency 14 | # and aliasing problems. 15 | # 16 | # See also {AST::Node}, {AST::Processor::Mixin} and {AST::Sexp} for 17 | # additional recommendations and design patterns. 18 | # 19 | # source://ast//lib/ast.rb#13 20 | module AST; end 21 | 22 | # Node is an immutable class, instances of which represent abstract 23 | # syntax tree nodes. It combines semantic information (i.e. anything 24 | # that affects the algorithmic properties of a program) with 25 | # meta-information (line numbers or compiler intermediates). 26 | # 27 | # Notes on inheritance 28 | # ==================== 29 | # 30 | # The distinction between semantics and metadata is important. Complete 31 | # semantic information should be contained within just the {#type} and 32 | # {#children} of a Node instance; in other words, if an AST was to be 33 | # stripped of all meta-information, it should remain a valid AST which 34 | # could be successfully processed to yield a result with the same 35 | # algorithmic properties. 36 | # 37 | # Thus, Node should never be inherited in order to define methods which 38 | # affect or return semantic information, such as getters for `class_name`, 39 | # `superclass` and `body` in the case of a hypothetical `ClassNode`. The 40 | # correct solution is to use a generic Node with a {#type} of `:class` 41 | # and three children. See also {Processor} for tips on working with such 42 | # ASTs. 43 | # 44 | # On the other hand, Node can and should be inherited to define 45 | # application-specific metadata (see also {#initialize}) or customize the 46 | # printing format. It is expected that an application would have one or two 47 | # such classes and use them across the entire codebase. 48 | # 49 | # The rationale for this pattern is extensibility and maintainability. 50 | # Unlike static ones, dynamic languages do not require the presence of a 51 | # predefined, rigid structure, nor does it improve dispatch efficiency, 52 | # and while such a structure can certainly be defined, it does not add 53 | # any value but incurs a maintaining cost. 54 | # For example, extending the AST even with a transformation-local 55 | # temporary node type requires making globally visible changes to 56 | # the codebase. 57 | # 58 | # source://ast//lib/ast/node.rb#40 59 | class AST::Node 60 | # Constructs a new instance of Node. 61 | # 62 | # The arguments `type` and `children` are converted with `to_sym` and 63 | # `to_a` respectively. Additionally, the result of converting `children` 64 | # is frozen. While mutating the arguments is generally considered harmful, 65 | # the most common case is to pass an array literal to the constructor. If 66 | # your code does not expect the argument to be frozen, use `#dup`. 67 | # 68 | # The `properties` hash is passed to {#assign_properties}. 69 | # 70 | # @return [Node] a new instance of Node 71 | # 72 | # source://ast//lib/ast/node.rb#72 73 | def initialize(type, children = T.unsafe(nil), properties = T.unsafe(nil)); end 74 | 75 | # Concatenates `array` with `children` and returns the resulting node. 76 | # 77 | # @return [AST::Node] 78 | # 79 | # source://ast//lib/ast/node.rb#168 80 | def +(array); end 81 | 82 | # Appends `element` to `children` and returns the resulting node. 83 | # 84 | # @return [AST::Node] 85 | # 86 | # source://ast//lib/ast/node.rb#177 87 | def <<(element); end 88 | 89 | # Compares `self` to `other`, possibly converting with `to_ast`. Only 90 | # `type` and `children` are compared; metadata is deliberately ignored. 91 | # 92 | # @return [Boolean] 93 | # 94 | # source://ast//lib/ast/node.rb#153 95 | def ==(other); end 96 | 97 | # Appends `element` to `children` and returns the resulting node. 98 | # 99 | # @return [AST::Node] 100 | # 101 | # source://ast//lib/ast/node.rb#177 102 | def append(element); end 103 | 104 | # Returns the children of this node. 105 | # The returned value is frozen. 106 | # The to_a alias is useful for decomposing nodes concisely. 107 | # For example: 108 | # 109 | # node = s(:gasgn, :$foo, s(:integer, 1)) 110 | # var_name, value = *node 111 | # p var_name # => :$foo 112 | # p value # => (integer 1) 113 | # 114 | # @return [Array] 115 | # 116 | # source://ast//lib/ast/node.rb#56 117 | def children; end 118 | 119 | # Nodes are already frozen, so there is no harm in returning the 120 | # current node as opposed to initializing from scratch and freezing 121 | # another one. 122 | # 123 | # @return self 124 | # 125 | # source://ast//lib/ast/node.rb#115 126 | def clone; end 127 | 128 | # Concatenates `array` with `children` and returns the resulting node. 129 | # 130 | # @return [AST::Node] 131 | # 132 | # source://ast//lib/ast/node.rb#168 133 | def concat(array); end 134 | 135 | # Enables matching for Node, where type is the first element 136 | # and the children are remaining items. 137 | # 138 | # @return [Array] 139 | # 140 | # source://ast//lib/ast/node.rb#253 141 | def deconstruct; end 142 | 143 | # Nodes are already frozen, so there is no harm in returning the 144 | # current node as opposed to initializing from scratch and freezing 145 | # another one. 146 | # 147 | # @return self 148 | # 149 | # source://ast//lib/ast/node.rb#115 150 | def dup; end 151 | 152 | # Test if other object is equal to 153 | # 154 | # @param other [Object] 155 | # @return [Boolean] 156 | # 157 | # source://ast//lib/ast/node.rb#85 158 | def eql?(other); end 159 | 160 | # Returns the precomputed hash value for this node 161 | # 162 | # @return [Fixnum] 163 | # 164 | # source://ast//lib/ast/node.rb#61 165 | def hash; end 166 | 167 | # Converts `self` to a s-expression ruby string. 168 | # The code return will recreate the node, using the sexp module s() 169 | # 170 | # @param indent [Integer] Base indentation level. 171 | # @return [String] 172 | # 173 | # source://ast//lib/ast/node.rb#211 174 | def inspect(indent = T.unsafe(nil)); end 175 | 176 | # Returns the children of this node. 177 | # The returned value is frozen. 178 | # The to_a alias is useful for decomposing nodes concisely. 179 | # For example: 180 | # 181 | # node = s(:gasgn, :$foo, s(:integer, 1)) 182 | # var_name, value = *node 183 | # p var_name # => :$foo 184 | # p value # => (integer 1) 185 | # 186 | # @return [Array] 187 | # 188 | # source://ast//lib/ast/node.rb#56 189 | def to_a; end 190 | 191 | # @return [AST::Node] self 192 | # 193 | # source://ast//lib/ast/node.rb#229 194 | def to_ast; end 195 | 196 | # Converts `self` to a pretty-printed s-expression. 197 | # 198 | # @param indent [Integer] Base indentation level. 199 | # @return [String] 200 | # 201 | # source://ast//lib/ast/node.rb#187 202 | def to_s(indent = T.unsafe(nil)); end 203 | 204 | # Converts `self` to a pretty-printed s-expression. 205 | # 206 | # @param indent [Integer] Base indentation level. 207 | # @return [String] 208 | # 209 | # source://ast//lib/ast/node.rb#187 210 | def to_sexp(indent = T.unsafe(nil)); end 211 | 212 | # Converts `self` to an Array where the first element is the type as a Symbol, 213 | # and subsequent elements are the same representation of its children. 214 | # 215 | # @return [Array] 216 | # 217 | # source://ast//lib/ast/node.rb#237 218 | def to_sexp_array; end 219 | 220 | # Returns the type of this node. 221 | # 222 | # @return [Symbol] 223 | # 224 | # source://ast//lib/ast/node.rb#43 225 | def type; end 226 | 227 | # Returns a new instance of Node where non-nil arguments replace the 228 | # corresponding fields of `self`. 229 | # 230 | # For example, `Node.new(:foo, [ 1, 2 ]).updated(:bar)` would yield 231 | # `(bar 1 2)`, and `Node.new(:foo, [ 1, 2 ]).updated(nil, [])` would 232 | # yield `(foo)`. 233 | # 234 | # If the resulting node would be identical to `self`, does nothing. 235 | # 236 | # @param type [Symbol, nil] 237 | # @param children [Array, nil] 238 | # @param properties [Hash, nil] 239 | # @return [AST::Node] 240 | # 241 | # source://ast//lib/ast/node.rb#133 242 | def updated(type = T.unsafe(nil), children = T.unsafe(nil), properties = T.unsafe(nil)); end 243 | 244 | protected 245 | 246 | # By default, each entry in the `properties` hash is assigned to 247 | # an instance variable in this instance of Node. A subclass should define 248 | # attribute readers for such variables. The values passed in the hash 249 | # are not frozen or whitelisted; such behavior can also be implemented 250 | # by subclassing Node and overriding this method. 251 | # 252 | # @return [nil] 253 | # 254 | # source://ast//lib/ast/node.rb#98 255 | def assign_properties(properties); end 256 | 257 | # Returns `@type` with all underscores replaced by dashes. This allows 258 | # to write symbol literals without quotes in Ruby sources and yet have 259 | # nicely looking s-expressions. 260 | # 261 | # @return [String] 262 | # 263 | # source://ast//lib/ast/node.rb#264 264 | def fancy_type; end 265 | 266 | private 267 | 268 | def original_dup; end 269 | end 270 | 271 | # This class includes {AST::Processor::Mixin}; however, it is 272 | # deprecated, since the module defines all of the behaviors that 273 | # the processor includes. Any new libraries should use 274 | # {AST::Processor::Mixin} instead of subclassing this. 275 | # 276 | # @deprecated Use {AST::Processor::Mixin} instead. 277 | # 278 | # source://ast//lib/ast/processor.rb#8 279 | class AST::Processor 280 | include ::AST::Processor::Mixin 281 | end 282 | 283 | # The processor module is a module which helps transforming one 284 | # AST into another. In a nutshell, the {#process} method accepts 285 | # a {Node} and dispatches it to a handler corresponding to its 286 | # type, and returns a (possibly) updated variant of the node. 287 | # 288 | # The processor module has a set of associated design patterns. 289 | # They are best explained with a concrete example. Let's define a 290 | # simple arithmetic language and an AST format for it: 291 | # 292 | # Terminals (AST nodes which do not have other AST nodes inside): 293 | # 294 | # * `(integer )`, 295 | # 296 | # Nonterminals (AST nodes with other nodes as children): 297 | # 298 | # * `(add )`, 299 | # * `(multiply )`, 300 | # * `(divide )`, 301 | # * `(negate )`, 302 | # * `(store )`: stores value of `` 303 | # into a variable named ``, 304 | # * `(load )`: loads value of a variable named 305 | # ``, 306 | # * `(each ...)`: computes each of the ``s and 307 | # prints the result. 308 | # 309 | # All AST nodes have the same Ruby class, and therefore they don't 310 | # know how to traverse themselves. (A solution which dynamically 311 | # checks the type of children is possible, but is slow and 312 | # error-prone.) So, a class including the module which knows how 313 | # to traverse the entire tree should be defined. Such classes 314 | # have a handler for each nonterminal node which recursively 315 | # processes children nodes: 316 | # 317 | # require 'ast' 318 | # 319 | # class ArithmeticsProcessor 320 | # include AST::Processor::Mixin 321 | # # This method traverses any binary operators such as (add) 322 | # # or (multiply). 323 | # def process_binary_op(node) 324 | # # Children aren't decomposed automatically; it is 325 | # # suggested to use Ruby multiple assignment expansion, 326 | # # as it is very convenient here. 327 | # left_expr, right_expr = *node 328 | # 329 | # # AST::Node#updated won't change node type if nil is 330 | # # passed as a first argument, which allows to reuse the 331 | # # same handler for multiple node types using `alias' 332 | # # (below). 333 | # node.updated(nil, [ 334 | # process(left_expr), 335 | # process(right_expr) 336 | # ]) 337 | # end 338 | # alias_method :on_add, :process_binary_op 339 | # alias_method :on_multiply, :process_binary_op 340 | # alias_method :on_divide, :process_binary_op 341 | # 342 | # def on_negate(node) 343 | # # It is also possible to use #process_all for more 344 | # # compact code if every child is a Node. 345 | # node.updated(nil, process_all(node)) 346 | # end 347 | # 348 | # def on_store(node) 349 | # expr, variable_name = *node 350 | # 351 | # # Note that variable_name is not a Node and thus isn't 352 | # # passed to #process. 353 | # node.updated(nil, [ 354 | # process(expr), 355 | # variable_name 356 | # ]) 357 | # end 358 | # 359 | # # (load) is effectively a terminal node, and so it does 360 | # # not need an explicit handler, as the following is the 361 | # # default behavior. Essentially, for any nodes that don't 362 | # # have a defined handler, the node remains unchanged. 363 | # def on_load(node) 364 | # nil 365 | # end 366 | # 367 | # def on_each(node) 368 | # node.updated(nil, process_all(node)) 369 | # end 370 | # end 371 | # 372 | # Let's test our ArithmeticsProcessor: 373 | # 374 | # include AST::Sexp 375 | # expr = s(:add, s(:integer, 2), s(:integer, 2)) 376 | # 377 | # p ArithmeticsProcessor.new.process(expr) == expr # => true 378 | # 379 | # As expected, it does not change anything at all. This isn't 380 | # actually very useful, so let's now define a Calculator, which 381 | # will compute the expression values: 382 | # 383 | # # This Processor folds nonterminal nodes and returns an 384 | # # (integer) terminal node. 385 | # class ArithmeticsCalculator < ArithmeticsProcessor 386 | # def compute_op(node) 387 | # # First, node children are processed and then unpacked 388 | # # to local variables. 389 | # nodes = process_all(node) 390 | # 391 | # if nodes.all? { |node| node.type == :integer } 392 | # # If each of those nodes represents a literal, we can 393 | # # fold this node! 394 | # values = nodes.map { |node| node.children.first } 395 | # AST::Node.new(:integer, [ 396 | # yield(values) 397 | # ]) 398 | # else 399 | # # Otherwise, we can just leave the current node in the 400 | # # tree and only update it with processed children 401 | # # nodes, which can be partially folded. 402 | # node.updated(nil, nodes) 403 | # end 404 | # end 405 | # 406 | # def on_add(node) 407 | # compute_op(node) { |left, right| left + right } 408 | # end 409 | # 410 | # def on_multiply(node) 411 | # compute_op(node) { |left, right| left * right } 412 | # end 413 | # end 414 | # 415 | # Let's check: 416 | # 417 | # p ArithmeticsCalculator.new.process(expr) # => (integer 4) 418 | # 419 | # Excellent, the calculator works! Now, a careful reader could 420 | # notice that the ArithmeticsCalculator does not know how to 421 | # divide numbers. What if we pass an expression with division to 422 | # it? 423 | # 424 | # expr_with_division = \ 425 | # s(:add, 426 | # s(:integer, 1), 427 | # s(:divide, 428 | # s(:add, s(:integer, 8), s(:integer, 4)), 429 | # s(:integer, 3))) # 1 + (8 + 4) / 3 430 | # 431 | # folded_expr_with_division = ArithmeticsCalculator.new.process(expr_with_division) 432 | # p folded_expr_with_division 433 | # # => (add 434 | # # (integer 1) 435 | # # (divide 436 | # # (integer 12) 437 | # # (integer 3))) 438 | # 439 | # As you can see, the expression was folded _partially_: the inner 440 | # `(add)` node which could be computed was folded to 441 | # `(integer 12)`, the `(divide)` node is left as-is because there 442 | # is no computing handler for it, and the root `(add)` node was 443 | # also left as it is because some of its children were not 444 | # literals. 445 | # 446 | # Note that this partial folding is only possible because the 447 | # _data_ format, i.e. the format in which the computed values of 448 | # the nodes are represented, is the same as the AST itself. 449 | # 450 | # Let's extend our ArithmeticsCalculator class further. 451 | # 452 | # class ArithmeticsCalculator 453 | # def on_divide(node) 454 | # compute_op(node) { |left, right| left / right } 455 | # end 456 | # 457 | # def on_negate(node) 458 | # # Note how #compute_op works regardless of the operator 459 | # # arity. 460 | # compute_op(node) { |value| -value } 461 | # end 462 | # end 463 | # 464 | # Now, let's apply our renewed ArithmeticsCalculator to a partial 465 | # result of previous evaluation: 466 | # 467 | # p ArithmeticsCalculator.new.process(expr_with_division) # => (integer 5) 468 | # 469 | # Five! Excellent. This is also pretty much how CRuby 1.8 executed 470 | # its programs. 471 | # 472 | # Now, let's do some automated bug searching. Division by zero is 473 | # an error, right? So if we could detect that someone has divided 474 | # by zero before the program is even run, that could save some 475 | # debugging time. 476 | # 477 | # class DivisionByZeroVerifier < ArithmeticsProcessor 478 | # class VerificationFailure < Exception; end 479 | # 480 | # def on_divide(node) 481 | # # You need to process the children to handle nested divisions 482 | # # such as: 483 | # # (divide 484 | # # (integer 1) 485 | # # (divide (integer 1) (integer 0)) 486 | # left, right = process_all(node) 487 | # 488 | # if right.type == :integer && 489 | # right.children.first == 0 490 | # raise VerificationFailure, "Ouch! This code divides by zero." 491 | # end 492 | # end 493 | # 494 | # def divides_by_zero?(ast) 495 | # process(ast) 496 | # false 497 | # rescue VerificationFailure 498 | # true 499 | # end 500 | # end 501 | # 502 | # nice_expr = \ 503 | # s(:divide, 504 | # s(:add, s(:integer, 10), s(:integer, 2)), 505 | # s(:integer, 4)) 506 | # 507 | # p DivisionByZeroVerifier.new.divides_by_zero?(nice_expr) 508 | # # => false. Good. 509 | # 510 | # bad_expr = \ 511 | # s(:add, s(:integer, 10), 512 | # s(:divide, s(:integer, 1), s(:integer, 0))) 513 | # 514 | # p DivisionByZeroVerifier.new.divides_by_zero?(bad_expr) 515 | # # => true. WHOOPS. DO NOT RUN THIS. 516 | # 517 | # Of course, this won't detect more complex cases... unless you 518 | # use some partial evaluation before! The possibilites are 519 | # endless. Have fun. 520 | # 521 | # source://ast//lib/ast/processor/mixin.rb#240 522 | module AST::Processor::Mixin 523 | # Default handler. Does nothing. 524 | # 525 | # @param node [AST::Node] 526 | # @return [AST::Node, nil] 527 | # 528 | # source://ast//lib/ast/processor/mixin.rb#284 529 | def handler_missing(node); end 530 | 531 | # Dispatches `node`. If a node has type `:foo`, then a handler 532 | # named `on_foo` is invoked with one argument, the `node`; if 533 | # there isn't such a handler, {#handler_missing} is invoked 534 | # with the same argument. 535 | # 536 | # If the handler returns `nil`, `node` is returned; otherwise, 537 | # the return value of the handler is passed along. 538 | # 539 | # @param node [AST::Node, nil] 540 | # @return [AST::Node, nil] 541 | # 542 | # source://ast//lib/ast/processor/mixin.rb#251 543 | def process(node); end 544 | 545 | # {#process}es each node from `nodes` and returns an array of 546 | # results. 547 | # 548 | # @param nodes [Array] 549 | # @return [Array] 550 | # 551 | # source://ast//lib/ast/processor/mixin.rb#274 552 | def process_all(nodes); end 553 | end 554 | 555 | # This simple module is very useful in the cases where one needs 556 | # to define deeply nested ASTs from Ruby code, for example, in 557 | # tests. It should be used like this: 558 | # 559 | # describe YourLanguage::AST do 560 | # include Sexp 561 | # 562 | # it "should correctly parse expressions" do 563 | # YourLanguage.parse("1 + 2 * 3").should == 564 | # s(:add, 565 | # s(:integer, 1), 566 | # s(:multiply, 567 | # s(:integer, 2), 568 | # s(:integer, 3))) 569 | # end 570 | # end 571 | # 572 | # This way the amount of boilerplate code is greatly reduced. 573 | # 574 | # source://ast//lib/ast/sexp.rb#20 575 | module AST::Sexp 576 | # Creates a {Node} with type `type` and children `children`. 577 | # Note that the resulting node is of the type AST::Node and not a 578 | # subclass. 579 | # This would not pose a problem with comparisons, as {Node#==} 580 | # ignores metadata. 581 | # 582 | # source://ast//lib/ast/sexp.rb#26 583 | def s(type, *children); end 584 | end 585 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/code_ownership@1.29.2.rbi: -------------------------------------------------------------------------------- 1 | # typed: true 2 | 3 | # DO NOT EDIT MANUALLY 4 | # This is an autogenerated file for types exported from the `code_ownership` gem. 5 | # Please instead update this file by running `bin/tapioca gem code_ownership`. 6 | 7 | # source://code_ownership//lib/code_ownership/cli.rb#7 8 | module CodeOwnership 9 | extend ::CodeOwnership 10 | 11 | requires_ancestor { Kernel } 12 | 13 | # Given a backtrace from either `Exception#backtrace` or `caller`, find the 14 | # first line that corresponds to a file with assigned ownership 15 | # 16 | # source://code_ownership//lib/code_ownership.rb#95 17 | sig do 18 | params( 19 | backtrace: T.nilable(T::Array[::String]), 20 | excluded_teams: T::Array[::CodeTeams::Team] 21 | ).returns(T.nilable(::CodeTeams::Team)) 22 | end 23 | def for_backtrace(backtrace, excluded_teams: T.unsafe(nil)); end 24 | 25 | # source://code_ownership//lib/code_ownership.rb#127 26 | sig { params(klass: T.nilable(T.any(::Class, ::Module))).returns(T.nilable(::CodeTeams::Team)) } 27 | def for_class(klass); end 28 | 29 | # source://code_ownership//lib/code_ownership.rb#21 30 | sig { params(file: ::String).returns(T.nilable(::CodeTeams::Team)) } 31 | def for_file(file); end 32 | 33 | # source://code_ownership//lib/code_ownership.rb#144 34 | sig { params(package: ::Packs::Pack).returns(T.nilable(::CodeTeams::Team)) } 35 | def for_package(package); end 36 | 37 | # source://code_ownership//lib/code_ownership.rb#39 38 | sig { params(team: T.any(::CodeTeams::Team, ::String)).returns(::String) } 39 | def for_team(team); end 40 | 41 | # source://code_ownership//lib/code_ownership.rb#83 42 | sig { params(files: T::Array[::String], autocorrect: T::Boolean, stage_changes: T::Boolean).void } 43 | def validate!(files: T.unsafe(nil), autocorrect: T.unsafe(nil), stage_changes: T.unsafe(nil)); end 44 | 45 | class << self 46 | # Generally, you should not ever need to do this, because once your ruby process loads, cached content should not change. 47 | # Namely, the set of files, packages, and directories which are tracked for ownership should not change. 48 | # The primary reason this is helpful is for clients of CodeOwnership who want to test their code, and each test context 49 | # has different ownership and tracked files. 50 | # 51 | # source://code_ownership//lib/code_ownership.rb#153 52 | sig { void } 53 | def bust_caches!; end 54 | 55 | # source://code_ownership//lib/code_ownership.rb#72 56 | sig { params(filename: ::String).void } 57 | def remove_file_annotation!(filename); end 58 | end 59 | end 60 | 61 | # source://code_ownership//lib/code_ownership/cli.rb#8 62 | class CodeOwnership::Cli 63 | class << self 64 | # For now, this just returns team ownership 65 | # Later, this could also return code ownership errors about that file. 66 | # 67 | # source://code_ownership//lib/code_ownership/cli.rb#76 68 | def for_file(argv); end 69 | 70 | # source://code_ownership//lib/code_ownership/cli.rb#123 71 | def for_team(argv); end 72 | 73 | # source://code_ownership//lib/code_ownership/cli.rb#9 74 | def run!(argv); end 75 | 76 | private 77 | 78 | # source://code_ownership//lib/code_ownership/cli.rb#33 79 | def validate!(argv); end 80 | end 81 | end 82 | 83 | # source://code_ownership//lib/code_ownership.rb#68 84 | class CodeOwnership::InvalidCodeOwnershipConfigurationError < ::StandardError; end 85 | 86 | # source://code_ownership//lib/code_ownership/private/configuration.rb#4 87 | module CodeOwnership::Private 88 | class << self 89 | # source://code_ownership//lib/code_ownership/private.rb#30 90 | sig { void } 91 | def bust_caches!; end 92 | 93 | # source://code_ownership//lib/code_ownership/private.rb#24 94 | sig { returns(::CodeOwnership::Private::Configuration) } 95 | def configuration; end 96 | 97 | # source://code_ownership//lib/code_ownership/private.rb#69 98 | sig { returns(::CodeOwnership::Private::OwnershipMappers::FileAnnotations) } 99 | def file_annotations_mapper; end 100 | 101 | # source://code_ownership//lib/code_ownership/private.rb#106 102 | sig { params(files: T::Array[::String]).returns(T::Hash[::String, T::Array[::String]]) } 103 | def files_by_mapper(files); end 104 | 105 | # source://code_ownership//lib/code_ownership/private.rb#96 106 | sig { params(team_name: ::String, location_of_reference: ::String).returns(::CodeTeams::Team) } 107 | def find_team!(team_name, location_of_reference); end 108 | 109 | # source://code_ownership//lib/code_ownership/private.rb#59 110 | sig { returns(T::Array[::CodeOwnership::Private::OwnershipMappers::Interface]) } 111 | def mappers; end 112 | 113 | # Returns a string version of the relative path to a Rails constant, 114 | # or nil if it can't find something 115 | # 116 | # source://code_ownership//lib/code_ownership/private.rb#77 117 | sig { params(klass: T.nilable(T.any(::Class, ::Module))).returns(T.nilable(::String)) } 118 | def path_from_klass(klass); end 119 | 120 | # The output of this function is string pathnames relative to the root. 121 | # 122 | # source://code_ownership//lib/code_ownership/private.rb#90 123 | sig { returns(T::Array[::String]) } 124 | def tracked_files; end 125 | 126 | # source://code_ownership//lib/code_ownership/private.rb#37 127 | sig { params(files: T::Array[::String], autocorrect: T::Boolean, stage_changes: T::Boolean).void } 128 | def validate!(files:, autocorrect: T.unsafe(nil), stage_changes: T.unsafe(nil)); end 129 | end 130 | end 131 | 132 | # source://code_ownership//lib/code_ownership/private/configuration.rb#5 133 | class CodeOwnership::Private::Configuration < ::T::Struct 134 | const :js_package_paths, T::Array[::String] 135 | const :owned_globs, T::Array[::String] 136 | const :skip_codeowners_validation, T::Boolean 137 | const :unowned_globs, T::Array[::String] 138 | 139 | class << self 140 | # source://code_ownership//lib/code_ownership/private/configuration.rb#15 141 | sig { returns(::CodeOwnership::Private::Configuration) } 142 | def fetch; end 143 | 144 | # source://sorbet-runtime/0.5.9924/lib/types/struct.rb#13 145 | def inherited(s); end 146 | 147 | # source://code_ownership//lib/code_ownership/private/configuration.rb#27 148 | sig { params(config_hash: T::Hash[T.untyped, T.untyped]).returns(T::Array[::String]) } 149 | def js_package_paths(config_hash); end 150 | end 151 | end 152 | 153 | # source://code_ownership//lib/code_ownership/private/configuration.rb#7 154 | CodeOwnership::Private::Configuration::DEFAULT_JS_PACKAGE_PATHS = T.let(T.unsafe(nil), Array) 155 | 156 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/interface.rb#7 157 | module CodeOwnership::Private::OwnershipMappers; end 158 | 159 | # Calculate, cache, and return a mapping of file names (relative to the root 160 | # of the repository) to team name. 161 | # 162 | # Example: 163 | # 164 | # { 165 | # 'app/models/company.rb' => Team.find('Setup & Onboarding'), 166 | # ... 167 | # } 168 | # 169 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#17 170 | class CodeOwnership::Private::OwnershipMappers::FileAnnotations 171 | include ::CodeOwnership::Private::OwnershipMappers::Interface 172 | 173 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#113 174 | sig { override.void } 175 | def bust_caches!; end 176 | 177 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#102 178 | sig { override.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 179 | def codeowners_lines_to_owners; end 180 | 181 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#108 182 | sig { override.returns(::String) } 183 | def description; end 184 | 185 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#50 186 | sig { params(filename: ::String).returns(T.nilable(::CodeTeams::Team)) } 187 | def file_annotation_based_owner(filename); end 188 | 189 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#29 190 | sig { override.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) } 191 | def map_file_to_owner(file); end 192 | 193 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#38 194 | sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 195 | def map_files_to_owners(files); end 196 | 197 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#86 198 | sig { params(filename: ::String).void } 199 | def remove_file_annotation!(filename); end 200 | end 201 | 202 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/file_annotations.rb#23 203 | CodeOwnership::Private::OwnershipMappers::FileAnnotations::TEAM_PATTERN = T.let(T.unsafe(nil), Regexp) 204 | 205 | # @abstract Subclasses must implement the `abstract` methods below. 206 | # 207 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/interface.rb#8 208 | module CodeOwnership::Private::OwnershipMappers::Interface 209 | interface! 210 | 211 | # @abstract 212 | # 213 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/interface.rb#45 214 | sig { abstract.void } 215 | def bust_caches!; end 216 | 217 | # @abstract 218 | # 219 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/interface.rb#37 220 | sig { abstract.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 221 | def codeowners_lines_to_owners; end 222 | 223 | # @abstract 224 | # 225 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/interface.rb#41 226 | sig { abstract.returns(::String) } 227 | def description; end 228 | 229 | # This should be fast when run with ONE file 230 | # 231 | # @abstract 232 | # 233 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/interface.rb#21 234 | sig { abstract.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) } 235 | def map_file_to_owner(file); end 236 | 237 | # This should be fast when run with MANY files 238 | # 239 | # @abstract 240 | # 241 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/interface.rb#31 242 | sig { abstract.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 243 | def map_files_to_owners(files); end 244 | end 245 | 246 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/js_package_ownership.rb#8 247 | class CodeOwnership::Private::OwnershipMappers::JsPackageOwnership 248 | include ::CodeOwnership::Private::OwnershipMappers::Interface 249 | 250 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/js_package_ownership.rb#80 251 | sig { override.void } 252 | def bust_caches!; end 253 | 254 | # Package ownership ignores the passed in files when generating code owners lines. 255 | # This is because Package ownership knows that the fastest way to find code owners for package based ownership 256 | # is to simply iterate over the packages and grab the owner, rather than iterating over each file just to get what package it is in 257 | # In theory this means that we may generate code owners lines that cover files that are not in the passed in argument, 258 | # but in practice this is not of consequence because in reality we never really want to generate code owners for only a 259 | # subset of files, but rather we want code ownership for all files. 260 | # 261 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/js_package_ownership.rb#54 262 | sig { override.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 263 | def codeowners_lines_to_owners; end 264 | 265 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/js_package_ownership.rb#64 266 | sig { override.returns(::String) } 267 | def description; end 268 | 269 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/js_package_ownership.rb#18 270 | sig { override.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) } 271 | def map_file_to_owner(file); end 272 | 273 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/js_package_ownership.rb#31 274 | sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 275 | def map_files_to_owners(files); end 276 | 277 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/js_package_ownership.rb#69 278 | sig { params(package: ::CodeOwnership::Private::ParseJsPackages::Package).returns(T.nilable(::CodeTeams::Team)) } 279 | def owner_for_package(package); end 280 | 281 | private 282 | 283 | # takes a file and finds the relevant `package.json` file by walking up the directory 284 | # structure. Example, given `packages/a/b/c.rb`, this looks for `packages/a/b/package.json`, `packages/a/package.json`, 285 | # `packages/package.json`, and `package.json` in that order, stopping at the first file to actually exist. 286 | # We do additional caching so that we don't have to check for file existence every time 287 | # 288 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/js_package_ownership.rb#91 289 | sig { params(file: ::String).returns(T.nilable(::CodeOwnership::Private::ParseJsPackages::Package)) } 290 | def map_file_to_relevant_package(file); end 291 | end 292 | 293 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/package_ownership.rb#8 294 | class CodeOwnership::Private::OwnershipMappers::PackageOwnership 295 | include ::CodeOwnership::Private::OwnershipMappers::Interface 296 | 297 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/package_ownership.rb#80 298 | sig { override.void } 299 | def bust_caches!; end 300 | 301 | # Package ownership ignores the passed in files when generating code owners lines. 302 | # This is because Package ownership knows that the fastest way to find code owners for package based ownership 303 | # is to simply iterate over the packages and grab the owner, rather than iterating over each file just to get what package it is in 304 | # In theory this means that we may generate code owners lines that cover files that are not in the passed in argument, 305 | # but in practice this is not of consequence because in reality we never really want to generate code owners for only a 306 | # subset of files, but rather we want code ownership for all files. 307 | # 308 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/package_ownership.rb#54 309 | sig { override.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 310 | def codeowners_lines_to_owners; end 311 | 312 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/package_ownership.rb#64 313 | sig { override.returns(::String) } 314 | def description; end 315 | 316 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/package_ownership.rb#18 317 | sig { override.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) } 318 | def map_file_to_owner(file); end 319 | 320 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/package_ownership.rb#31 321 | sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 322 | def map_files_to_owners(files); end 323 | 324 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/package_ownership.rb#69 325 | sig { params(package: ::Packs::Pack).returns(T.nilable(::CodeTeams::Team)) } 326 | def owner_for_package(package); end 327 | end 328 | 329 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/team_globs.rb#8 330 | class CodeOwnership::Private::OwnershipMappers::TeamGlobs 331 | include ::CodeOwnership::Private::OwnershipMappers::Interface 332 | 333 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/team_globs.rb#56 334 | sig { override.void } 335 | def bust_caches!; end 336 | 337 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/team_globs.rb#45 338 | sig { override.returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 339 | def codeowners_lines_to_owners; end 340 | 341 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/team_globs.rb#62 342 | sig { override.returns(::String) } 343 | def description; end 344 | 345 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/team_globs.rb#38 346 | sig { override.params(file: ::String).returns(T.nilable(::CodeTeams::Team)) } 347 | def map_file_to_owner(file); end 348 | 349 | # source://code_ownership//lib/code_ownership/private/ownership_mappers/team_globs.rb#22 350 | sig { override.params(files: T::Array[::String]).returns(T::Hash[::String, T.nilable(::CodeTeams::Team)]) } 351 | def map_files_to_owners(files); end 352 | end 353 | 354 | # Modeled off of ParsePackwerk 355 | # 356 | # source://code_ownership//lib/code_ownership/private/parse_js_packages.rb#8 357 | module CodeOwnership::Private::ParseJsPackages 358 | class << self 359 | # source://code_ownership//lib/code_ownership/private/parse_js_packages.rb#57 360 | sig { returns(T::Array[::CodeOwnership::Private::ParseJsPackages::Package]) } 361 | def all; end 362 | end 363 | end 364 | 365 | # source://code_ownership//lib/code_ownership/private/parse_js_packages.rb#13 366 | CodeOwnership::Private::ParseJsPackages::METADATA = T.let(T.unsafe(nil), String) 367 | 368 | # source://code_ownership//lib/code_ownership/private/parse_js_packages.rb#12 369 | CodeOwnership::Private::ParseJsPackages::PACKAGE_JSON_NAME = T.let(T.unsafe(nil), String) 370 | 371 | # source://code_ownership//lib/code_ownership/private/parse_js_packages.rb#15 372 | class CodeOwnership::Private::ParseJsPackages::Package < ::T::Struct 373 | const :metadata, T::Hash[::String, T.untyped] 374 | const :name, ::String 375 | 376 | # source://code_ownership//lib/code_ownership/private/parse_js_packages.rb#48 377 | sig { returns(::Pathname) } 378 | def directory; end 379 | 380 | class << self 381 | # source://code_ownership//lib/code_ownership/private/parse_js_packages.rb#22 382 | sig { params(pathname: ::Pathname).returns(::CodeOwnership::Private::ParseJsPackages::Package) } 383 | def from(pathname); end 384 | 385 | # source://sorbet-runtime/0.5.9924/lib/types/struct.rb#13 386 | def inherited(s); end 387 | end 388 | end 389 | 390 | # source://code_ownership//lib/code_ownership/private/parse_js_packages.rb#11 391 | CodeOwnership::Private::ParseJsPackages::ROOT_PACKAGE_NAME = T.let(T.unsafe(nil), String) 392 | 393 | # source://code_ownership//lib/code_ownership/private/team_plugins/ownership.rb#5 394 | module CodeOwnership::Private::TeamPlugins; end 395 | 396 | # source://code_ownership//lib/code_ownership/private/team_plugins/github.rb#6 397 | class CodeOwnership::Private::TeamPlugins::Github < ::CodeTeams::Plugin 398 | # source://code_ownership//lib/code_ownership/private/team_plugins/github.rb#13 399 | sig { returns(::CodeOwnership::Private::TeamPlugins::Github::GithubStruct) } 400 | def github; end 401 | end 402 | 403 | # source://code_ownership//lib/code_ownership/private/team_plugins/github.rb#10 404 | class CodeOwnership::Private::TeamPlugins::Github::GithubStruct < ::Struct 405 | # Returns the value of attribute do_not_add_to_codeowners_file 406 | # 407 | # @return [Object] the current value of do_not_add_to_codeowners_file 408 | def do_not_add_to_codeowners_file; end 409 | 410 | # Sets the attribute do_not_add_to_codeowners_file 411 | # 412 | # @param value [Object] the value to set the attribute do_not_add_to_codeowners_file to. 413 | # @return [Object] the newly set value 414 | # 415 | # source://code_ownership//lib/code_ownership/private/team_plugins/github.rb#10 416 | def do_not_add_to_codeowners_file=(_); end 417 | 418 | # Returns the value of attribute team 419 | # 420 | # @return [Object] the current value of team 421 | def team; end 422 | 423 | # Sets the attribute team 424 | # 425 | # @param value [Object] the value to set the attribute team to. 426 | # @return [Object] the newly set value 427 | # 428 | # source://code_ownership//lib/code_ownership/private/team_plugins/github.rb#10 429 | def team=(_); end 430 | 431 | class << self 432 | def [](*_arg0); end 433 | def inspect; end 434 | def members; end 435 | def new(*_arg0); end 436 | end 437 | end 438 | 439 | # source://code_ownership//lib/code_ownership/private/team_plugins/ownership.rb#6 440 | class CodeOwnership::Private::TeamPlugins::Ownership < ::CodeTeams::Plugin 441 | # source://code_ownership//lib/code_ownership/private/team_plugins/ownership.rb#11 442 | sig { returns(T::Array[::String]) } 443 | def owned_globs; end 444 | end 445 | 446 | # source://code_ownership//lib/code_ownership/private/validations/interface.rb#5 447 | module CodeOwnership::Private::Validations; end 448 | 449 | # source://code_ownership//lib/code_ownership/private/validations/files_have_owners.rb#6 450 | class CodeOwnership::Private::Validations::FilesHaveOwners 451 | include ::CodeOwnership::Private::Validations::Interface 452 | 453 | # source://code_ownership//lib/code_ownership/private/validations/files_have_owners.rb#12 454 | sig do 455 | override 456 | .params( 457 | files: T::Array[::String], 458 | autocorrect: T::Boolean, 459 | stage_changes: T::Boolean 460 | ).returns(T::Array[::String]) 461 | end 462 | def validation_errors(files:, autocorrect: T.unsafe(nil), stage_changes: T.unsafe(nil)); end 463 | end 464 | 465 | # source://code_ownership//lib/code_ownership/private/validations/files_have_unique_owners.rb#6 466 | class CodeOwnership::Private::Validations::FilesHaveUniqueOwners 467 | include ::CodeOwnership::Private::Validations::Interface 468 | 469 | # source://code_ownership//lib/code_ownership/private/validations/files_have_unique_owners.rb#12 470 | sig do 471 | override 472 | .params( 473 | files: T::Array[::String], 474 | autocorrect: T::Boolean, 475 | stage_changes: T::Boolean 476 | ).returns(T::Array[::String]) 477 | end 478 | def validation_errors(files:, autocorrect: T.unsafe(nil), stage_changes: T.unsafe(nil)); end 479 | end 480 | 481 | # source://code_ownership//lib/code_ownership/private/validations/github_codeowners_up_to_date.rb#6 482 | class CodeOwnership::Private::Validations::GithubCodeownersUpToDate 483 | include ::CodeOwnership::Private::Validations::Interface 484 | 485 | # source://code_ownership//lib/code_ownership/private/validations/github_codeowners_up_to_date.rb#12 486 | sig do 487 | override 488 | .params( 489 | files: T::Array[::String], 490 | autocorrect: T::Boolean, 491 | stage_changes: T::Boolean 492 | ).returns(T::Array[::String]) 493 | end 494 | def validation_errors(files:, autocorrect: T.unsafe(nil), stage_changes: T.unsafe(nil)); end 495 | 496 | private 497 | 498 | # Generate the contents of a CODEOWNERS file that GitHub can use to 499 | # automatically assign reviewers 500 | # https://help.github.com/articles/about-codeowners/ 501 | # 502 | # source://code_ownership//lib/code_ownership/private/validations/github_codeowners_up_to_date.rb#100 503 | sig { returns(T::Array[::String]) } 504 | def codeowners_file_lines; end 505 | end 506 | 507 | # @abstract Subclasses must implement the `abstract` methods below. 508 | # 509 | # source://code_ownership//lib/code_ownership/private/validations/interface.rb#6 510 | module CodeOwnership::Private::Validations::Interface 511 | interface! 512 | 513 | # @abstract 514 | # 515 | # source://code_ownership//lib/code_ownership/private/validations/interface.rb#13 516 | sig do 517 | abstract 518 | .params( 519 | files: T::Array[::String], 520 | autocorrect: T::Boolean, 521 | stage_changes: T::Boolean 522 | ).returns(T::Array[::String]) 523 | end 524 | def validation_errors(files:, autocorrect: T.unsafe(nil), stage_changes: T.unsafe(nil)); end 525 | end 526 | -------------------------------------------------------------------------------- /sorbet/rbi/gems/dogapi@1.45.0.rbi: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT MANUALLY 2 | # This is an autogenerated file for types exported from the `dogapi` gem. 3 | # Please instead update this file by running `bin/tapioca sync`. 4 | 5 | # typed: true 6 | 7 | module Dogapi 8 | class << self 9 | def find_datadog_host; end 10 | def find_localhost; end 11 | def find_proxy; end 12 | def symbolized_access(hash); end 13 | def validate_tags(tags); end 14 | end 15 | end 16 | 17 | class Dogapi::APIService 18 | def initialize(api_key, application_key, silent = T.unsafe(nil), timeout = T.unsafe(nil), endpoint = T.unsafe(nil), skip_ssl_validation = T.unsafe(nil)); end 19 | 20 | def api_key; end 21 | def application_key; end 22 | def connect; end 23 | def handle_redirect(conn, req, resp, retries = T.unsafe(nil)); end 24 | def handle_response(resp); end 25 | def prepare_params(extra_params, url, with_app_key); end 26 | def prepare_request(method, url, params, body, send_json, with_app_key); end 27 | def request(method, url, extra_params, body, send_json, with_app_key = T.unsafe(nil)); end 28 | def should_set_api_and_app_keys_in_params?(url); end 29 | def suppress_error_if_silent(e); end 30 | end 31 | 32 | class Dogapi::Client 33 | def initialize(api_key, application_key = T.unsafe(nil), host = T.unsafe(nil), device = T.unsafe(nil), silent = T.unsafe(nil), timeout = T.unsafe(nil), endpoint = T.unsafe(nil), skip_ssl_validation = T.unsafe(nil)); end 34 | 35 | def add_items_to_dashboard_list(dashboard_list_id, dashboards); end 36 | def add_tags(host_id, tags, source = T.unsafe(nil)); end 37 | def alert(query, options = T.unsafe(nil)); end 38 | def all_tags(source = T.unsafe(nil)); end 39 | def aws_integration_create(config); end 40 | def aws_integration_delete(config); end 41 | def aws_integration_generate_external_id(config); end 42 | def aws_integration_list; end 43 | def aws_integration_list_namespaces; end 44 | def aws_integration_update(config, new_config); end 45 | def aws_logs_add_lambda(config); end 46 | def aws_logs_check_lambda(config); end 47 | def aws_logs_check_services(config); end 48 | def aws_logs_integration_delete(config); end 49 | def aws_logs_integrations_list; end 50 | def aws_logs_list_services; end 51 | def aws_logs_save_services(config); end 52 | def azure_integration_create(config); end 53 | def azure_integration_delete(config); end 54 | def azure_integration_list; end 55 | def azure_integration_update(config); end 56 | def azure_integration_update_host_filters(config); end 57 | def batch_metrics; end 58 | def can_delete_monitors(monitor_ids); end 59 | def can_delete_service_level_objective(slo_ids); end 60 | def cancel_downtime(downtime_id); end 61 | def cancel_downtime_by_scope(scope); end 62 | def comment(message, options = T.unsafe(nil)); end 63 | def create_board(title, widgets, layout_type, options = T.unsafe(nil)); end 64 | def create_dashboard(title, description, graphs, template_variables = T.unsafe(nil), read_only = T.unsafe(nil)); end 65 | def create_dashboard_list(name); end 66 | def create_embed(graph_json, description = T.unsafe(nil)); end 67 | def create_integration(source_type_name, config); end 68 | def create_logs_pipeline(name, filter, options = T.unsafe(nil)); end 69 | def create_screenboard(description); end 70 | def create_service_level_objective(type, slo_name, thresholds, options = T.unsafe(nil)); end 71 | def create_synthetics_test(type, config, options = T.unsafe(nil)); end 72 | def create_user(description = T.unsafe(nil)); end 73 | def datadog_host; end 74 | def datadog_host=(_arg0); end 75 | def delete_alert(alert_id); end 76 | def delete_board(dashboard_id); end 77 | def delete_comment(comment_id); end 78 | def delete_dashboard(dash_id); end 79 | def delete_dashboard_list(dashboard_list_id); end 80 | def delete_event(id); end 81 | def delete_integration(source_type_name); end 82 | def delete_items_from_dashboard_list(dashboard_list_id, dashboards); end 83 | def delete_logs_pipeline(pipeline_id); end 84 | def delete_many_service_level_objective(slo_ids); end 85 | def delete_monitor(monitor_id, options = T.unsafe(nil)); end 86 | def delete_screenboard(board_id); end 87 | def delete_service_level_objective(slo_id); end 88 | def delete_synthetics_tests(test_ids); end 89 | def delete_timeframes_service_level_objective(ops); end 90 | def detach_tags(host_id, source = T.unsafe(nil)); end 91 | def detatch_tags(host_id); end 92 | def disable_user(handle); end 93 | def emit_event(event, options = T.unsafe(nil)); end 94 | def emit_point(metric, value, options = T.unsafe(nil)); end 95 | def emit_points(metric, points, options = T.unsafe(nil)); end 96 | def enable_embed(embed_id); end 97 | def gcp_integration_create(config); end 98 | def gcp_integration_delete(config); end 99 | def gcp_integration_list; end 100 | def gcp_integration_update(config); end 101 | def get_active_metrics(from); end 102 | def get_alert(alert_id); end 103 | def get_all_alerts; end 104 | def get_all_boards; end 105 | def get_all_dashboard_lists; end 106 | def get_all_downtimes(options = T.unsafe(nil)); end 107 | def get_all_embeds; end 108 | def get_all_logs_pipelines; end 109 | def get_all_monitors(options = T.unsafe(nil)); end 110 | def get_all_screenboards; end 111 | def get_all_synthetics_tests; end 112 | def get_all_users; end 113 | def get_board(dashboard_id); end 114 | def get_custom_metrics_usage(start_hr, end_hr = T.unsafe(nil)); end 115 | def get_dashboard(dash_id); end 116 | def get_dashboard_list(dashboard_list_id); end 117 | def get_dashboards; end 118 | def get_downtime(downtime_id, options = T.unsafe(nil)); end 119 | def get_embed(embed_id, description = T.unsafe(nil)); end 120 | def get_event(id); end 121 | def get_fargate_usage(start_hr, end_hr = T.unsafe(nil)); end 122 | def get_hosts_usage(start_hr, end_hr = T.unsafe(nil)); end 123 | def get_integration(source_type_name); end 124 | def get_items_of_dashboard_list(dashboard_list_id); end 125 | def get_logs_pipeline(pipeline_id); end 126 | def get_logs_usage(start_hr, end_hr = T.unsafe(nil)); end 127 | def get_metadata(metric); end 128 | def get_monitor(monitor_id, options = T.unsafe(nil)); end 129 | def get_points(query, from, to); end 130 | def get_screenboard(board_id); end 131 | def get_service_level_objective(slo_id); end 132 | def get_service_level_objective_history(slo_id, from_ts, to_ts); end 133 | def get_synthetics_devices; end 134 | def get_synthetics_locations; end 135 | def get_synthetics_result(test_id, result_id); end 136 | def get_synthetics_results(test_id); end 137 | def get_synthetics_test(test_id); end 138 | def get_synthetics_usage(start_hr, end_hr = T.unsafe(nil)); end 139 | def get_traces_usage(start_hr, end_hr = T.unsafe(nil)); end 140 | def get_user(handle); end 141 | def graph_snapshot(metric_query, start_ts, end_ts, event_query = T.unsafe(nil)); end 142 | def host_tags(host_id, source = T.unsafe(nil), by_source = T.unsafe(nil)); end 143 | def host_totals; end 144 | def invite(emails, options = T.unsafe(nil)); end 145 | def monitor(type, query, options = T.unsafe(nil)); end 146 | def mute_alerts; end 147 | def mute_host(hostname, options = T.unsafe(nil)); end 148 | def mute_monitor(monitor_id, options = T.unsafe(nil)); end 149 | def mute_monitors; end 150 | def resolve_monitors(monitor_groups = T.unsafe(nil), options = T.unsafe(nil), version = T.unsafe(nil)); end 151 | def revoke_embed(embed_id); end 152 | def revoke_screenboard(board_id); end 153 | def schedule_downtime(scope, options = T.unsafe(nil)); end 154 | def search(query); end 155 | def search_hosts(options = T.unsafe(nil)); end 156 | def search_monitor_groups(options = T.unsafe(nil)); end 157 | def search_monitors(options = T.unsafe(nil)); end 158 | def search_service_level_objective(slo_ids = T.unsafe(nil), query = T.unsafe(nil), offset = T.unsafe(nil), limit = T.unsafe(nil)); end 159 | def service_check(check, host, status, options = T.unsafe(nil)); end 160 | def share_screenboard(board_id); end 161 | def start_event(event, options = T.unsafe(nil)); end 162 | def start_pause_synthetics_test(test_id, new_status); end 163 | def stream(start, stop, options = T.unsafe(nil)); end 164 | def unmute_alerts; end 165 | def unmute_host(hostname); end 166 | def unmute_monitor(monitor_id, options = T.unsafe(nil)); end 167 | def unmute_monitors; end 168 | def update_alert(alert_id, query, options = T.unsafe(nil)); end 169 | def update_board(dashboard_id, title, widgets, layout_type, options = T.unsafe(nil)); end 170 | def update_comment(comment_id, options = T.unsafe(nil)); end 171 | def update_dashboard(dash_id, title, description, graphs, template_variables = T.unsafe(nil), read_only = T.unsafe(nil)); end 172 | def update_dashboard_list(dashboard_list_id, name); end 173 | def update_downtime(downtime_id, options = T.unsafe(nil)); end 174 | def update_integration(source_type_name, config); end 175 | def update_items_of_dashboard_list(dashboard_list_id, dashboards); end 176 | def update_logs_pipeline(pipeline_id, name, filter, options = T.unsafe(nil)); end 177 | def update_metadata(metric, options = T.unsafe(nil)); end 178 | def update_monitor(monitor_id, query, options = T.unsafe(nil)); end 179 | def update_screenboard(board_id, description); end 180 | def update_service_level_objective(slo_id, type, options = T.unsafe(nil)); end 181 | def update_synthetics_test(test_id, type, config, options = T.unsafe(nil)); end 182 | def update_tags(host_id, tags, source = T.unsafe(nil)); end 183 | def update_user(handle, description = T.unsafe(nil)); end 184 | def v2; end 185 | def v2=(_arg0); end 186 | def validate_monitor(type, query, options = T.unsafe(nil)); end 187 | 188 | private 189 | 190 | def override_scope(options = T.unsafe(nil)); end 191 | end 192 | 193 | class Dogapi::ClientV2 194 | def initialize(api_key, application_key = T.unsafe(nil), host = T.unsafe(nil), device = T.unsafe(nil), silent = T.unsafe(nil), timeout = T.unsafe(nil), endpoint = T.unsafe(nil), skip_ssl_validation = T.unsafe(nil)); end 195 | 196 | def add_items_to_dashboard_list(dashboard_list_id, dashboards); end 197 | def datadog_host; end 198 | def datadog_host=(_arg0); end 199 | def delete_items_from_dashboard_list(dashboard_list_id, dashboards); end 200 | def get_items_of_dashboard_list(dashboard_list_id); end 201 | def update_items_of_dashboard_list(dashboard_list_id, dashboards); end 202 | end 203 | 204 | class Dogapi::Event 205 | def initialize(msg_text, options = T.unsafe(nil)); end 206 | 207 | def aggregation_key; end 208 | def date_happened; end 209 | def msg_text; end 210 | def msg_title; end 211 | def parent; end 212 | def priority; end 213 | def tags; end 214 | def to_hash; end 215 | end 216 | 217 | class Dogapi::EventService < ::Dogapi::Service 218 | def start(api_key, event, scope, source_type = T.unsafe(nil)); end 219 | def submit(api_key, event, scope = T.unsafe(nil), source_type = T.unsafe(nil)); end 220 | 221 | private 222 | 223 | def finish(api_key, event_id, successful = T.unsafe(nil)); end 224 | end 225 | 226 | Dogapi::EventService::API_VERSION = T.let(T.unsafe(nil), String) 227 | Dogapi::EventService::MAX_BODY_LENGTH = T.let(T.unsafe(nil), Integer) 228 | Dogapi::EventService::MAX_TITLE_LENGTH = T.let(T.unsafe(nil), Integer) 229 | 230 | class Dogapi::MetricService < ::Dogapi::Service 231 | def submit(api_key, scope, metric, points); end 232 | end 233 | 234 | Dogapi::MetricService::API_VERSION = T.let(T.unsafe(nil), String) 235 | 236 | class Dogapi::Scope 237 | def initialize(host = T.unsafe(nil), device = T.unsafe(nil)); end 238 | 239 | def device; end 240 | def host; end 241 | end 242 | 243 | class Dogapi::Service 244 | def initialize(api_key, api_host = T.unsafe(nil)); end 245 | 246 | def connect; end 247 | def request(method, url, params); end 248 | end 249 | 250 | Dogapi::USER_AGENT = T.let(T.unsafe(nil), String) 251 | class Dogapi::V1; end 252 | 253 | class Dogapi::V1::AlertService < ::Dogapi::APIService 254 | def alert(query, options = T.unsafe(nil)); end 255 | def delete_alert(alert_id); end 256 | def get_alert(alert_id); end 257 | def get_all_alerts; end 258 | def mute_alerts; end 259 | def unmute_alerts; end 260 | def update_alert(alert_id, query, options); end 261 | end 262 | 263 | Dogapi::V1::AlertService::API_VERSION = T.let(T.unsafe(nil), String) 264 | 265 | class Dogapi::V1::AwsIntegrationService < ::Dogapi::APIService 266 | def aws_integration_create(config); end 267 | def aws_integration_delete(config); end 268 | def aws_integration_generate_external_id(config); end 269 | def aws_integration_list; end 270 | def aws_integration_list_namespaces; end 271 | def aws_integration_update(config, new_config); end 272 | end 273 | 274 | Dogapi::V1::AwsIntegrationService::API_VERSION = T.let(T.unsafe(nil), String) 275 | 276 | class Dogapi::V1::AwsLogsService < ::Dogapi::APIService 277 | def aws_logs_add_lambda(config); end 278 | def aws_logs_check_lambda(config); end 279 | def aws_logs_check_services(config); end 280 | def aws_logs_integration_delete(config); end 281 | def aws_logs_integrations_list; end 282 | def aws_logs_list_services; end 283 | def aws_logs_save_services(config); end 284 | end 285 | 286 | Dogapi::V1::AwsLogsService::API_VERSION = T.let(T.unsafe(nil), String) 287 | 288 | class Dogapi::V1::AzureIntegrationService < ::Dogapi::APIService 289 | def azure_integration_create(config); end 290 | def azure_integration_delete(config); end 291 | def azure_integration_list; end 292 | def azure_integration_update(config); end 293 | def azure_integration_update_host_filters(config); end 294 | end 295 | 296 | Dogapi::V1::AzureIntegrationService::API_VERSION = T.let(T.unsafe(nil), String) 297 | 298 | class Dogapi::V1::CommentService < ::Dogapi::APIService 299 | def comment(message, options = T.unsafe(nil)); end 300 | def delete_comment(comment_id); end 301 | def update_comment(comment_id, options = T.unsafe(nil)); end 302 | end 303 | 304 | Dogapi::V1::CommentService::API_VERSION = T.let(T.unsafe(nil), String) 305 | 306 | class Dogapi::V1::DashService < ::Dogapi::APIService 307 | def create_dashboard(title, description, graphs, template_variables = T.unsafe(nil), read_only = T.unsafe(nil)); end 308 | def delete_dashboard(dash_id); end 309 | def get_dashboard(dash_id); end 310 | def get_dashboards; end 311 | def update_dashboard(dash_id, title, description, graphs, template_variables = T.unsafe(nil), read_only = T.unsafe(nil)); end 312 | end 313 | 314 | Dogapi::V1::DashService::API_VERSION = T.let(T.unsafe(nil), String) 315 | 316 | class Dogapi::V1::DashboardListService < ::Dogapi::APIService 317 | def add_items(resource_id, dashboards); end 318 | def all; end 319 | def create(name); end 320 | def delete(resource_id); end 321 | def delete_items(resource_id, dashboards); end 322 | def get(resource_id); end 323 | def get_items(resource_id); end 324 | def update(resource_id, name); end 325 | def update_items(resource_id, dashboards); end 326 | end 327 | 328 | Dogapi::V1::DashboardListService::API_VERSION = T.let(T.unsafe(nil), String) 329 | Dogapi::V1::DashboardListService::RESOURCE_NAME = T.let(T.unsafe(nil), String) 330 | Dogapi::V1::DashboardListService::SUB_RESOURCE_NAME = T.let(T.unsafe(nil), String) 331 | 332 | class Dogapi::V1::DashboardService < ::Dogapi::APIService 333 | def create_board(title, widgets, layout_type, options); end 334 | def delete_board(dashboard_id); end 335 | def get_all_boards; end 336 | def get_board(dashboard_id); end 337 | def update_board(dashboard_id, title, widgets, layout_type, options); end 338 | end 339 | 340 | Dogapi::V1::DashboardService::API_VERSION = T.let(T.unsafe(nil), String) 341 | Dogapi::V1::DashboardService::RESOURCE_NAME = T.let(T.unsafe(nil), String) 342 | 343 | class Dogapi::V1::EmbedService < ::Dogapi::APIService 344 | def create_embed(graph_json, description = T.unsafe(nil)); end 345 | def enable_embed(embed_id); end 346 | def get_all_embeds; end 347 | def get_embed(embed_id, description = T.unsafe(nil)); end 348 | def revoke_embed(embed_id); end 349 | end 350 | 351 | Dogapi::V1::EmbedService::API_VERSION = T.let(T.unsafe(nil), String) 352 | 353 | class Dogapi::V1::EventService < ::Dogapi::APIService 354 | def delete(id); end 355 | def get(id); end 356 | def post(event, scope = T.unsafe(nil)); end 357 | def stream(start, stop, options = T.unsafe(nil)); end 358 | end 359 | 360 | Dogapi::V1::EventService::API_VERSION = T.let(T.unsafe(nil), String) 361 | Dogapi::V1::EventService::MAX_BODY_LENGTH = T.let(T.unsafe(nil), Integer) 362 | Dogapi::V1::EventService::MAX_TITLE_LENGTH = T.let(T.unsafe(nil), Integer) 363 | 364 | class Dogapi::V1::GcpIntegrationService < ::Dogapi::APIService 365 | def gcp_integration_create(config); end 366 | def gcp_integration_delete(config); end 367 | def gcp_integration_list; end 368 | def gcp_integration_update(config); end 369 | end 370 | 371 | Dogapi::V1::GcpIntegrationService::API_VERSION = T.let(T.unsafe(nil), String) 372 | 373 | class Dogapi::V1::HostsService < ::Dogapi::APIService 374 | def search(options = T.unsafe(nil)); end 375 | def totals; end 376 | end 377 | 378 | Dogapi::V1::HostsService::API_VERSION = T.let(T.unsafe(nil), String) 379 | 380 | class Dogapi::V1::IntegrationService < ::Dogapi::APIService 381 | def create_integration(source_type_name, config); end 382 | def delete_integration(source_type_name); end 383 | def get_integration(source_type_name); end 384 | def update_integration(source_type_name, config); end 385 | end 386 | 387 | Dogapi::V1::IntegrationService::API_VERSION = T.let(T.unsafe(nil), String) 388 | 389 | class Dogapi::V1::LogsPipelineService < ::Dogapi::APIService 390 | def create_logs_pipeline(name, filter, options = T.unsafe(nil)); end 391 | def delete_logs_pipeline(pipeline_id); end 392 | def get_all_logs_pipelines; end 393 | def get_logs_pipeline(pipeline_id); end 394 | def update_logs_pipeline(pipeline_id, name, filter, options = T.unsafe(nil)); end 395 | end 396 | 397 | Dogapi::V1::LogsPipelineService::API_VERSION = T.let(T.unsafe(nil), String) 398 | 399 | class Dogapi::V1::MetadataService < ::Dogapi::APIService 400 | def get(metric_name); end 401 | def update(metric_name, options = T.unsafe(nil)); end 402 | end 403 | 404 | Dogapi::V1::MetadataService::API_VERSION = T.let(T.unsafe(nil), String) 405 | 406 | class Dogapi::V1::MetricService < ::Dogapi::APIService 407 | def flush_buffer; end 408 | def get(query, from, to); end 409 | def get_active_metrics(from); end 410 | def make_metric_payload(metric, points, scope, options); end 411 | def submit(*args); end 412 | def submit_to_api(metric, points, scope, options = T.unsafe(nil)); end 413 | def submit_to_buffer(metric, points, scope, options = T.unsafe(nil)); end 414 | def switch_to_batched; end 415 | def switch_to_single; end 416 | def upload(metrics); end 417 | end 418 | 419 | Dogapi::V1::MetricService::API_VERSION = T.let(T.unsafe(nil), String) 420 | 421 | class Dogapi::V1::MonitorService < ::Dogapi::APIService 422 | def can_delete_monitors(monitor_ids); end 423 | def cancel_downtime(downtime_id); end 424 | def cancel_downtime_by_scope(scope); end 425 | def delete_monitor(monitor_id, options = T.unsafe(nil)); end 426 | def get_all_downtimes(options = T.unsafe(nil)); end 427 | def get_all_monitors(options = T.unsafe(nil)); end 428 | def get_downtime(downtime_id, options = T.unsafe(nil)); end 429 | def get_monitor(monitor_id, options = T.unsafe(nil)); end 430 | def monitor(type, query, options = T.unsafe(nil)); end 431 | def mute_host(hostname, options = T.unsafe(nil)); end 432 | def mute_monitor(monitor_id, options = T.unsafe(nil)); end 433 | def mute_monitors; end 434 | def resolve_monitors(monitor_groups = T.unsafe(nil), options = T.unsafe(nil), version = T.unsafe(nil)); end 435 | def schedule_downtime(scope, options = T.unsafe(nil)); end 436 | def search_monitor_groups(options = T.unsafe(nil)); end 437 | def search_monitors(options = T.unsafe(nil)); end 438 | def unmute_host(hostname); end 439 | def unmute_monitor(monitor_id, options = T.unsafe(nil)); end 440 | def unmute_monitors; end 441 | def update_downtime(downtime_id, options = T.unsafe(nil)); end 442 | def update_monitor(monitor_id, query = T.unsafe(nil), options = T.unsafe(nil)); end 443 | def validate_monitor(type, query, options = T.unsafe(nil)); end 444 | end 445 | 446 | Dogapi::V1::MonitorService::API_VERSION = T.let(T.unsafe(nil), String) 447 | 448 | class Dogapi::V1::ScreenboardService < ::Dogapi::APIService 449 | def create_screenboard(description); end 450 | def delete_screenboard(board_id); end 451 | def get_all_screenboards; end 452 | def get_screenboard(board_id); end 453 | def revoke_screenboard(board_id); end 454 | def share_screenboard(board_id); end 455 | def update_screenboard(board_id, description); end 456 | end 457 | 458 | Dogapi::V1::ScreenboardService::API_VERSION = T.let(T.unsafe(nil), String) 459 | 460 | class Dogapi::V1::SearchService < ::Dogapi::APIService 461 | def search(query); end 462 | end 463 | 464 | Dogapi::V1::SearchService::API_VERSION = T.let(T.unsafe(nil), String) 465 | 466 | class Dogapi::V1::ServiceCheckService < ::Dogapi::APIService 467 | def service_check(check, host, status, options = T.unsafe(nil)); end 468 | end 469 | 470 | Dogapi::V1::ServiceCheckService::API_VERSION = T.let(T.unsafe(nil), String) 471 | 472 | class Dogapi::V1::ServiceLevelObjectiveService < ::Dogapi::APIService 473 | def can_delete_service_level_objective(slo_ids); end 474 | def create_service_level_objective(type, slo_name, thresholds, options = T.unsafe(nil)); end 475 | def delete_many_service_level_objective(slo_ids); end 476 | def delete_service_level_objective(slo_id); end 477 | def delete_timeframes_service_level_objective(ops); end 478 | def get_service_level_objective(slo_id); end 479 | def get_service_level_objective_history(slo_id, from_ts, to_ts); end 480 | def search_service_level_objective(slo_ids, query, offset, limit); end 481 | def update_service_level_objective(slo_id, type, options = T.unsafe(nil)); end 482 | end 483 | 484 | Dogapi::V1::ServiceLevelObjectiveService::API_VERSION = T.let(T.unsafe(nil), String) 485 | 486 | class Dogapi::V1::SnapshotService < ::Dogapi::APIService 487 | def snapshot(metric_query, start_ts, end_ts, event_query = T.unsafe(nil)); end 488 | end 489 | 490 | Dogapi::V1::SnapshotService::API_VERSION = T.let(T.unsafe(nil), String) 491 | 492 | class Dogapi::V1::SyntheticsService < ::Dogapi::APIService 493 | def create_synthetics_test(type, config, options = T.unsafe(nil)); end 494 | def delete_synthetics_tests(test_ids); end 495 | def get_all_synthetics_tests; end 496 | def get_synthetics_devices; end 497 | def get_synthetics_locations; end 498 | def get_synthetics_result(test_id, result_id); end 499 | def get_synthetics_results(test_id); end 500 | def get_synthetics_test(test_id); end 501 | def start_pause_synthetics_test(test_id, new_status); end 502 | def update_synthetics_test(test_id, type, config, options = T.unsafe(nil)); end 503 | end 504 | 505 | Dogapi::V1::SyntheticsService::API_VERSION = T.let(T.unsafe(nil), String) 506 | 507 | class Dogapi::V1::TagService < ::Dogapi::APIService 508 | def add(host_id, tags, source = T.unsafe(nil)); end 509 | def detach(host_id, source = T.unsafe(nil)); end 510 | def detatch(host_id); end 511 | def get(host_id, source = T.unsafe(nil), by_source = T.unsafe(nil)); end 512 | def get_all(source = T.unsafe(nil)); end 513 | def update(host_id, tags, source = T.unsafe(nil)); end 514 | end 515 | 516 | Dogapi::V1::TagService::API_VERSION = T.let(T.unsafe(nil), String) 517 | 518 | class Dogapi::V1::UsageService < ::Dogapi::APIService 519 | def get_custom_metrics_usage(start_hr, end_hr = T.unsafe(nil)); end 520 | def get_fargate_usage(start_hr, end_hr = T.unsafe(nil)); end 521 | def get_hosts_usage(start_hr, end_hr = T.unsafe(nil)); end 522 | def get_logs_usage(start_hr, end_hr = T.unsafe(nil)); end 523 | def get_synthetics_usage(start_hr, end_hr = T.unsafe(nil)); end 524 | def get_traces_usage(start_hr, end_hr = T.unsafe(nil)); end 525 | end 526 | 527 | Dogapi::V1::UsageService::API_VERSION = T.let(T.unsafe(nil), String) 528 | 529 | class Dogapi::V1::UserService < ::Dogapi::APIService 530 | def create_user(description = T.unsafe(nil)); end 531 | def disable_user(handle); end 532 | def get_all_users; end 533 | def get_user(handle); end 534 | def invite(emails, options = T.unsafe(nil)); end 535 | def update_user(handle, description = T.unsafe(nil)); end 536 | end 537 | 538 | Dogapi::V1::UserService::API_VERSION = T.let(T.unsafe(nil), String) 539 | class Dogapi::V2; end 540 | 541 | class Dogapi::V2::DashboardListService < ::Dogapi::APIService 542 | def add_items(resource_id, dashboards); end 543 | def delete_items(resource_id, dashboards); end 544 | def get_items(resource_id); end 545 | def update_items(resource_id, dashboards); end 546 | end 547 | 548 | Dogapi::V2::DashboardListService::API_VERSION = T.let(T.unsafe(nil), String) 549 | Dogapi::V2::DashboardListService::RESOURCE_NAME = T.let(T.unsafe(nil), String) 550 | Dogapi::V2::DashboardListService::SUB_RESOURCE_NAME = T.let(T.unsafe(nil), String) 551 | Dogapi::VERSION = T.let(T.unsafe(nil), String) 552 | -------------------------------------------------------------------------------- /docs/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "See text widget for dashboard documentation.", 3 | "layout_type": "ordered", 4 | "notify_list": [], 5 | "reflow_type": "fixed", 6 | "tags": [], 7 | "template_variable_presets": [], 8 | "template_variables": [ 9 | { 10 | "available_values": [], 11 | "default": "*", 12 | "name": "app", 13 | "prefix": "app" 14 | }, 15 | { 16 | "available_values": [], 17 | "default": "*", 18 | "name": "team", 19 | "prefix": "team" 20 | }, 21 | { 22 | "available_values": [], 23 | "default": "*", 24 | "name": "package", 25 | "prefix": "package" 26 | }, 27 | { 28 | "available_values": [], 29 | "default": "false", 30 | "name": "max_enforcements", 31 | "prefix": "max_enforcements" 32 | }, 33 | { 34 | "available_values": [], 35 | "defaults": [ 36 | "dependency", 37 | "privacy" 38 | ], 39 | "name": "violation_type", 40 | "prefix": "violation_type" 41 | } 42 | ], 43 | "title": "[Modularization] Pack Stats", 44 | "widgets": [ 45 | { 46 | "definition": { 47 | "background_color": "vivid_blue", 48 | "layout_type": "ordered", 49 | "show_title": true, 50 | "title": "Open this to see the documentation for this dashboard!", 51 | "type": "group", 52 | "widgets": [ 53 | { 54 | "definition": { 55 | "background_color": "white", 56 | "content": "# Template Variables\nAt the top, there are some template variables you can set to filter the entire dashboard by...\n\n| Key | Default | Examples |\n|---|---|---|\n| *app* (required) | `my_app` | `my_app` |\n| *max_enforcements* | `false` | `false`, `true` |\n| *package* | `*` | `packs/background_jobs` |\n| *team* | `*` | `payments` |\n| *violation_type* | `dependency`,`privacy` | `layer`,`visibility` |\n\nFor `max_enforcement`, \n - `false` means: Violation counts without changing `enforce_x` in `package.yml` files. Used to track violations we care about.\n - `true` means: Violation counts after changing `enforce_x` to `true` for all packages. Used for \"what if\" scenarios.\n\n# Pro-Tip: Split graph by variables\nClick \"split graph\" in the \"full-screen\" mode of a widget to see a graph broken up by team, violation type, or package. Here's an [example (violations over time)](https://app.datadoghq.com/dashboard/s3q-cb3-bed?fullscreen_end_ts=1683557460383&fullscreen_paused=false&fullscreen_section=split%20graph&fullscreen_start_ts=1683543060383&fullscreen_widget=452601886481639&from_ts=1683543021000&to_ts=1683557421000&live=true).\n\n# Additional Documentation\nMore information at [https://go/packs](https://docs.google.com/document/d/1OGYqV1pt1r6g6LimCDs8RSIR7hBZ7BVO1yohk2Jnu0M/edit#heading=h.4cufcvb5oqvd)\n", 57 | "font_size": "14", 58 | "has_padding": true, 59 | "show_tick": false, 60 | "text_align": "left", 61 | "tick_edge": "left", 62 | "tick_pos": "50%", 63 | "type": "note", 64 | "vertical_align": "top" 65 | }, 66 | "id": 5169947781850282, 67 | "layout": { 68 | "height": 7, 69 | "width": 12, 70 | "x": 0, 71 | "y": 0 72 | } 73 | } 74 | ] 75 | }, 76 | "id": 6370727016503428, 77 | "layout": { 78 | "height": 1, 79 | "width": 12, 80 | "x": 0, 81 | "y": 0 82 | } 83 | }, 84 | { 85 | "definition": { 86 | "background_color": "vivid_purple", 87 | "layout_type": "ordered", 88 | "show_title": true, 89 | "title": "Violations By Package", 90 | "title_align": "center", 91 | "type": "group", 92 | "widgets": [ 93 | { 94 | "definition": { 95 | "requests": [ 96 | { 97 | "change_type": "absolute", 98 | "compare_to": "hour_before", 99 | "formulas": [ 100 | { 101 | "formula": "month_before(query1)" 102 | }, 103 | { 104 | "alias": "current violation count", 105 | "formula": "query1" 106 | } 107 | ], 108 | "increase_good": false, 109 | "order_by": "change", 110 | "order_dir": "desc", 111 | "queries": [ 112 | { 113 | "aggregator": "last", 114 | "data_source": "metrics", 115 | "name": "query1", 116 | "query": "avg:modularization.by_package.violations.count{$app,$package,$team,$max_enforcements,$violation_type} by {package,violation_type}" 117 | } 118 | ], 119 | "response_format": "scalar", 120 | "show_present": true 121 | } 122 | ], 123 | "title": "Current count (left) and monthly change (right) of violations", 124 | "title_align": "left", 125 | "title_size": "16", 126 | "type": "change" 127 | }, 128 | "id": 3643760892520117, 129 | "layout": { 130 | "height": 3, 131 | "width": 12, 132 | "x": 0, 133 | "y": 0 134 | } 135 | }, 136 | { 137 | "definition": { 138 | "legend_columns": [ 139 | "value" 140 | ], 141 | "legend_layout": "vertical", 142 | "requests": [ 143 | { 144 | "display_type": "line", 145 | "formulas": [ 146 | { 147 | "alias": "violation count", 148 | "formula": "query1" 149 | } 150 | ], 151 | "queries": [ 152 | { 153 | "data_source": "metrics", 154 | "name": "query1", 155 | "query": "avg:modularization.by_package.violations.count{$app,$package,$team,$max_enforcements,$violation_type} by {package,violation_type}" 156 | } 157 | ], 158 | "response_format": "timeseries", 159 | "style": { 160 | "line_type": "solid", 161 | "line_width": "normal", 162 | "palette": "dog_classic" 163 | } 164 | } 165 | ], 166 | "show_legend": false, 167 | "title": "Violations over time", 168 | "title_align": "left", 169 | "title_size": "16", 170 | "type": "timeseries" 171 | }, 172 | "id": 452601886481639, 173 | "layout": { 174 | "height": 3, 175 | "width": 12, 176 | "x": 0, 177 | "y": 3 178 | } 179 | }, 180 | { 181 | "definition": { 182 | "requests": [ 183 | { 184 | "conditional_formats": [ 185 | { 186 | "comparator": ">", 187 | "palette": "white_on_red", 188 | "value": 0 189 | } 190 | ], 191 | "formulas": [ 192 | { 193 | "formula": "query1", 194 | "limit": { 195 | "count": 500, 196 | "order": "desc" 197 | } 198 | } 199 | ], 200 | "queries": [ 201 | { 202 | "aggregator": "last", 203 | "data_source": "metrics", 204 | "name": "query1", 205 | "query": "avg:modularization.by_package.violations.by_other_package.count{$app,$package,$team,$max_enforcements,$violation_type} by {violation_type,package,other_package}" 206 | } 207 | ], 208 | "response_format": "scalar" 209 | } 210 | ], 211 | "title": "Count of violation pairs (format is count, violation type, pack responsible for violation, other pack)", 212 | "title_align": "left", 213 | "title_size": "16", 214 | "type": "toplist" 215 | }, 216 | "id": 4999182362039404, 217 | "layout": { 218 | "height": 3, 219 | "width": 12, 220 | "x": 0, 221 | "y": 6 222 | } 223 | } 224 | ] 225 | }, 226 | "id": 6323770581286804, 227 | "layout": { 228 | "height": 1, 229 | "width": 12, 230 | "x": 0, 231 | "y": 1 232 | } 233 | }, 234 | { 235 | "definition": { 236 | "background_color": "vivid_pink", 237 | "layout_type": "ordered", 238 | "show_title": true, 239 | "title": "Violations By Team", 240 | "title_align": "center", 241 | "type": "group", 242 | "widgets": [ 243 | { 244 | "definition": { 245 | "requests": [ 246 | { 247 | "change_type": "absolute", 248 | "compare_to": "hour_before", 249 | "formulas": [ 250 | { 251 | "formula": "month_before(query1)" 252 | }, 253 | { 254 | "alias": "current violation count", 255 | "formula": "query1" 256 | } 257 | ], 258 | "increase_good": false, 259 | "order_by": "present", 260 | "order_dir": "desc", 261 | "queries": [ 262 | { 263 | "aggregator": "last", 264 | "data_source": "metrics", 265 | "name": "query1", 266 | "query": "avg:modularization.by_team.violations.count{$app,$package,$team,$max_enforcements,$violation_type} by {team,violation_type}" 267 | } 268 | ], 269 | "response_format": "scalar", 270 | "show_present": true 271 | } 272 | ], 273 | "title": "Current count (left) and monthly change (right) of violations", 274 | "title_align": "left", 275 | "title_size": "16", 276 | "type": "change" 277 | }, 278 | "id": 729312621174170, 279 | "layout": { 280 | "height": 5, 281 | "width": 12, 282 | "x": 0, 283 | "y": 0 284 | } 285 | }, 286 | { 287 | "definition": { 288 | "legend_columns": [ 289 | "avg", 290 | "min", 291 | "max", 292 | "value", 293 | "sum" 294 | ], 295 | "legend_layout": "auto", 296 | "requests": [ 297 | { 298 | "display_type": "line", 299 | "formulas": [ 300 | { 301 | "alias": "violation count", 302 | "formula": "query1" 303 | } 304 | ], 305 | "queries": [ 306 | { 307 | "data_source": "metrics", 308 | "name": "query1", 309 | "query": "avg:modularization.by_team.violations.count{$app,$package,$team,$max_enforcements,$violation_type} by {team,violation_type}" 310 | } 311 | ], 312 | "response_format": "timeseries", 313 | "style": { 314 | "line_type": "solid", 315 | "line_width": "normal", 316 | "palette": "dog_classic" 317 | } 318 | } 319 | ], 320 | "show_legend": false, 321 | "title": "Violations over time", 322 | "title_align": "left", 323 | "title_size": "16", 324 | "type": "timeseries" 325 | }, 326 | "id": 8106464030577332, 327 | "layout": { 328 | "height": 3, 329 | "width": 12, 330 | "x": 0, 331 | "y": 5 332 | } 333 | }, 334 | { 335 | "definition": { 336 | "requests": [ 337 | { 338 | "conditional_formats": [ 339 | { 340 | "comparator": ">", 341 | "palette": "white_on_red", 342 | "value": 0 343 | } 344 | ], 345 | "formulas": [ 346 | { 347 | "formula": "query1", 348 | "limit": { 349 | "count": 500, 350 | "order": "desc" 351 | } 352 | } 353 | ], 354 | "queries": [ 355 | { 356 | "aggregator": "last", 357 | "data_source": "metrics", 358 | "name": "query1", 359 | "query": "avg:modularization.by_team.violations.by_other_team.count{$app,$package,$team,$max_enforcements,$violation_type} by {violation_type,team,other_team}" 360 | } 361 | ], 362 | "response_format": "scalar" 363 | } 364 | ], 365 | "title": "Count of violation pairs (format is count, violation type, team responsible for violation, other team)", 366 | "title_align": "left", 367 | "title_size": "16", 368 | "type": "toplist" 369 | }, 370 | "id": 7551686457697865, 371 | "layout": { 372 | "height": 3, 373 | "width": 12, 374 | "x": 0, 375 | "y": 8 376 | } 377 | } 378 | ] 379 | }, 380 | "id": 8036812677312511, 381 | "layout": { 382 | "height": 1, 383 | "width": 12, 384 | "x": 0, 385 | "y": 2 386 | } 387 | }, 388 | { 389 | "definition": { 390 | "background_color": "vivid_orange", 391 | "layout_type": "ordered", 392 | "show_title": true, 393 | "title": "Packwerk Usage By Team", 394 | "title_align": "center", 395 | "type": "group", 396 | "widgets": [ 397 | { 398 | "definition": { 399 | "requests": [ 400 | { 401 | "formulas": [ 402 | { 403 | "formula": "query1", 404 | "limit": { 405 | "count": 500, 406 | "order": "desc" 407 | } 408 | } 409 | ], 410 | "queries": [ 411 | { 412 | "aggregator": "last", 413 | "data_source": "metrics", 414 | "name": "query1", 415 | "query": "avg:modularization.by_team.all_packages.count{$app,$package,$team,$max_enforcements} by {team}" 416 | } 417 | ], 418 | "response_format": "scalar" 419 | } 420 | ], 421 | "title": "Number of packs", 422 | "title_align": "left", 423 | "title_size": "16", 424 | "type": "toplist" 425 | }, 426 | "id": 2759533194955744, 427 | "layout": { 428 | "height": 3, 429 | "width": 6, 430 | "x": 0, 431 | "y": 0 432 | } 433 | }, 434 | { 435 | "definition": { 436 | "requests": [ 437 | { 438 | "formulas": [ 439 | { 440 | "alias": "% of packs enforcing violation_type", 441 | "formula": "(query1 + query3) / (query2 + query1 + query3) * 100", 442 | "limit": { 443 | "count": 500, 444 | "order": "desc" 445 | } 446 | } 447 | ], 448 | "queries": [ 449 | { 450 | "aggregator": "last", 451 | "data_source": "metrics", 452 | "name": "query1", 453 | "query": "avg:modularization.by_team.packwerk_checkers.true.count{$app,$team,$package,$max_enforcements,$violation_type} by {team,violation_type}" 454 | }, 455 | { 456 | "aggregator": "last", 457 | "data_source": "metrics", 458 | "name": "query3", 459 | "query": "avg:modularization.by_team.packwerk_checkers.strict.count{$app,$team,$package,$max_enforcements,$violation_type} by {team,violation_type}" 460 | }, 461 | { 462 | "aggregator": "last", 463 | "data_source": "metrics", 464 | "name": "query2", 465 | "query": "avg:modularization.by_team.packwerk_checkers.false.count{$app,$team,$package,$max_enforcements,$violation_type} by {team,violation_type}" 466 | } 467 | ], 468 | "response_format": "scalar" 469 | } 470 | ], 471 | "title": "% of packs using enforce_*", 472 | "title_align": "left", 473 | "title_size": "16", 474 | "type": "query_table" 475 | }, 476 | "id": 7803071284986718, 477 | "layout": { 478 | "height": 3, 479 | "width": 6, 480 | "x": 6, 481 | "y": 0 482 | } 483 | } 484 | ] 485 | }, 486 | "id": 5988093184398876, 487 | "layout": { 488 | "height": 1, 489 | "is_column_break": true, 490 | "width": 12, 491 | "x": 0, 492 | "y": 3 493 | } 494 | }, 495 | { 496 | "definition": { 497 | "background_color": "vivid_yellow", 498 | "layout_type": "ordered", 499 | "show_title": true, 500 | "title": "Overall Packs Usage", 501 | "type": "group", 502 | "widgets": [ 503 | { 504 | "definition": { 505 | "custom_links": [ 506 | { 507 | "is_hidden": true, 508 | "override_label": "containers" 509 | }, 510 | { 511 | "is_hidden": true, 512 | "override_label": "hosts" 513 | }, 514 | { 515 | "is_hidden": true, 516 | "override_label": "logs" 517 | }, 518 | { 519 | "is_hidden": true, 520 | "override_label": "traces" 521 | }, 522 | { 523 | "is_hidden": true, 524 | "override_label": "profiles" 525 | } 526 | ], 527 | "legend_columns": [ 528 | "value" 529 | ], 530 | "legend_layout": "vertical", 531 | "requests": [ 532 | { 533 | "display_type": "line", 534 | "formulas": [ 535 | { 536 | "alias": "total violations", 537 | "formula": "query1" 538 | } 539 | ], 540 | "queries": [ 541 | { 542 | "data_source": "metrics", 543 | "name": "query1", 544 | "query": "avg:modularization.all_packages.violations.count{$app,$max_enforcements,$violation_type} by {violation_type}" 545 | } 546 | ], 547 | "response_format": "timeseries", 548 | "style": { 549 | "line_type": "solid", 550 | "line_width": "normal", 551 | "palette": "dog_classic" 552 | } 553 | } 554 | ], 555 | "show_legend": true, 556 | "title": "Total violations over time", 557 | "title_align": "left", 558 | "title_size": "16", 559 | "type": "timeseries" 560 | }, 561 | "id": 6899218535842667, 562 | "layout": { 563 | "height": 3, 564 | "width": 12, 565 | "x": 0, 566 | "y": 0 567 | } 568 | }, 569 | { 570 | "definition": { 571 | "legend_columns": [ 572 | "value" 573 | ], 574 | "legend_layout": "vertical", 575 | "markers": [], 576 | "requests": [ 577 | { 578 | "display_type": "line", 579 | "formulas": [ 580 | { 581 | "alias": "% packages using enforce_violation_type: true | strict", 582 | "formula": "(query2 + query3) / query5 * 100" 583 | } 584 | ], 585 | "queries": [ 586 | { 587 | "data_source": "metrics", 588 | "name": "query2", 589 | "query": "avg:modularization.all_packages.packwerk_checkers.true.count{$app,$max_enforcements,$violation_type} by {app,violation_type}" 590 | }, 591 | { 592 | "data_source": "metrics", 593 | "name": "query5", 594 | "query": "avg:modularization.all_packages.count{$app,$max_enforcements} by {app}" 595 | }, 596 | { 597 | "data_source": "metrics", 598 | "name": "query4", 599 | "query": "avg:modularization.all_packages.packwerk_checkers.false.count{$app,$max_enforcements,$violation_type} by {app,violation_type}" 600 | }, 601 | { 602 | "data_source": "metrics", 603 | "name": "query3", 604 | "query": "avg:modularization.all_packages.packwerk_checkers.strict.count{$app,$max_enforcements,$violation_type} by {app,violation_type}" 605 | } 606 | ], 607 | "response_format": "timeseries", 608 | "style": { 609 | "line_type": "solid", 610 | "line_width": "normal", 611 | "palette": "dog_classic" 612 | } 613 | } 614 | ], 615 | "show_legend": true, 616 | "title": "% of packs using packwerk checker enforcements", 617 | "title_align": "left", 618 | "title_size": "16", 619 | "type": "timeseries", 620 | "yaxis": { 621 | "max": "100", 622 | "min": "0" 623 | } 624 | }, 625 | "id": 5484448588630899, 626 | "layout": { 627 | "height": 3, 628 | "width": 6, 629 | "x": 0, 630 | "y": 3 631 | } 632 | }, 633 | { 634 | "definition": { 635 | "legend_columns": [ 636 | "avg", 637 | "min", 638 | "max", 639 | "value", 640 | "sum" 641 | ], 642 | "legend_layout": "vertical", 643 | "markers": [], 644 | "requests": [ 645 | { 646 | "display_type": "line", 647 | "formulas": [ 648 | { 649 | "alias": "% of packages with non-empty public API", 650 | "formula": "query3 / query1 * 100" 651 | } 652 | ], 653 | "queries": [ 654 | { 655 | "data_source": "metrics", 656 | "name": "query3", 657 | "query": "avg:modularization.all_packages.using_public_directory.count{$app,$max_enforcements} by {app}" 658 | }, 659 | { 660 | "data_source": "metrics", 661 | "name": "query1", 662 | "query": "avg:modularization.all_packages.count{$app,$max_enforcements} by {app}" 663 | } 664 | ], 665 | "response_format": "timeseries", 666 | "style": { 667 | "line_type": "solid", 668 | "line_width": "normal", 669 | "palette": "dog_classic" 670 | } 671 | } 672 | ], 673 | "show_legend": false, 674 | "title": "% of packs with non-empty public API", 675 | "title_align": "left", 676 | "title_size": "16", 677 | "type": "timeseries", 678 | "yaxis": { 679 | "max": "100", 680 | "min": "0" 681 | } 682 | }, 683 | "id": 2831082575270868, 684 | "layout": { 685 | "height": 2, 686 | "width": 6, 687 | "x": 6, 688 | "y": 3 689 | } 690 | }, 691 | { 692 | "definition": { 693 | "requests": [ 694 | { 695 | "formulas": [ 696 | { 697 | "formula": "query3", 698 | "limit": { 699 | "count": 500, 700 | "order": "desc" 701 | } 702 | } 703 | ], 704 | "queries": [ 705 | { 706 | "aggregator": "last", 707 | "data_source": "metrics", 708 | "name": "query3", 709 | "query": "max:modularization.by_package.depended_on.count{$app,$package,$team} by {app,package}" 710 | } 711 | ], 712 | "response_format": "scalar" 713 | } 714 | ], 715 | "title": "Packs most depended on", 716 | "title_align": "left", 717 | "title_size": "16", 718 | "type": "toplist" 719 | }, 720 | "id": 505612504525801, 721 | "layout": { 722 | "height": 3, 723 | "width": 6, 724 | "x": 6, 725 | "y": 5 726 | } 727 | }, 728 | { 729 | "definition": { 730 | "legend_columns": [ 731 | "avg", 732 | "min", 733 | "max", 734 | "value", 735 | "sum" 736 | ], 737 | "legend_layout": "vertical", 738 | "markers": [], 739 | "requests": [ 740 | { 741 | "display_type": "line", 742 | "formulas": [ 743 | { 744 | "alias": "% of packages with README.md", 745 | "formula": "query3 / query1 * 100" 746 | } 747 | ], 748 | "queries": [ 749 | { 750 | "data_source": "metrics", 751 | "name": "query3", 752 | "query": "avg:modularization.all_packages.has_readme.count{$app,$max_enforcements} by {app}" 753 | }, 754 | { 755 | "data_source": "metrics", 756 | "name": "query1", 757 | "query": "avg:modularization.all_packages.count{$app,$max_enforcements} by {app}" 758 | } 759 | ], 760 | "response_format": "timeseries", 761 | "style": { 762 | "line_type": "solid", 763 | "line_width": "normal", 764 | "palette": "dog_classic" 765 | } 766 | } 767 | ], 768 | "show_legend": false, 769 | "title": "% of packs that have a README", 770 | "title_align": "left", 771 | "title_size": "16", 772 | "type": "timeseries", 773 | "yaxis": { 774 | "max": "100", 775 | "min": "0" 776 | } 777 | }, 778 | "id": 7370986557741715, 779 | "layout": { 780 | "height": 2, 781 | "width": 6, 782 | "x": 0, 783 | "y": 6 784 | } 785 | }, 786 | { 787 | "definition": { 788 | "custom_links": [ 789 | { 790 | "is_hidden": true, 791 | "override_label": "containers" 792 | }, 793 | { 794 | "is_hidden": true, 795 | "override_label": "hosts" 796 | }, 797 | { 798 | "is_hidden": true, 799 | "override_label": "logs" 800 | }, 801 | { 802 | "is_hidden": true, 803 | "override_label": "traces" 804 | }, 805 | { 806 | "is_hidden": true, 807 | "override_label": "profiles" 808 | } 809 | ], 810 | "legend_columns": [ 811 | "value" 812 | ], 813 | "legend_layout": "vertical", 814 | "requests": [ 815 | { 816 | "display_type": "bars", 817 | "formulas": [ 818 | { 819 | "formula": "query1 - month_before(query2)" 820 | } 821 | ], 822 | "queries": [ 823 | { 824 | "data_source": "metrics", 825 | "name": "query1", 826 | "query": "avg:modularization.all_packages.violations.count{$app,$max_enforcements,$violation_type} by {violation_type}.rollup(max, 2419200)" 827 | }, 828 | { 829 | "data_source": "metrics", 830 | "name": "query2", 831 | "query": "avg:modularization.all_packages.violations.count{$app,$max_enforcements,$violation_type} by {violation_type}.rollup(max, 2419200)" 832 | } 833 | ], 834 | "response_format": "timeseries", 835 | "style": { 836 | "line_type": "solid", 837 | "line_width": "normal", 838 | "palette": "dog_classic" 839 | } 840 | } 841 | ], 842 | "show_legend": true, 843 | "title": "Change in violations each month", 844 | "title_align": "left", 845 | "title_size": "16", 846 | "type": "timeseries" 847 | }, 848 | "id": 7600367732349558, 849 | "layout": { 850 | "height": 3, 851 | "width": 12, 852 | "x": 0, 853 | "y": 8 854 | } 855 | } 856 | ] 857 | }, 858 | "id": 3553542546106134, 859 | "layout": { 860 | "height": 1, 861 | "width": 12, 862 | "x": 0, 863 | "y": 4 864 | } 865 | } 866 | ] 867 | } 868 | --------------------------------------------------------------------------------