├── .browserslistrc ├── .github ├── dependabot.yml └── workflows │ └── rubyonrails.yml ├── .gitignore ├── .postcssrc.yml ├── .prettierrc ├── .rspec ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── Procfile ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .keep │ │ ├── gem.svg │ │ └── orange-waves.svg │ ├── javascripts │ │ └── application.js │ └── stylesheets │ │ └── application.scss ├── controllers │ ├── application_controller.rb │ ├── comparison_controller.rb │ ├── concerns │ │ └── .keep │ ├── home_controller.rb │ └── versions_controller.rb ├── helpers │ └── application_helper.rb ├── javascript │ └── packs │ │ └── application.js ├── jobs │ └── application_job.rb ├── lib │ └── date_range.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── import_status.rb │ └── stat.rb ├── views │ ├── home │ │ ├── _chart.js.erb │ │ ├── _charts.html.erb │ │ ├── _totals.js.erb │ │ └── index.html.erb │ ├── layouts │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ └── versions │ │ └── show.js.erb └── workers │ ├── import_stats_day_worker.rb │ ├── import_stats_days_worker.rb │ ├── import_stats_file_worker.rb │ └── import_stats_worker.rb ├── babel.config.js ├── bin ├── bundle ├── production ├── puma ├── rails ├── rake ├── setup ├── sidekiq ├── update ├── webpack ├── webpack-dev-server └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── aws_s3.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── generators.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── sidekiq.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── sidekiq.yml ├── webpack │ ├── development.js │ ├── environment.js │ ├── production.js │ └── test.js └── webpacker.yml ├── db ├── migrate │ ├── 20180513075234_enable_uuid.rb │ ├── 20180513075308_create_stats.rb │ ├── 20180929063650_create_import_statuses.rb │ ├── 20190708173946_add_timestamps_to_stats.rb │ └── 20190713153752_add_unique_index_to_stats.rb ├── schema.rb ├── seeds.rb └── seeds │ └── stats.csv ├── gems.locked ├── gems.rb ├── lib ├── assets │ └── .keep └── tasks │ ├── .keep │ └── temporary.rake ├── log └── .keep ├── package.json ├── postcss.config.js ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── readme_images └── design.png ├── spec ├── models │ ├── import_status_spec.rb │ └── stat_spec.rb ├── rails_helper.rb ├── spec_helper.rb ├── support │ ├── legacy_stats.json │ └── new_stats.json └── workers │ ├── import_stats_day_worker_spec.rb │ └── import_stats_worker_spec.rb ├── tmp └── .keep ├── vendor └── .keep └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | registries: 3 | rubygems-server-gems-contribsys-com: 4 | type: rubygems-server 5 | url: https://gems.contribsys.com 6 | username: "${{secrets.RUBYGEMS_SERVER_GEMS_CONTRIBSYS_COM_USERNAME}}" 7 | password: "${{secrets.RUBYGEMS_SERVER_GEMS_CONTRIBSYS_COM_PASSWORD}}" 8 | 9 | updates: 10 | - package-ecosystem: bundler 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | open-pull-requests-limit: 3 15 | registries: 16 | - rubygems-server-gems-contribsys-com 17 | - package-ecosystem: npm 18 | directory: "/" 19 | schedule: 20 | interval: daily 21 | open-pull-requests-limit: 3 22 | -------------------------------------------------------------------------------- /.github/workflows/rubyonrails.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are 2 | # provided by a third-party and are governed by separate terms of service, 3 | # privacy policy, and support documentation. 4 | # 5 | # This workflow will install a prebuilt Ruby version, install dependencies, and 6 | # run tests and linters. 7 | name: "Ruby on Rails CI" 8 | on: 9 | push: 10 | branches: [ main ] 11 | pull_request: 12 | branches: [ main ] 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | services: 17 | postgres: 18 | image: postgres:11-alpine 19 | ports: 20 | - "5432:5432" 21 | env: 22 | POSTGRES_DB: rails_test 23 | POSTGRES_USER: rails 24 | POSTGRES_PASSWORD: password 25 | env: 26 | RAILS_ENV: test 27 | DATABASE_URL: "postgres://rails:password@localhost:5432/rails_test" 28 | BUNDLE_GEMS__CONTRIBSYS__COM: "${{ secrets.BUNDLE_GEMS__CONTRIBSYS__COM }}" 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v2 32 | # Add or replace dependency steps here 33 | - name: Install Ruby and gems 34 | uses: ruby/setup-ruby@8f312efe1262fb463d906e9bf040319394c18d3e # v1.92 35 | with: 36 | bundler-cache: true 37 | # Add or replace database setup steps here 38 | - name: Set up database schema 39 | run: bin/rails db:schema:load 40 | # Add or replace test runners here 41 | - name: Run tests 42 | run: bin/rake 43 | 44 | # lint: 45 | # runs-on: ubuntu-latest 46 | # steps: 47 | # - name: Checkout code 48 | # uses: actions/checkout@v2 49 | # - name: Install Ruby and gems 50 | # uses: ruby/setup-ruby@8f312efe1262fb463d906e9bf040319394c18d3e # v1.92 51 | # with: 52 | # bundler-cache: true 53 | # # Add or replace any other lints here 54 | # - name: Security audit dependencies 55 | # run: bin/bundler-audit --update 56 | # - name: Security audit application code 57 | # run: bin/brakeman -q -w2 58 | # - name: Lint Ruby files 59 | # run: bin/rubocop --parallel 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | /node_modules 17 | /yarn-error.log 18 | 19 | /public/assets 20 | .byebug_history 21 | 22 | # Ignore master key for decrypting credentials and more. 23 | /config/master.key 24 | /public/packs 25 | /public/packs-test 26 | yarn-debug.log* 27 | .yarn-integrity 28 | -------------------------------------------------------------------------------- /.postcssrc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | postcss-import: {} 3 | postcss-cssnext: {} 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "preferSingleQuotes": false 4 | } 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.5 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | gems.rb -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | gems.locked -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bin/puma -C config/puma.rb 2 | worker: bin/sidekiq -C config/sidekiq.yml 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Ruby Ecosystem 2 | 3 | We collect metrics from Bundler and rubygems.org and graph them. 4 | 5 | ## What metrics, exactly? 6 | 7 | To start with, we will graph over time: 8 | 9 | * Request counts for Ruby and Bundler, segregated by version. 10 | * Request counts made from various platforms (eg. Linux, Windows etc) 11 | * Request counts made from various CI providers (eg. CircleCI, Jenkins etc) 12 | 13 | ## Why? 14 | 15 | Apart from being interesting to look at, this information has the potential to help: 16 | 17 | * Gem authors understand what versions of Ruby they should consider supporting 18 | * Developers make decisions on if and when they should upgrade their Ruby version 19 | * The Ruby community as a whole understand better how it is evolving and changing 20 | 21 | 22 | ## How does it work? 23 | 24 | Running `bundle install` or `gem install` makes requests to rubygems.org to download various gems. These requests also send information to rubygems.org about the current Ruby and/or Bundler version being used, the platform being used and a few other things. This infomation is logged and stored in S3. Kirby (https://github.com/rubytogether/kirby) parses this info (there's a lot of it!) and aggregates it into JSON files. 25 | 26 | The Ecosystem app retrieves this JSON information on a daily basis and graphs it. 27 | 28 | 29 | ## Where is it? 30 | 31 | Ecosystem is currently hosted on Heroku at: http://ecosystem.rubytogether.org 32 | 33 | ## WIP - Design 34 | 35 | Here's what Brendan Miller (designer at Cloud City Development) has come up with so far. We are currently working on building this page out: 36 | 37 | ![Design 1](https://raw.githubusercontent.com/rubytogether/ecosystem/sidk/update-readme/readme_images/design.png) 38 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | 8 | namespace :stats do 9 | task :import => :environment do 10 | ImportStatsWorker.perform_async 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/gem.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/assets/images/orange-waves.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's 5 | // vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require rails-ujs 14 | //= require_tree . 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap"); 2 | 3 | body { 4 | font-family: "Roboto", sans-serif; 5 | background-color: #e5e5e5; 6 | color: #475972; 7 | margin: 0; 8 | 9 | a, a:visited { 10 | color: white; 11 | } 12 | 13 | header { 14 | background: url("../images/orange-waves.svg"); 15 | color: white; 16 | padding-bottom: 85px; 17 | padding-top: 85px; 18 | text-align: center; 19 | h1 { 20 | font-weight: 600; 21 | font-size: 50px; 22 | line-height: 59px; 23 | } 24 | 25 | h2 { 26 | font-weight: 300; 27 | font-size: 32px; 28 | line-height: 40px; 29 | } 30 | } 31 | 32 | main { 33 | margin: 5% 10%; 34 | 35 | h3 { 36 | text-align: center; 37 | font-weight: 300; 38 | font-size: 30px; 39 | line-height: 35px; 40 | } 41 | 42 | p { 43 | max-width: 40em; 44 | margin: 0 auto 2em auto; 45 | } 46 | 47 | .chart-container { 48 | margin-bottom: 5%; 49 | 50 | .chart-heading { 51 | strong { 52 | font-size: 18px; 53 | line-height: 21px; 54 | color: black; 55 | } 56 | 57 | a { 58 | font-weight: 500; 59 | font-size: 14px; 60 | line-height: 16px; 61 | letter-spacing: 0.03em; 62 | text-decoration: none; 63 | margin-right: 30px; 64 | color: #acc0e6; 65 | &.active { 66 | color: #5f7195; 67 | } 68 | } 69 | 70 | display: flex; 71 | justify-content: space-between; 72 | width: 94%; 73 | padding: 0 2%; 74 | } 75 | .apexcharts-xaxis-label { 76 | fill: #8e9bb5; 77 | &._3months, 78 | &._1year { 79 | &:nth-child(even) { 80 | visibility: hidden; 81 | } 82 | } 83 | } 84 | 85 | .apexcharts-yaxis-label { 86 | fill: #8e9bb5; 87 | } 88 | } 89 | } 90 | } 91 | 92 | .hidden { 93 | display: none !important; 94 | } 95 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | rescue_from ActionController::InvalidAuthenticityToken, with: :invalid_token 3 | 4 | def invalid_token 5 | render file: Rails.public_path.join('500.html'), 6 | status: :unprocessable_entity, layout: false 7 | end 8 | 9 | def not_found 10 | render file: Rails.public_path.join('404.html'), status: :not_found, layout: false 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/comparison_controller.rb: -------------------------------------------------------------------------------- 1 | class ComparisonController < ApplicationController 2 | def show 3 | key1 = params.fetch(:key1) 4 | key2 = params.fetch(:key2) 5 | @range = DateRange.new(params) 6 | 7 | data = Stat.send(@range.prefix + "comparison", key1, key2, @range.value) 8 | 9 | dates = data.map(&:first).sort.uniq 10 | count_map = Hash.new { |h, k| h[k] = {} } 11 | data.each { |date, key, count| count_map[date][key] = count } 12 | 13 | key1_points = 14 | dates.map do |date| 15 | first = count_map.dig(date, key1) || 0 16 | second = count_map.dig(date, key2) || 0 17 | count = first - second 18 | y = count.to_f / count_map.dig(date, key1) * 100 19 | { x: date.strftime("%m/%d"), y: y } 20 | end 21 | 22 | key1_without_unique = key1.delete_suffix("_unique") 23 | key2_without_unique = key2.delete_suffix("_unique") 24 | 25 | # TODO: Make this more general 26 | key1_name = key1_without_unique == "bundler" && key2_without_unique == "ci" ? "Non-CI" : key1_without_unique 27 | key1_series = { name: key1_name, data: key1_points } 28 | 29 | key2_points = 30 | dates.map do |date| 31 | count = count_map.dig(date, key2) 32 | y = count.to_f / count_map.dig(date, key1) * 100 33 | { x: date.strftime("%m/%d"), y: y } 34 | end 35 | 36 | key2_name = key2_without_unique == "ci" ? "CI" : key2_without_unique 37 | key2_series = { name: key2_name, data: key2_points } 38 | 39 | @series = [key1_series, key2_series] 40 | 41 | @ranges_container_selector = ".comparison-#{key1}-#{key2}-graph-ranges" 42 | 43 | respond_to do |format| 44 | format.json { render json: @series } 45 | format.js { render "versions/show" } 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | return render plain: "No data!" if Stat.count.zero? 4 | 5 | suffix = params[:unique] ? "_unique" : "" 6 | 7 | @version_charts = { 8 | "Ruby" => "/versions/ruby#{suffix}", 9 | "Bundler" => "/versions/bundler#{suffix}", 10 | "RubyGems" => "/versions/rubygems#{suffix}", 11 | "Platform" => "/versions/platform#{suffix}", 12 | "CI Provider" => "/versions/ci#{suffix}" 13 | } 14 | 15 | @comparison_charts = { 16 | "Rubygems vs Bundler" => "/comparison/rubygems#{suffix}/bundler#{suffix}", 17 | "CI vs non-CI" => "/comparison/bundler#{suffix}/ci#{suffix}" 18 | } 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/versions_controller.rb: -------------------------------------------------------------------------------- 1 | class VersionsController < ApplicationController 2 | def show 3 | key = params.fetch(:key) 4 | limit = Integer(params.fetch(:limit) { DEFAULT_LIMITS[key] }).clamp(0, MAX_LIMIT) 5 | @range = DateRange.new(params) 6 | 7 | data = Stat.send(@range.prefix + "data", key, @range.value) 8 | date_totals = Stat.send(@range.prefix + "totals", key, @range.value) 9 | 10 | if params[:total] 11 | points = 12 | date_totals.map do |date, count| 13 | { x: date.strftime("%m/%d"), y: count } 14 | end 15 | @series = [{ name: "total", data: points }] 16 | @total = true 17 | @ranges_container_selector = ".ruby-total-ranges" 18 | else 19 | dates = data.map(&:first).sort.uniq 20 | count_map = count(data) 21 | 22 | # Build one series for each version, with an entry for every date 23 | # 24 | # [ {name: "2.2.2", data: [{x: "2018-07-13", y: 3932}, ...] }, ... ] 25 | versions = count_map.values.flat_map(&:keys).compact.sort.uniq 26 | top = 27 | versions.max_by(limit) do |v| 28 | count_map[count_map.keys.last][v] || 0 29 | end 30 | 31 | top_series = 32 | top.map do |version| 33 | points = 34 | dates.map do |date| 35 | count = count_map.dig(date, version) || 0 36 | y = (count.to_f / date_totals[date]) * 100 37 | { x: date.strftime("%m/%d"), y: y } 38 | end 39 | 40 | { name: "#{version}", data: points } 41 | end 42 | 43 | if top.length < versions.length 44 | the_rest = { 45 | name: "Others", 46 | data: 47 | dates.map do |date| 48 | count = count_map[date].reject { |v| v.in?(top) }.sum { |v, c| c } 49 | y = (count.to_f / date_totals[date]) * 100 50 | # TODO: Move the formatting to JS 51 | { x: date.strftime("%m/%d"), y: y } 52 | end 53 | } 54 | end 55 | 56 | @series = the_rest ? top_series.push(the_rest) : top_series 57 | @ranges_container_selector = ".versions-#{key}-graph-ranges" 58 | end 59 | 60 | respond_to do |format| 61 | format.json { render json: @series } 62 | format.js 63 | end 64 | end 65 | 66 | private 67 | 68 | DEFAULT_LIMITS = { "ruby_unique" => 7, "bundler_unique" => 7, "rubygems_unique" => 7 } 69 | DEFAULT_LIMITS.default = 5 70 | MAX_LIMIT = 25 71 | 72 | def count(data) 73 | # Build a hash of hashes we can use to look up values for a specific date 74 | count_map = Hash.new { |h, k| h[k] = {} } 75 | data.each { |date, version, count| count_map[date][version] = count } 76 | count_map 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper; end 2 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | import "stylesheets/application.scss"; 3 | 4 | import jQuery from "jquery"; 5 | import ApexCharts from "apexcharts"; 6 | import Rails from "rails-ujs"; 7 | require.context("../../assets/images", false, /\.(png|jpe?g|svg)$/); 8 | 9 | Rails.start(); 10 | 11 | window.jQuery = jQuery; 12 | window.ApexCharts = ApexCharts; 13 | window.Rails = Rails; 14 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base; end 2 | -------------------------------------------------------------------------------- /app/lib/date_range.rb: -------------------------------------------------------------------------------- 1 | class DateRange 2 | RANGES = { 3 | "1year" => Date.today - 1.year, 4 | "3months" => Date.today - 90.days, 5 | "1month" => Date.today - 30.days, 6 | "2weeks" => Date.today - 2.weeks 7 | } 8 | 9 | RANGES.default = Date.today - 90.days 10 | 11 | attr_reader :value 12 | 13 | def initialize(params) 14 | @value = RANGES[params[:range]] 15 | end 16 | 17 | def prefix 18 | @value > RANGES["1year"] ? "daily_" : "weekly_" 19 | end 20 | 21 | def key 22 | RANGES.key(@value) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/import_status.rb: -------------------------------------------------------------------------------- 1 | class ImportStatus < ApplicationRecord 2 | def self.import(key, json_body) 3 | json = JSON.parse(json_body) 4 | 5 | json.each do |date, data| 6 | create!(key: key, date: date, data: data) 7 | end 8 | end 9 | 10 | def self.fetched?(key) 11 | where(key: key).count > 0 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/models/stat.rb: -------------------------------------------------------------------------------- 1 | class Stat < ApplicationRecord 2 | def self.weekly_data(key, range) 3 | if key.in?(BUCKETS.keys) 4 | base_query(key, range).group(:week, BUCKETS[key]).pluck( 5 | week, 6 | send(BUCKETS[key]), 7 | sum 8 | ) 9 | else 10 | base_query(key, range).group(:week, :value).pluck(week, :value, sum) 11 | end 12 | end 13 | 14 | def self.weekly_totals(key, range) 15 | base_query(key, range).group(:week).pluck(week, sum).reduce( 16 | {} 17 | ) do |totals, row| 18 | totals[row[0]] = row[1] 19 | totals 20 | end 21 | end 22 | 23 | def self.daily_totals(key, range) 24 | base_query(key, range).group(:date).order(:date).sum(:count) 25 | end 26 | 27 | def self.daily_data(key, range) 28 | if key.in?(BUCKETS.keys) 29 | base_query(key, range).group(:date, BUCKETS[key]).pluck( 30 | :date, 31 | send(BUCKETS[key]), 32 | sum 33 | ) 34 | else 35 | base_query(key, range).pluck(:date, :value, :count) 36 | end 37 | end 38 | 39 | def self.daily_comparison(key1, key2, range) 40 | Stat.where("(key = ? OR key = ?) AND date > ?", key1, key2, range).where 41 | .not(value: "") 42 | .group(:date, :key) 43 | .order(:date) 44 | .pluck(:date, :key, sum) 45 | end 46 | 47 | def self.weekly_comparison(key1, key2, range) 48 | Stat.where("(key = ? OR key = ?) AND date > ?", key1, key2, range).where 49 | .not(value: "") 50 | .group(:week, :key) 51 | .pluck(week, :key, sum) 52 | end 53 | 54 | private 55 | 56 | BUCKETS = { 57 | "ruby" => :minor, 58 | "bundler" => :minor, 59 | "rubygems" => :minor, 60 | "ci" => :ci, 61 | "platform" => :platform 62 | } 63 | BUCKETS.merge!(BUCKETS.transform_keys { |k| "#{k}_unique" }) 64 | 65 | PLATFORMS = %w[linux mingw32 darwin java] 66 | 67 | def self.week 68 | Arel.sql("date_trunc('week', date) AS week") 69 | end 70 | 71 | def self.minor 72 | Arel.sql("substring(value from '\\d.\\d*') AS minor") 73 | end 74 | 75 | def self.sum 76 | Arel.sql("sum(count) AS sum") 77 | end 78 | 79 | def self.ci 80 | # values for ci are a comma separated string corresponding to 81 | # various CI related ENV vars that are set on the source system. 82 | # Here, we have a somewhat ugly case statement in SQL to figure out 83 | # which one of the values to use 84 | Arel.sql( 85 | "CASE array_length(string_to_array(value, ','), 1) 86 | WHEN 1 THEN CASE value 87 | WHEN 'ci' THEN 'unknown' 88 | ELSE value 89 | END 90 | WHEN 2 THEN CASE (string_to_array(value, ','))[1] 91 | WHEN '' THEN (string_to_array(value, ','))[2] 92 | WHEN 'ci' THEN (string_to_array(value, ','))[2] 93 | ELSE (string_to_array(value, ','))[1] 94 | END 95 | ELSE 'unknown' 96 | END 97 | AS ci" 98 | ) 99 | end 100 | 101 | def self.platform 102 | Arel.sql( 103 | "COALESCE(" + 104 | PLATFORMS.map { |p| "substring(value from '#{p}')" }.join(",") + 105 | ", 'other') AS platform" 106 | ) 107 | end 108 | 109 | def self.base_query(key, range) 110 | Stat.where("key = ? AND date > ?", key, range).where.not(value: "") 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /app/views/home/_chart.js.erb: -------------------------------------------------------------------------------- 1 | Rails.ajax({ 2 | url: "<%= path %>", 3 | type: "get", 4 | success: series => { 5 | const options = { 6 | stroke: { 7 | width: 2 8 | }, 9 | grid: { 10 | show: true, 11 | yaxis: { 12 | lines: { 13 | show: true 14 | } 15 | } 16 | }, 17 | legend: { 18 | formatter: value => value[0].toUpperCase() + value.slice(1), 19 | itemMargin: { 20 | horizontal: 20, 21 | vertical: 25 22 | } 23 | }, 24 | chart: { 25 | id: "chart-<%= path.parameterize %>", 26 | height: 400, 27 | animations: { 28 | enabled: false 29 | }, 30 | toolbar: { show: false } 31 | }, 32 | series, 33 | yaxis: { 34 | labels: { 35 | formatter: value => `${Math.round(Number(value) * 100) / 100}%`, 36 | style: { 37 | fontSize: "14px", 38 | lineHeight: "16px", 39 | fontFamily: "Roboto", 40 | fontWeight: 300 41 | } 42 | } 43 | }, 44 | xaxis: { 45 | labels: { 46 | style: { 47 | fontSize: "10px", 48 | lineHeight: "16px", 49 | fontFamily: "Roboto", 50 | fontWeight: 300, 51 | cssClass: "_3months" 52 | } 53 | } 54 | }, 55 | colors: [ 56 | "#1ABC9C", 57 | "#F1C40F", 58 | "#3498DB", 59 | "#E74C3C", 60 | "#9B59B6", 61 | "#34495E" 62 | ], 63 | dataLabels: { enabled: false }, 64 | tooltip: { 65 | custom: ({ 66 | series, 67 | seriesIndex, 68 | w: { 69 | config: { colors }, 70 | globals: { seriesNames } 71 | }, 72 | dataPointIndex 73 | }) => { 74 | return [...series] 75 | .sort((a, b) => b[dataPointIndex] - a[dataPointIndex]) 76 | .map((s, i) => { 77 | const sIndex = series.indexOf(s); 78 | return ` 79 |
80 | 83 |
84 |
85 | ${ 86 | seriesNames[sIndex] 87 | }: 88 | ${Math.round( 89 | Number(s[dataPointIndex]) * 100 90 | ) / 100}% 91 |
92 |
93 |
`; 94 | }) 95 | .join("\n"); 96 | } 97 | } 98 | }; 99 | new ApexCharts( 100 | document.querySelector("#<%= path.parameterize %>-graph"), 101 | options 102 | ).render(); 103 | document 104 | .querySelector(".<%= path.parameterize %>-graph-ranges") 105 | .parentElement.classList.toggle("hidden"); 106 | } 107 | }); 108 | -------------------------------------------------------------------------------- /app/views/home/_charts.html.erb: -------------------------------------------------------------------------------- 1 | <% charts.each do | name, path | %> 2 |
3 | 12 | <%= content_tag :div, id: "#{path.parameterize}-graph" do %> 13 | <% end %> 14 |
15 | 16 | <% end %> 17 | 18 | -------------------------------------------------------------------------------- /app/views/home/_totals.js.erb: -------------------------------------------------------------------------------- 1 | Rails.ajax({ 2 | url: "/versions/<%= CGI.escape(key) %>?total=true", 3 | type: "get", 4 | success: series => { 5 | const options = { 6 | stroke: { 7 | width: 2 8 | }, 9 | chart: { 10 | id: "totals-chart-versions-<%= CGI.escape(key) %>", 11 | height: 400, 12 | type: "bar", 13 | animations: { enabled: false } 14 | }, 15 | series, 16 | xaxis: { 17 | labels: { 18 | style: { 19 | fontSize: "10px", 20 | lineHeight: "16px", 21 | fontFamily: "Roboto", 22 | fontWeight: 300, 23 | cssClass: "_3months" 24 | } 25 | } 26 | }, 27 | colors: [ 28 | "#3498DB", 29 | ], 30 | dataLabels: { enabled: false } 31 | }; 32 | new ApexCharts( 33 | document.querySelector("#<%= key %>-total"), 34 | options 35 | ).render(); 36 | document 37 | .querySelector(".ruby-total-ranges") 38 | .parentElement.classList.toggle("hidden"); 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 |

Version breakdowns

2 |

3 | The version graphs try to count each run of gem install or bundle install exactly one time. However, since each of those commands can trigger dozens or even hundreds of HTTP requests, these results are not necessarily accurate. After trying to keep only one entry per install command, we group each minor version together because graphing every patch version just makes a mess. 4 |

5 | <%= render "charts", charts: @version_charts %> 6 | 7 |

Traffic Comparisons

8 |

9 | Group all traffic for relative comparison. In these graphs, "RubyGems" means gem install while "Bundler" means bundle install, and "Unknown" means Bundler found a value in the CI environment variable, but no other variable indicating the specific CI environment. 10 |

11 | <%= render "charts", charts: @comparison_charts %> 12 | 13 |

Traffic Totals

14 |

15 | The count of requests whose logs were fully processed and counted on a given day. All other charts are percentages relative to the total, while this chart shows the absolute number of data points available on a given day. 16 |

17 |
18 | 27 | <%= content_tag :div, id: "ruby-total" do %> 28 | <% end %> 29 |
30 | 31 | 37 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RubyGems.org stats 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | <%= stylesheet_pack_tag 'application', media: 'all' %> 9 | <%= javascript_pack_tag 'application' %> 10 | 11 | 12 | 13 |
14 | <%= image_pack_tag "gem.svg" %> 15 |

RubyGems.org stats

16 |

17 | Logs produced by rubygems.org, 18 | processed by kirby, and 19 | displayed by ecosystem. 20 |
21 | You can explore the most recent data yourself on Honeycomb. 22 |

23 |

24 | Coded by @indirect 25 | and @sidk, 26 | hosted by Ruby Together. 27 |

28 |
29 |
30 | <%= yield %> 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/versions/show.js.erb: -------------------------------------------------------------------------------- 1 | ApexCharts.exec( 2 | '<%= @total ? "totals-" : "" %>chart-<%= request.path.parameterize %>', 3 | "updateSeries", 4 | JSON.parse(`<%= raw(@series.to_json) %>`), 5 | true 6 | ); 7 | ApexCharts.exec( 8 | '<%= @total ? "totals-" : "" %>chart-<%= request.path.parameterize %>', 9 | "updateOptions", 10 | { 11 | xaxis: { 12 | labels: { 13 | style: { 14 | cssClass: "_<%= @range.key %>" 15 | } 16 | } 17 | } 18 | } 19 | ); 20 | 21 | Array 22 | .from(document.querySelectorAll("<%= @ranges_container_selector %> a")) 23 | .forEach(link => link.classList.remove("active")) 24 | document 25 | .querySelector("<%= @ranges_container_selector %>") 26 | .querySelector("[class=\"<%= @range.key %>\"]") 27 | .classList.add("active") 28 | -------------------------------------------------------------------------------- /app/workers/import_stats_day_worker.rb: -------------------------------------------------------------------------------- 1 | class ImportStatsDayWorker 2 | include Sidekiq::Worker 3 | 4 | def perform(date) 5 | import_status_ids = [] 6 | stats = Hash.new { |h, k| h[k] = Hash.new(0) } 7 | 8 | ImportStatus.where(date: date).find_each do |is| 9 | import_status_ids.push(is.id) 10 | 11 | is.data.each do |name, vv| 12 | vv.each do |version, count| 13 | if count.is_a?(Hash) 14 | stats[name][version] += count["total"] 15 | stats["#{name}_unique"][version] += count["unique"] 16 | else 17 | stats[name][version] += count 18 | end 19 | end 20 | end 21 | end 22 | 23 | ImportStatus.transaction do 24 | ImportStatus.where(id: import_status_ids).update_all( 25 | imported_at: Time.now 26 | ) 27 | 28 | Stat.bulk_insert( 29 | update_duplicates: %w[date key value], ignore: false 30 | ) do |t| 31 | stats.each do |name, value_map| 32 | value_map.each do |value, count| 33 | t.add(date: date, key: name, value: value, count: count) 34 | end 35 | end 36 | end 37 | end 38 | 39 | Rails.logger.info "Finished importing stats for #{date}" 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/workers/import_stats_days_worker.rb: -------------------------------------------------------------------------------- 1 | class ImportStatsDaysWorker 2 | include Sidekiq::Worker 3 | 4 | def perform 5 | dates = ImportStatus.where("imported_at IS NULL").pluck(:date).uniq 6 | dates -= [Time.now.utc.to_date] 7 | 8 | Sidekiq::Client.push_bulk( 9 | "class" => "ImportStatsDayWorker", "args" => dates.map { |d| [d] } 10 | ) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/workers/import_stats_file_worker.rb: -------------------------------------------------------------------------------- 1 | class ImportStatsFileWorker 2 | include Sidekiq::Worker 3 | 4 | def perform(key) 5 | return if ImportStatus.fetched?(key) 6 | 7 | s3 = Aws::S3::Client.new 8 | bucket = Rails.application.config.stats.bucket_name 9 | 10 | body = s3.get_object(bucket: bucket, key: key).body.read 11 | ImportStatus.import(key, body) 12 | Rails.logger.info("Downloaded #{key}") 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/workers/import_stats_worker.rb: -------------------------------------------------------------------------------- 1 | class ImportStatsWorker 2 | include Sidekiq::Worker 3 | 4 | def perform 5 | s3 = Aws::S3::Client.new 6 | bucket_name = Rails.application.config.stats.bucket_name 7 | prefix = Rails.application.config.stats.prefix 8 | 9 | last_key = ImportStatus.order("key COLLATE \"C\" DESC"). 10 | limit(1).pluck(:key).first 11 | keys = s3.list_objects_v2( 12 | bucket: bucket_name, prefix: prefix, start_after: last_key 13 | ).contents.map(&:key) 14 | 15 | Rails.logger.info "Found #{keys.size} pending stats files" 16 | 17 | Sidekiq::Client.push_bulk( 18 | "class" => "ImportStatsFileWorker", "args" => keys.map { |k| [k] } 19 | ) 20 | 21 | if keys.size == 1000 22 | Rails.logger.info "Re-running #{self.class} in 5 minutes" 23 | self.class.perform_in(5.minutes) 24 | else 25 | Rails.logger.info "Running ImportStatsDaysWorker in 5 minutes" 26 | ImportStatsDaysWorker.perform_in(5.minutes) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | var validEnv = ["development", "test", "production"]; 3 | var currentEnv = api.env(); 4 | var isDevelopmentEnv = api.env("development"); 5 | var isProductionEnv = api.env("production"); 6 | var isTestEnv = api.env("test"); 7 | 8 | if (!validEnv.includes(currentEnv)) { 9 | throw new Error( 10 | "Please specify a valid `NODE_ENV` or " + 11 | '`BABEL_ENV` environment variables. Valid values are "development", ' + 12 | '"test", and "production". Instead, received: ' + 13 | JSON.stringify(currentEnv) + 14 | "." 15 | ); 16 | } 17 | 18 | return { 19 | presets: [ 20 | isTestEnv && [ 21 | require("@babel/preset-env").default, 22 | { 23 | targets: { 24 | node: "current" 25 | } 26 | } 27 | ], 28 | (isProductionEnv || isDevelopmentEnv) && [ 29 | require("@babel/preset-env").default, 30 | { 31 | forceAllTransforms: true, 32 | useBuiltIns: "entry", 33 | corejs: 3, 34 | modules: false, 35 | exclude: ["transform-typeof-symbol"] 36 | } 37 | ] 38 | ].filter(Boolean), 39 | plugins: [ 40 | require("babel-plugin-macros"), 41 | require("@babel/plugin-syntax-dynamic-import").default, 42 | isTestEnv && require("babel-plugin-dynamic-import-node"), 43 | require("@babel/plugin-transform-destructuring").default, 44 | [ 45 | require("@babel/plugin-proposal-class-properties").default, 46 | { 47 | loose: true 48 | } 49 | ], 50 | [ 51 | require("@babel/plugin-proposal-object-rest-spread").default, 52 | { 53 | useBuiltIns: true 54 | } 55 | ], 56 | [ 57 | require("@babel/plugin-transform-runtime").default, 58 | { 59 | helpers: false, 60 | regenerator: true, 61 | corejs: false 62 | } 63 | ], 64 | [ 65 | require("@babel/plugin-transform-regenerator").default, 66 | { 67 | async: false 68 | } 69 | ] 70 | ].filter(Boolean) 71 | }; 72 | }; 73 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 || ">= 0.a" 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../gems.rb", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= begin 65 | env_var_version || cli_arg_version || 66 | lockfile_version || "#{Gem::Requirement.default}.a" 67 | end 68 | end 69 | 70 | def load_bundler! 71 | ENV["BUNDLE_GEMFILE"] ||= gemfile 72 | 73 | # must dup string for RG < 1.8 compatibility 74 | activate_bundler(bundler_version.dup) 75 | end 76 | 77 | def activate_bundler(bundler_version) 78 | if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0") 79 | bundler_version = "< 2" 80 | end 81 | gem_error = activation_error_handling do 82 | gem "bundler", bundler_version 83 | end 84 | return if gem_error.nil? 85 | require_error = activation_error_handling do 86 | require "bundler/version" 87 | end 88 | return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 89 | warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`" 90 | exit 42 91 | end 92 | 93 | def activation_error_handling 94 | yield 95 | nil 96 | rescue StandardError, LoadError => e 97 | e 98 | end 99 | end 100 | 101 | m.load_bundler! 102 | 103 | if m.invoked_as_script? 104 | load Gem.bin_path("bundler", "bundle") 105 | end 106 | -------------------------------------------------------------------------------- /bin/production: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | HEROKU_APP=rt-ecosystem exec "${HEROKU_COMMAND:-heroku}" "$@" 3 | -------------------------------------------------------------------------------- /bin/puma: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'puma' 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("../../gems.rb", 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("puma", "puma") 30 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:setup' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /bin/sidekiq: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sidekiq' 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("../../gems.rb", 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("sidekiq", "sidekiq") 30 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /bin/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "rubygems" 11 | require "bundler/setup" 12 | 13 | require "webpacker" 14 | require "webpacker/webpack_runner" 15 | 16 | APP_ROOT = File.expand_path("..", __dir__) 17 | Dir.chdir(APP_ROOT) do 18 | Webpacker::WebpackRunner.run(ARGV) 19 | end 20 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "rubygems" 11 | require "bundler/setup" 12 | 13 | require "webpacker" 14 | require "webpacker/dev_server_runner" 15 | 16 | APP_ROOT = File.expand_path("..", __dir__) 17 | Dir.chdir(APP_ROOT) do 18 | Webpacker::DevServerRunner.run(ARGV) 19 | end 20 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | # require "active_storage/engine" 9 | require "action_controller/railtie" 10 | require "action_mailer/railtie" 11 | require "action_view/railtie" 12 | # require "action_cable/engine" 13 | # require "sprockets/railtie" 14 | # require "rails/test_unit/railtie" 15 | 16 | # Require the gems listed in Gemfile, including any gems 17 | # you've limited to :test, :development, or :production. 18 | Bundler.require(*Rails.groups) 19 | 20 | module Ecosystem 21 | class Application < Rails::Application 22 | config.load_defaults 5.2 23 | 24 | # Settings in config/environments/* take precedence over those specified here. 25 | # Application configuration can go into files in config/initializers 26 | # -- all .rb files in that directory are automatically loaded after loading 27 | # the framework and any gems in your application. 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | hqjgPubl7eseaj/twsGO2pIw8U0Z24mQcutC6KrAeyXIu6KbeCUrgvKcRQVnEbbDSNSsIWTcMrkAWuIlv/YM8wxYQz/Zxv3/ICV7PYOc14iN1Fnxle+MoBEIZ16scPEHgg3L520+27uEjrOzul9tiA3aW/HcrhuSoMJMSd3i3FlpdbjhwakpFQLBM5MNTftlKzF/kpapUAULjkze6FcPrHm5u3tkxtgAZnPWYJ7I5JbIQSLq5cPgzj8jcSW99zCHt8V19+/qi99D7pjCUJFx6RD6IMv0isiuZEfyPuiy1yUMYnzT+mqIqnD8Fr97VR+jIefItpNSTMFAYRxvBppNXxZFQvwCWnXRi3Lfxjmy7OnkuMD7K9Oez0KMPYpnTdCEfq2z7iaUoc96ipMbDWGtwRzEg0NzP5i8BdDf--Q18ccBuAiTlZYAoi--RLFLKBnSuweW/iGSpTor6g== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: unicode 4 | pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + 1 %> 5 | 6 | development: 7 | <<: *default 8 | database: ecosystem 9 | 10 | test: 11 | <<: *default 12 | database: ecosystem_test 13 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Verifies that versions and hashed value of the package contents in the project's package.json 3 | config.webpacker.check_yarn_integrity = true 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join("tmp", "caching-dev.txt").exist? 20 | config.action_controller.perform_caching = true 21 | 22 | config.cache_store = :memory_store 23 | config.public_file_server.headers = { 24 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 25 | } 26 | else 27 | config.action_controller.perform_caching = false 28 | 29 | config.cache_store = :null_store 30 | end 31 | 32 | # Don't care if the mailer can't send. 33 | config.action_mailer.raise_delivery_errors = false 34 | 35 | config.action_mailer.perform_caching = false 36 | 37 | # Print deprecation notices to the Rails logger. 38 | config.active_support.deprecation = :log 39 | 40 | # Raise an error on page load if there are pending migrations. 41 | config.active_record.migration_error = :page_load 42 | 43 | # Highlight code that triggered database queries in logs. 44 | config.active_record.verbose_query_logs = true 45 | 46 | # Raises error for missing translations 47 | # config.action_view.raise_on_missing_translations = true 48 | 49 | # Use an evented file watcher to asynchronously detect changes in source code, 50 | # routes, locales, etc. This feature depends on the listen gem. 51 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 52 | end 53 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Verifies that versions and hashed value of the package contents in the project's package.json 3 | config.webpacker.check_yarn_integrity = false 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 26 | 27 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 28 | # config.action_controller.asset_host = 'http://assets.example.com' 29 | 30 | # Specifies the header that your server uses for sending files. 31 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 32 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 33 | 34 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 35 | # config.force_ssl = true 36 | 37 | # Use the lowest log level to ensure availability of diagnostic information 38 | # when problems arise. 39 | config.log_level = :info 40 | 41 | # Prepend all log lines with the following tags. 42 | config.log_tags = %i[request_id] 43 | 44 | # Use a different cache store in production. 45 | # config.cache_store = :mem_cache_store 46 | 47 | # Use a real queuing backend for Active Job (and separate queues per environment) 48 | # config.active_job.queue_adapter = :resque 49 | # config.active_job.queue_name_prefix = "ecosystem_#{Rails.env}" 50 | 51 | config.action_mailer.perform_caching = false 52 | 53 | # Ignore bad email addresses and do not raise email delivery errors. 54 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 55 | # config.action_mailer.raise_delivery_errors = false 56 | 57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 58 | # the I18n.default_locale when a translation cannot be found). 59 | config.i18n.fallbacks = true 60 | 61 | # Send deprecation notices to registered listeners. 62 | config.active_support.deprecation = :notify 63 | 64 | # Use default logging formatter so that PID and timestamp are not suppressed. 65 | config.log_formatter = ::Logger::Formatter.new 66 | 67 | # Use a different logger for distributed setups. 68 | # require 'syslog/logger' 69 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 70 | 71 | if ENV["RAILS_LOG_TO_STDOUT"].present? 72 | logger = ActiveSupport::Logger.new(STDOUT) 73 | logger.formatter = config.log_formatter 74 | config.logger = ActiveSupport::TaggedLogging.new(logger) 75 | config.lograge.enabled = true 76 | end 77 | 78 | # Do not dump schema after migrations. 79 | config.active_record.dump_schema_after_migration = false 80 | end 81 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | config.action_mailer.perform_caching = false 32 | 33 | # Tell Action Mailer not to deliver emails to the real world. 34 | # The :test delivery method accumulates sent emails in the 35 | # ActionMailer::Base.deliveries array. 36 | config.action_mailer.delivery_method = :test 37 | 38 | # Print deprecation notices to the stderr. 39 | config.active_support.deprecation = :stderr 40 | 41 | # Raises error for missing translations 42 | # config.action_view.raise_on_missing_translations = true 43 | end 44 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/aws_s3.rb: -------------------------------------------------------------------------------- 1 | require "aws-sdk-core" 2 | 3 | Aws.config.update(logger: ::Rails.logger) 4 | 5 | Rails.application.config.stats = 6 | OpenStruct.new( 7 | bucket_name: 8 | ENV.fetch("STATS_BUCKET_NAME", "rubygems-logs-staging.rubytogether"), 9 | prefix: ENV.fetch("STATS_BUCKET_PREFIX", "fastly_stats/") 10 | ) 11 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | Rails.application.config.content_security_policy do |policy| 8 | policy.default_src :self, :https 9 | policy.font_src :self, :https, :data 10 | policy.img_src :self, :https, :data 11 | policy.object_src :none 12 | policy.script_src :self, :https, "'unsafe-inline'" 13 | policy.style_src :self, :https, "'unsafe-inline' fonts.googleapis.com" 14 | 15 | # Specify URI for violation reports 16 | # policy.report_uri "/csp-violation-report-endpoint" 17 | 18 | if Rails.env.development? 19 | policy.connect_src :self, 20 | :https, 21 | "http://localhost:3036", 22 | "ws://localhost:3035" 23 | policy.style_src :self, :blob, "'unsafe-inline' fonts.googleapis.com" 24 | end 25 | end 26 | 27 | # If you are using UJS then enable automatic nonce generation 28 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 29 | 30 | # Report CSP violations to a specified URI 31 | # For further information see the following documentation: 32 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 33 | # Rails.application.config.content_security_policy_report_only = true 34 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += %i[password] 5 | -------------------------------------------------------------------------------- /config/initializers/generators.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.generators do |g| 2 | g.orm :active_record, primary_key_type: :uuid 3 | end 4 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/sidekiq.rb: -------------------------------------------------------------------------------- 1 | Sidekiq.configure_server do |config| 2 | config.redis = { 3 | url: ENV["REDIS_URL"], 4 | ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } 5 | } 6 | end 7 | 8 | Sidekiq.configure_client do |config| 9 | config.redis = { 10 | url: ENV["REDIS_URL"], 11 | ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE } 12 | } 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) { wrap_parameters format: %i[json] } 8 | 9 | # To enable root element in JSON for ActiveRecord objects. 10 | # ActiveSupport.on_load(:active_record) do 11 | # self.include_root_in_json = true 12 | # end 13 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3_000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. 30 | # 31 | # preload_app! 32 | 33 | # Allow puma to be restarted by `rails restart` command. 34 | plugin :tmp_restart 35 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | get "versions/:key" => "versions#show" 3 | get "comparison/:key1/:key2" => "comparison#show" 4 | 5 | if Rails.env.development? 6 | require "sidekiq/pro/web" 7 | mount Sidekiq::Web => "/sidekiq" 8 | end 9 | 10 | root "home#index" 11 | match "*missing", to: "application#not_found", via: :all 12 | end 13 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 5 3 | :timeout: 8 4 | 5 | :queues: 6 | - critical 7 | - default 8 | - low 9 | 10 | production: 11 | :concurrency: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> 12 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || "development"; 2 | 3 | const environment = require("./environment"); 4 | 5 | module.exports = environment.toWebpackConfig(); 6 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require("@rails/webpacker"); 2 | 3 | module.exports = environment; 4 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || "production"; 2 | 3 | const environment = require("./environment"); 4 | 5 | module.exports = environment.toWebpackConfig(); 6 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || "development"; 2 | 3 | const environment = require("./environment"); 4 | 5 | module.exports = environment.toWebpackConfig(); 6 | -------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: packs 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/cache/webpacker 9 | check_yarn_integrity: false 10 | webpack_compile_output: false 11 | 12 | # Additional paths webpack should lookup modules 13 | # ['app/assets', 'engine/foo/app/assets'] 14 | resolved_paths: ['app/assets'] 15 | 16 | # Reload manifest.json on all requests so we reload latest compiled packs 17 | cache_manifest: false 18 | 19 | # Extract and emit a css file 20 | extract_css: false 21 | 22 | static_assets_extensions: 23 | - .jpg 24 | - .jpeg 25 | - .png 26 | - .gif 27 | - .tiff 28 | - .ico 29 | - .svg 30 | - .eot 31 | - .otf 32 | - .ttf 33 | - .woff 34 | - .woff2 35 | 36 | extensions: 37 | - .mjs 38 | - .js 39 | - .sass 40 | - .scss 41 | - .css 42 | - .module.sass 43 | - .module.scss 44 | - .module.css 45 | - .png 46 | - .svg 47 | - .gif 48 | - .jpeg 49 | - .jpg 50 | 51 | development: 52 | <<: *default 53 | compile: true 54 | 55 | # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules 56 | check_yarn_integrity: true 57 | 58 | # Reference: https://webpack.js.org/configuration/dev-server/ 59 | dev_server: 60 | https: false 61 | host: localhost 62 | port: 3035 63 | public: localhost:3035 64 | hmr: false 65 | # Inline should be set to true if using HMR 66 | inline: true 67 | overlay: true 68 | compress: true 69 | disable_host_check: true 70 | use_local_ip: false 71 | quiet: false 72 | headers: 73 | 'Access-Control-Allow-Origin': '*' 74 | watch_options: 75 | ignored: '**/node_modules/**' 76 | 77 | 78 | test: 79 | <<: *default 80 | compile: true 81 | 82 | # Compile test packs to a separate directory 83 | public_output_path: packs-test 84 | 85 | production: 86 | <<: *default 87 | 88 | # Production depends on precompilation of packs prior to booting for performance. 89 | compile: false 90 | 91 | # Extract and emit a css file 92 | extract_css: true 93 | 94 | # Cache manifest.json for performance 95 | cache_manifest: true 96 | -------------------------------------------------------------------------------- /db/migrate/20180513075234_enable_uuid.rb: -------------------------------------------------------------------------------- 1 | class EnableUuid < ActiveRecord::Migration[5.2] 2 | def change 3 | enable_extension "pgcrypto" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20180513075308_create_stats.rb: -------------------------------------------------------------------------------- 1 | class CreateStats < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :stats, id: :uuid do |t| 4 | t.date :date, null: false 5 | t.string :key, null: false 6 | t.string :value, null: false 7 | t.integer :count, null: false, default: 0 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20180929063650_create_import_statuses.rb: -------------------------------------------------------------------------------- 1 | class CreateImportStatuses < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :import_statuses, id: :uuid do |t| 4 | t.string :key, unique: true, null: false 5 | t.date :date, null: false 6 | t.timestamp :imported_at 7 | t.jsonb :data 8 | t.timestamps 9 | 10 | t.index :key 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20190708173946_add_timestamps_to_stats.rb: -------------------------------------------------------------------------------- 1 | class AddTimestampsToStats < ActiveRecord::Migration[5.2] 2 | def change 3 | add_timestamps :stats, null: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190713153752_add_unique_index_to_stats.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueIndexToStats < ActiveRecord::Migration[5.2] 2 | def change 3 | add_index :stats, %i[date key value], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2019_07_13_153752) do 14 | # These are extensions that must be enabled in order to support this database 15 | enable_extension "pgcrypto" 16 | enable_extension "plpgsql" 17 | 18 | create_table "import_statuses", 19 | id: :uuid, 20 | default: -> { "gen_random_uuid()" }, 21 | force: :cascade do |t| 22 | t.string "key", null: false 23 | t.date "date", null: false 24 | t.datetime "imported_at" 25 | t.jsonb "data" 26 | t.datetime "created_at", null: false 27 | t.datetime "updated_at", null: false 28 | t.index %w[key], name: "index_import_statuses_on_key" 29 | end 30 | 31 | create_table "stats", 32 | id: :uuid, 33 | default: -> { "gen_random_uuid()" }, 34 | force: :cascade do |t| 35 | t.date "date", null: false 36 | t.string "key", null: false 37 | t.string "value", null: false 38 | t.integer "count", default: 0, null: false 39 | t.datetime "created_at" 40 | t.datetime "updated_at" 41 | t.index %w[date key value], 42 | name: "index_stats_on_date_and_key_and_value", unique: true 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | require "csv" 2 | 3 | data_file = File.expand_path("seeds/stats.csv", __dir__) 4 | 5 | created_at = Time.now 6 | 7 | stats = [] 8 | CSV.foreach(data_file) { |row| stats.push([[created_at.year, *row[1..2]].join("-"), *row[3..-1]]) } 9 | stats.shift 10 | 11 | stat_values = 12 | stats.map do |s| 13 | s.push(created_at, created_at) 14 | "(" + s.map { |v| "'#{v}'" }.join(", ") + ")" 15 | end.join(", ") 16 | 17 | insert = 18 | "INSERT INTO stats 19 | (date, value, count, key, created_at, updated_at) 20 | VALUES #{ 21 | stat_values 22 | }" 23 | Stat.connection.execute(insert) # remove header row 24 | -------------------------------------------------------------------------------- /gems.locked: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/jamis/bulk_insert.git 3 | revision: 3ded6b059af8d239ca0f5fbecca0de6138d49592 4 | specs: 5 | bulk_insert (1.7.0) 6 | activerecord (>= 3.2.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | remote: https://gems.contribsys.com/ 11 | specs: 12 | actioncable (5.2.3) 13 | actionpack (= 5.2.3) 14 | nio4r (~> 2.0) 15 | websocket-driver (>= 0.6.1) 16 | actionmailer (5.2.3) 17 | actionpack (= 5.2.3) 18 | actionview (= 5.2.3) 19 | activejob (= 5.2.3) 20 | mail (~> 2.5, >= 2.5.4) 21 | rails-dom-testing (~> 2.0) 22 | actionpack (5.2.3) 23 | actionview (= 5.2.3) 24 | activesupport (= 5.2.3) 25 | rack (~> 2.0) 26 | rack-test (>= 0.6.3) 27 | rails-dom-testing (~> 2.0) 28 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 29 | actionview (5.2.3) 30 | activesupport (= 5.2.3) 31 | builder (~> 3.1) 32 | erubi (~> 1.4) 33 | rails-dom-testing (~> 2.0) 34 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 35 | activejob (5.2.3) 36 | activesupport (= 5.2.3) 37 | globalid (>= 0.3.6) 38 | activemodel (5.2.3) 39 | activesupport (= 5.2.3) 40 | activerecord (5.2.3) 41 | activemodel (= 5.2.3) 42 | activesupport (= 5.2.3) 43 | arel (>= 9.0) 44 | activestorage (5.2.3) 45 | actionpack (= 5.2.3) 46 | activerecord (= 5.2.3) 47 | marcel (~> 0.3.1) 48 | activesupport (5.2.3) 49 | concurrent-ruby (~> 1.0, >= 1.0.2) 50 | i18n (>= 0.7, < 2) 51 | minitest (~> 5.1) 52 | tzinfo (~> 1.1) 53 | apexcharts (0.1.5) 54 | smart_kv (= 0.2.7) 55 | arel (9.0.0) 56 | aws-eventstream (1.0.3) 57 | aws-partitions (1.193.0) 58 | aws-sdk-core (3.61.1) 59 | aws-eventstream (~> 1.0, >= 1.0.2) 60 | aws-partitions (~> 1.0) 61 | aws-sigv4 (~> 1.1) 62 | jmespath (~> 1.0) 63 | aws-sdk-kms (1.24.0) 64 | aws-sdk-core (~> 3, >= 3.61.1) 65 | aws-sigv4 (~> 1.1) 66 | aws-sdk-s3 (1.46.0) 67 | aws-sdk-core (~> 3, >= 3.61.1) 68 | aws-sdk-kms (~> 1) 69 | aws-sigv4 (~> 1.1) 70 | aws-sigv4 (1.1.0) 71 | aws-eventstream (~> 1.0, >= 1.0.2) 72 | bindex (0.5.0) 73 | bootsnap (1.4.6) 74 | msgpack (~> 1.0) 75 | builder (3.2.3) 76 | byebug (11.0.0) 77 | coderay (1.1.2) 78 | concurrent-ruby (1.1.5) 79 | connection_pool (2.2.2) 80 | crass (1.0.4) 81 | diff-lcs (1.3) 82 | erubi (1.8.0) 83 | ffi (1.15.4) 84 | globalid (0.4.2) 85 | activesupport (>= 4.2.0) 86 | honeybadger (4.11.0) 87 | i18n (1.6.0) 88 | concurrent-ruby (~> 1.0) 89 | jmespath (1.4.0) 90 | listen (3.1.5) 91 | rb-fsevent (~> 0.9, >= 0.9.4) 92 | rb-inotify (~> 0.9, >= 0.9.7) 93 | ruby_dep (~> 1.2) 94 | lograge (0.11.2) 95 | actionpack (>= 4) 96 | activesupport (>= 4) 97 | railties (>= 4) 98 | request_store (~> 1.0) 99 | loofah (2.2.3) 100 | crass (~> 1.0.2) 101 | nokogiri (>= 1.5.9) 102 | mail (2.7.1) 103 | mini_mime (>= 0.1.1) 104 | marcel (0.3.3) 105 | mimemagic (~> 0.3.2) 106 | method_source (0.9.2) 107 | mimemagic (0.3.10) 108 | nokogiri (~> 1) 109 | rake 110 | mini_mime (1.0.2) 111 | mini_portile2 (2.6.1) 112 | minitest (5.11.3) 113 | msgpack (1.3.3) 114 | nio4r (2.5.4) 115 | nokogiri (1.12.5) 116 | mini_portile2 (~> 2.6.1) 117 | racc (~> 1.4) 118 | nokogiri (1.12.5-x86_64-darwin) 119 | racc (~> 1.4) 120 | pg (1.1.4) 121 | pry (0.12.2) 122 | coderay (~> 1.1.0) 123 | method_source (~> 0.9.0) 124 | pry-byebug (3.7.0) 125 | byebug (~> 11.0) 126 | pry (~> 0.10) 127 | pry-rails (0.3.9) 128 | pry (>= 0.10.4) 129 | puma (4.3.6) 130 | nio4r (~> 2.0) 131 | racc (1.6.0) 132 | rack (2.0.8) 133 | rack-protection (2.0.7) 134 | rack 135 | rack-proxy (0.6.5) 136 | rack 137 | rack-test (1.1.0) 138 | rack (>= 1.0, < 3) 139 | rails (5.2.3) 140 | actioncable (= 5.2.3) 141 | actionmailer (= 5.2.3) 142 | actionpack (= 5.2.3) 143 | actionview (= 5.2.3) 144 | activejob (= 5.2.3) 145 | activemodel (= 5.2.3) 146 | activerecord (= 5.2.3) 147 | activestorage (= 5.2.3) 148 | activesupport (= 5.2.3) 149 | bundler (>= 1.3.0) 150 | railties (= 5.2.3) 151 | sprockets-rails (>= 2.0.0) 152 | rails-dom-testing (2.0.3) 153 | activesupport (>= 4.2.0) 154 | nokogiri (>= 1.6) 155 | rails-html-sanitizer (1.2.0) 156 | loofah (~> 2.2, >= 2.2.2) 157 | railties (5.2.3) 158 | actionpack (= 5.2.3) 159 | activesupport (= 5.2.3) 160 | method_source 161 | rake (>= 0.8.7) 162 | thor (>= 0.19.0, < 2.0) 163 | rake (12.3.3) 164 | rb-fsevent (0.10.3) 165 | rb-inotify (0.9.10) 166 | ffi (>= 0.5.0, < 2) 167 | redis (4.1.2) 168 | request_store (1.5.0) 169 | rack (>= 1.4) 170 | rspec-core (3.8.2) 171 | rspec-support (~> 3.8.0) 172 | rspec-expectations (3.8.4) 173 | diff-lcs (>= 1.2.0, < 2.0) 174 | rspec-support (~> 3.8.0) 175 | rspec-mocks (3.8.1) 176 | diff-lcs (>= 1.2.0, < 2.0) 177 | rspec-support (~> 3.8.0) 178 | rspec-rails (3.8.2) 179 | actionpack (>= 3.0) 180 | activesupport (>= 3.0) 181 | railties (>= 3.0) 182 | rspec-core (~> 3.8.0) 183 | rspec-expectations (~> 3.8.0) 184 | rspec-mocks (~> 3.8.0) 185 | rspec-support (~> 3.8.0) 186 | rspec-support (3.8.2) 187 | ruby_dep (1.5.0) 188 | sidekiq (5.2.7) 189 | connection_pool (~> 2.2, >= 2.2.2) 190 | rack (>= 1.5.0) 191 | rack-protection (>= 1.5.0) 192 | redis (>= 3.3.5, < 5) 193 | sidekiq-pro (4.0.5) 194 | concurrent-ruby (>= 1.0.5) 195 | sidekiq (>= 5.2.1) 196 | smart_kv (0.2.7) 197 | sprockets (3.7.2) 198 | concurrent-ruby (~> 1.0) 199 | rack (> 1, < 3) 200 | sprockets-rails (3.2.1) 201 | actionpack (>= 4.0) 202 | activesupport (>= 4.0) 203 | sprockets (>= 3.0.0) 204 | thor (0.20.3) 205 | thread_safe (0.3.6) 206 | tzinfo (1.2.5) 207 | thread_safe (~> 0.1) 208 | web-console (3.7.0) 209 | actionview (>= 5.0) 210 | activemodel (>= 5.0) 211 | bindex (>= 0.4.0) 212 | railties (>= 5.0) 213 | webpacker (4.0.7) 214 | activesupport (>= 4.2) 215 | rack-proxy (>= 0.6.1) 216 | railties (>= 4.2) 217 | websocket-driver (0.7.1) 218 | websocket-extensions (>= 0.1.0) 219 | websocket-extensions (0.1.4) 220 | 221 | PLATFORMS 222 | ruby 223 | x86_64-darwin-17 224 | 225 | DEPENDENCIES 226 | apexcharts 227 | aws-sdk-s3 (~> 1.46) 228 | bootsnap (>= 1.1.0) 229 | bulk_insert! 230 | concurrent-ruby 231 | connection_pool 232 | honeybadger (~> 4.11) 233 | listen (>= 3.0.5, < 3.2) 234 | lograge (~> 0.11.2) 235 | pg (>= 0.18, < 2.0) 236 | pry-byebug 237 | pry-rails (~> 0.3.9) 238 | puma (~> 4.1) 239 | rack 240 | rack-protection 241 | rails (~> 5.2.3) 242 | redis 243 | rspec-rails (~> 3.8) 244 | sidekiq (~> 5.2) 245 | sidekiq-pro! 246 | web-console (>= 3.3.0) 247 | webpacker (~> 4.0) 248 | 249 | RUBY VERSION 250 | ruby 2.7.5p203 251 | 252 | BUNDLED WITH 253 | 2.1.4 254 | -------------------------------------------------------------------------------- /gems.rb: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby Pathname.new(".ruby-version").expand_path(__dir__).read 5 | 6 | gem "rails", "~> 5.2.3" 7 | 8 | gem "apexcharts" 9 | gem "aws-sdk-s3", "~> 1.46" 10 | gem "bootsnap", ">= 1.1.0", require: false 11 | gem "bulk_insert", git: "https://github.com/jamis/bulk_insert.git" 12 | gem "honeybadger", "~> 4.11" 13 | gem "lograge", "~> 0.11.2" 14 | gem "pg", ">= 0.18", "< 2.0" 15 | gem "puma", "~> 4.1" 16 | gem "sidekiq", "~> 5.2" 17 | gem "webpacker", "~> 4.0" 18 | 19 | # Gems in both rubygems.org and gems.contribsys.com 20 | gem "redis" 21 | gem "connection_pool" 22 | gem "rack-protection" 23 | gem "concurrent-ruby" 24 | gem "rack" 25 | 26 | group :development, :test do 27 | gem "pry-byebug", require: false 28 | gem "pry-rails", "~> 0.3.9" 29 | gem "rspec-rails", "~> 3.8" 30 | end 31 | 32 | group :development do 33 | gem "web-console", ">= 3.3.0" 34 | gem "listen", ">= 3.0.5", "< 3.2" 35 | end 36 | 37 | source "https://gems.contribsys.com/" do 38 | gem "sidekiq-pro" 39 | end 40 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/lib/tasks/.keep -------------------------------------------------------------------------------- /lib/tasks/temporary.rake: -------------------------------------------------------------------------------- 1 | namespace :temporary do 2 | desc "one-off tasks" 3 | task destroy_duplicate_stats: :environment do 4 | # delete all duplicate rows preserving the row with the maximum count 5 | Stat.where.not(id: Stat.order(:date, :key, :value, "count desc").select("distinct on (date, key, value) id")).delete_all 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecosystem", 3 | "private": true, 4 | "dependencies": { 5 | "@rails/webpacker": "^4.0.7", 6 | "apexcharts": "^3.8.4", 7 | "cssnext": "^1.8.4", 8 | "jquery": "^3.3.1", 9 | "postcss-cssnext": "^3.1.0", 10 | "rails-ujs": "^5.2.3" 11 | }, 12 | "devDependencies": { 13 | "@prettier/plugin-ruby": "^0.15.0", 14 | "eslint": "^6.4.0", 15 | "eslint-config-prettier": "^6.0.0", 16 | "eslint-config-standard": "^14.1.0", 17 | "eslint-plugin-import": "^2.18.2", 18 | "eslint-plugin-node": "^9.2.0", 19 | "eslint-plugin-prettier": "^3.1.1", 20 | "eslint-plugin-promise": "^4.2.1", 21 | "eslint-plugin-standard": "^4.0.1", 22 | "prettier": "^1.18.2", 23 | "webpack-dev-server": "^3.8.1" 24 | }, 25 | "engines": { 26 | "node": "14.x" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require("postcss-import"), 4 | require("postcss-flexbugs-fixes"), 5 | require("postcss-preset-env")({ 6 | autoprefixer: { 7 | flexbox: "no-2009" 8 | }, 9 | stage: 3 10 | }) 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /readme_images/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/readme_images/design.png -------------------------------------------------------------------------------- /spec/models/import_status_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe ImportStatus, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/stat_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Stat, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require 'spec_helper' 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require File.expand_path('../../config/environment', __FILE__) 5 | # Prevent database truncation if the environment is production 6 | abort("The Rails environment is running in production mode!") if Rails.env.production? 7 | require 'rspec/rails' 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | # 18 | # The following line is provided for convenience purposes. It has the downside 19 | # of increasing the boot-up time by auto-requiring all files in the support 20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 21 | # require only the support files necessary. 22 | # 23 | # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f } 24 | 25 | # Checks for pending migrations and applies them before tests are run. 26 | # If you are not using ActiveRecord, you can remove these lines. 27 | begin 28 | ActiveRecord::Migration.maintain_test_schema! 29 | rescue ActiveRecord::PendingMigrationError => e 30 | puts e.to_s.strip 31 | exit 1 32 | end 33 | RSpec.configure do |config| 34 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 35 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 36 | 37 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 38 | # examples within a transaction, remove the following line or assign false 39 | # instead of true. 40 | config.use_transactional_fixtures = true 41 | 42 | # RSpec Rails can automatically mix in different behaviours to your tests 43 | # based on their file location, for example enabling you to call `get` and 44 | # `post` in specs under `spec/controllers`. 45 | # 46 | # You can disable this behaviour by removing the line below, and instead 47 | # explicitly tag your specs with their type, e.g.: 48 | # 49 | # RSpec.describe UsersController, :type => :controller do 50 | # # ... 51 | # end 52 | # 53 | # The different available types are documented in the features, such as in 54 | # https://relishapp.com/rspec/rspec-rails/docs 55 | config.infer_spec_type_from_file_location! 56 | 57 | # Filter lines from Rails gems in backtraces. 58 | config.filter_rails_from_backtrace! 59 | # arbitrary gems may also be filtered via: 60 | # config.filter_gems_from_backtrace("gem name") 61 | end 62 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | =begin 50 | # This allows you to limit a spec run to individual examples or groups 51 | # you care about by tagging them with `:focus` metadata. When nothing 52 | # is tagged with `:focus`, all examples get run. RSpec also provides 53 | # aliases for `it`, `describe`, and `context` that include `:focus` 54 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 55 | config.filter_run_when_matching :focus 56 | 57 | # Allows RSpec to persist some state between runs in order to support 58 | # the `--only-failures` and `--next-failure` CLI options. We recommend 59 | # you configure your source control system to ignore this file. 60 | config.example_status_persistence_file_path = "spec/examples.txt" 61 | 62 | # Limits the available syntax to the non-monkey patched syntax that is 63 | # recommended. For more details, see: 64 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 65 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 66 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 67 | config.disable_monkey_patching! 68 | 69 | # Many RSpec users commonly either run the entire suite or an individual 70 | # file, and it's useful to allow more verbose output when running an 71 | # individual spec file. 72 | if config.files_to_run.one? 73 | # Use the documentation formatter for detailed output, 74 | # unless a formatter has already been configured 75 | # (e.g. via a command-line flag). 76 | config.default_formatter = "doc" 77 | end 78 | 79 | # Print the 10 slowest examples and example groups at the 80 | # end of the spec run, to help surface which specs are running 81 | # particularly slow. 82 | config.profile_examples = 10 83 | 84 | # Run specs in random order to surface order dependencies. If you find an 85 | # order dependency and want to debug it, you can fix the order by providing 86 | # the seed, which is printed after each run. 87 | # --seed 1234 88 | config.order = :random 89 | 90 | # Seed global randomization in this process using the `--seed` CLI option. 91 | # Setting this allows you to use `--seed` to deterministically reproduce 92 | # test failures related to randomization by passing the same `--seed` value 93 | # as the one that triggered the failure. 94 | Kernel.srand config.seed 95 | =end 96 | end 97 | -------------------------------------------------------------------------------- /spec/support/legacy_stats.json: -------------------------------------------------------------------------------- 1 | { 2 | "2022-01-31":{ 3 | "bundler":{ 4 | "1.12.5":120, 5 | "1.15.4":5, 6 | "1.16.1":33, 7 | "1.16.2":1, 8 | "1.16.6":8, 9 | "1.17.2":4, 10 | "1.17.3":7, 11 | "1.9.10":1, 12 | "2.1.4":13, 13 | "2.2.15":6, 14 | "2.2.16":2, 15 | "2.2.22":3, 16 | "2.2.33":1, 17 | "2.2.7":1, 18 | "2.3.6":3 19 | }, 20 | "ci":{ 21 | "ci":3, 22 | "circle,ci":1, 23 | "gitlab,ci":2 24 | }, 25 | "gemstash":{ 26 | 27 | }, 28 | "platform":{ 29 | "aarch64-linux":2, 30 | "universal-darwin-20":4, 31 | "universal-java-1.8":1, 32 | "universal-java-11":4, 33 | "x86_64-Oracle Corporation-linux":2, 34 | "x86_64-darwin-19":3, 35 | "x86_64-darwin-21":1, 36 | "x86_64-linux":152, 37 | "x86_64-pc-linux":14, 38 | "x86_64-pc-linux-gnu":185, 39 | "x86_64-w64-mingw32":7 40 | }, 41 | "ruby":{ 42 | "1.8.7":1, 43 | "1.9.3":4, 44 | "2.0.0":9, 45 | "2.1.10":1, 46 | "2.1.6":7, 47 | "2.3.0":8, 48 | "2.3.1":21, 49 | "2.3.3":12, 50 | "2.3.5":13, 51 | "2.3.6":97, 52 | "2.4.10":3, 53 | "2.4.3":8, 54 | "2.4.4":5, 55 | "2.4.5":11, 56 | "2.4.7":5, 57 | "2.4.9":1, 58 | "2.5.1":12, 59 | "2.5.3":42, 60 | "2.5.5":16, 61 | "2.5.7":14, 62 | "2.5.8":2, 63 | "2.5.9":2, 64 | "2.6.2":1, 65 | "2.6.3":8, 66 | "2.6.4":1, 67 | "2.6.5":17, 68 | "2.6.6":5, 69 | "2.6.7":3, 70 | "2.6.8":3, 71 | "2.7.0":4, 72 | "2.7.1":5, 73 | "2.7.2":11, 74 | "2.7.3":3, 75 | "2.7.4":2, 76 | "2.7.5":9, 77 | "3.0.1":5, 78 | "3.0.2":3, 79 | "3.0.3":1 80 | }, 81 | "rubygems":{ 82 | "1.3.7":1, 83 | "1.8.23":1, 84 | "2.0.14.1":9, 85 | "2.4.8":7, 86 | "2.5.2":2, 87 | "2.5.2.1":13, 88 | "2.6.10":4, 89 | "2.6.11":3, 90 | "2.6.13":7, 91 | "2.6.14":116, 92 | "2.6.14.4":9, 93 | "2.6.6":11, 94 | "2.6.7":1, 95 | "2.6.8":8, 96 | "2.7.10":1, 97 | "2.7.11":1, 98 | "2.7.6":52, 99 | "2.7.6.2":9, 100 | "2.7.6.3":2, 101 | "2.7.7":2, 102 | "2.7.9":24, 103 | "3.0.3":37, 104 | "3.0.3.1":5, 105 | "3.0.6":4, 106 | "3.0.8":1, 107 | "3.1.2":9, 108 | "3.1.4":10, 109 | "3.1.6":13, 110 | "3.2.15":5, 111 | "3.2.20":1, 112 | "3.2.22":3, 113 | "3.2.29":1, 114 | "3.2.32":1, 115 | "3.2.7":1, 116 | "3.3.6":1 117 | }, 118 | "server_region":{ 119 | 120 | }, 121 | "tls_cipher":{ 122 | "":2, 123 | "AES128-GCM":3, 124 | "AES256-GCM":54, 125 | "ECDHE-RSA-AES128-GCM-SHA256":321, 126 | "ECDHE-RSA-AES128-SHA256":1 127 | } 128 | }, 129 | "2022-02-01":{ 130 | "bundler":{ 131 | "1.10.6":10, 132 | "1.11.2":79, 133 | "1.12.5":29054, 134 | "1.13.5":11, 135 | "1.13.6":18, 136 | "1.13.7":41, 137 | "1.14.3":1, 138 | "1.14.4":1, 139 | "1.14.5":1, 140 | "1.14.6":68, 141 | "1.15.0":6, 142 | "1.15.1":61, 143 | "1.15.3":3, 144 | "1.15.4":1890, 145 | "1.16.0":15, 146 | "1.16.1":5638, 147 | "1.16.2":149, 148 | "1.16.3":8, 149 | "1.16.5":39, 150 | "1.16.6":2506, 151 | "1.17.1":16, 152 | "1.17.2":2148, 153 | "1.17.3":2863, 154 | "1.17.3.ol.0.1.0":1, 155 | "1.5.3":5, 156 | "1.7.3":2, 157 | "1.7.4":3, 158 | "1.7.5":1, 159 | "1.9.10":10, 160 | "1.9.4":1, 161 | "2.0.1":16, 162 | "2.0.2":21, 163 | "2.1.1":1, 164 | "2.1.2":17, 165 | "2.1.4":2763, 166 | "2.2.0":2, 167 | "2.2.10":1, 168 | "2.2.11":10, 169 | "2.2.12":1, 170 | "2.2.13":1, 171 | "2.2.15":573, 172 | "2.2.16":33, 173 | "2.2.17":17, 174 | "2.2.18":1, 175 | "2.2.19":8, 176 | "2.2.2":3, 177 | "2.2.20":64, 178 | "2.2.21":2, 179 | "2.2.22":755, 180 | "2.2.23":2, 181 | "2.2.24":14, 182 | "2.2.25":11, 183 | "2.2.26":45, 184 | "2.2.27":57, 185 | "2.2.28":37, 186 | "2.2.29":48, 187 | "2.2.3":15, 188 | "2.2.30":19, 189 | "2.2.31":116, 190 | "2.2.32":408, 191 | "2.2.33":389, 192 | "2.2.4":3, 193 | "2.2.5":4, 194 | "2.2.6":5, 195 | "2.2.7":2, 196 | "2.2.8":34, 197 | "2.2.9":10, 198 | "2.3.0":3, 199 | "2.3.1":1, 200 | "2.3.2":2, 201 | "2.3.3":23, 202 | "2.3.4":52, 203 | "2.3.5":33, 204 | "2.3.6":204, 205 | "2.4.0.dev":2 206 | }, 207 | "ci":{ 208 | "aws-code-build,ci":3, 209 | "ci":479, 210 | "circle,ci":206, 211 | "circle-ci":1, 212 | "gitlab,ci":47, 213 | "jenkins":72, 214 | "jenkins,ci":84, 215 | "semaphore,ci":24, 216 | "travis,ci":10 217 | }, 218 | "gemstash":{ 219 | 220 | }, 221 | "platform":{ 222 | "aarch64-apple-darwin21.2.0":1, 223 | "aarch64-linux":305, 224 | "aarch64-linux-android":2, 225 | "aarch64-unknown-linux":16, 226 | "aarch64-unknown-linux-gnu":135, 227 | "aarch64-unknown-linux-musl":3, 228 | "amd64-freebsd-12":1, 229 | "arm-apple-darwin20.2.0":1, 230 | "arm-apple-darwin20.6.0":3, 231 | "arm-apple-darwin21.2.0":2, 232 | "arm-apple-darwin21.3.0":1, 233 | "arm-linux":11, 234 | "arm-linux-androideabi":1, 235 | "arm64-apple-darwin20":27, 236 | "arm64-apple-darwin21":16, 237 | "arm64-darwin-20":5, 238 | "arm64-darwin-21":11, 239 | "armv7l-linux":3, 240 | "armv7l-linux-eabihf":4, 241 | "armv7l-unknown-linux-eabihf":2, 242 | "armv7l-unknown-linux-gnueabihf":3, 243 | "i686-pc-linux-gnu":1, 244 | "i686-w64-mingw32":14, 245 | "powerpc-ibm-aix6.1.0.0":8, 246 | "powerpc-ibm-aix7.1.5.0":5, 247 | "powerpc64le-unknown-linux":1, 248 | "sparc-sun-solaris2.11":9, 249 | "universal-darwin-16":6, 250 | "universal-darwin-17":15, 251 | "universal-darwin-18":1, 252 | "universal-darwin-19":52, 253 | "universal-darwin-20":325, 254 | "universal-darwin-21":44, 255 | "universal-java-1.8":167, 256 | "universal-java-11":751, 257 | "x64-mingw32":358, 258 | "x64-w64-mingw32":252, 259 | "x86-linux":6, 260 | "x86-mingw32":258, 261 | "x86-solaris-2.10":6, 262 | "x86_64-AdoptOpenJDK-linux":42, 263 | "x86_64-Amazon.com Inc.-linux":1, 264 | "x86_64-Azul Systems, Inc.-linux":4, 265 | "x86_64-Eclipse Adoptium-linux":17, 266 | "x86_64-Oracle Corporation-linux":37, 267 | "x86_64-Private Build-linux":3, 268 | "x86_64-Red Hat, Inc.-linux":8, 269 | "x86_64-Temurin-linux":3, 270 | "x86_64-Ubuntu-linux":10, 271 | "x86_64-alpine-linux-musl":17, 272 | "x86_64-apple-darwin13.4.0":1, 273 | "x86_64-apple-darwin14.5.0":12, 274 | "x86_64-apple-darwin15.6.0":8, 275 | "x86_64-apple-darwin16.7.0":5, 276 | "x86_64-apple-darwin17.7.0":5, 277 | "x86_64-apple-darwin18":3, 278 | "x86_64-apple-darwin18.6.0":5, 279 | "x86_64-apple-darwin18.7.0":2, 280 | "x86_64-apple-darwin19":65, 281 | "x86_64-apple-darwin19.3.0":1, 282 | "x86_64-apple-darwin19.4.0":2, 283 | "x86_64-apple-darwin19.5.0":1, 284 | "x86_64-apple-darwin19.6.0":18, 285 | "x86_64-apple-darwin20":69, 286 | "x86_64-apple-darwin20.1.0":1, 287 | "x86_64-apple-darwin20.5.0":3, 288 | "x86_64-apple-darwin20.6.0":31, 289 | "x86_64-apple-darwin21":9, 290 | "x86_64-apple-darwin21.1.0":9, 291 | "x86_64-apple-darwin21.2.0":5, 292 | "x86_64-apple-darwin21.3.0":1, 293 | "x86_64-cygwin":5, 294 | "x86_64-darwin-13":15, 295 | "x86_64-darwin-14":4, 296 | "x86_64-darwin-15":10, 297 | "x86_64-darwin-17":1, 298 | "x86_64-darwin-18":16, 299 | "x86_64-darwin-19":164, 300 | "x86_64-darwin-20":109, 301 | "x86_64-darwin-21":21, 302 | "x86_64-koji-linux-gnu":15, 303 | "x86_64-linux":29383, 304 | "x86_64-linux-musl":57, 305 | "x86_64-pc-linux":2471, 306 | "x86_64-pc-linux-gnu":43658, 307 | "x86_64-pc-linux-musl":61, 308 | "x86_64-redhat-linux":4, 309 | "x86_64-redhat-linux-gnu":6, 310 | "x86_64-solaris-2.11":4, 311 | "x86_64-unknown-freebsd10.3":43, 312 | "x86_64-unknown-freebsd11.3":1, 313 | "x86_64-unknown-freebsd11.4":2, 314 | "x86_64-unknown-linux":1, 315 | "x86_64-unknown-linux-gnu":89, 316 | "x86_64-w64-mingw32":3228 317 | }, 318 | "ruby":{ 319 | "1.8.7":52, 320 | "1.9.3":379, 321 | "2.0.0":1456, 322 | "2.1.0":4, 323 | "2.1.1":2, 324 | "2.1.10":266, 325 | "2.1.2":4, 326 | "2.1.3":11, 327 | "2.1.4":52, 328 | "2.1.5":34, 329 | "2.1.6":1254, 330 | "2.1.7":2, 331 | "2.1.8":292, 332 | "2.1.9":140, 333 | "2.2.0":21, 334 | "2.2.10":15, 335 | "2.2.2":50, 336 | "2.2.3":18, 337 | "2.2.4":46, 338 | "2.2.5":2, 339 | "2.2.6":13, 340 | "2.2.7":39, 341 | "2.2.8":1, 342 | "2.2.9":3, 343 | "2.3.0":972, 344 | "2.3.1":7730, 345 | "2.3.3":2588, 346 | "2.3.4":118, 347 | "2.3.5":3484, 348 | "2.3.6":21210, 349 | "2.3.7":72, 350 | "2.3.8":199, 351 | "2.4.0":25, 352 | "2.4.1":204, 353 | "2.4.10":398, 354 | "2.4.2":736, 355 | "2.4.3":1045, 356 | "2.4.4":926, 357 | "2.4.5":1503, 358 | "2.4.6":11, 359 | "2.4.7":1023, 360 | "2.4.9":85, 361 | "2.5.0":52, 362 | "2.5.1":3621, 363 | "2.5.2":2, 364 | "2.5.3":6836, 365 | "2.5.4":9, 366 | "2.5.5":2378, 367 | "2.5.7":2390, 368 | "2.5.8":1072, 369 | "2.5.9":264, 370 | "2.6.0":16, 371 | "2.6.1":236, 372 | "2.6.2":59, 373 | "2.6.3":1524, 374 | "2.6.4":96, 375 | "2.6.5":5671, 376 | "2.6.6":1358, 377 | "2.6.7":651, 378 | "2.6.8":844, 379 | "2.6.9":179, 380 | "2.7.0":518, 381 | "2.7.1":1082, 382 | "2.7.2":1827, 383 | "2.7.3":788, 384 | "2.7.4":845, 385 | "2.7.5":2089, 386 | "3.0.0":18, 387 | "3.0.1":560, 388 | "3.0.2":768, 389 | "3.0.3":308, 390 | "3.1.0":50, 391 | "3.2.0":2 392 | }, 393 | "rubygems":{ 394 | "1.3.7":46, 395 | "1.6.2":2, 396 | "1.8.11":3, 397 | "1.8.23":85, 398 | "1.8.23.2":1, 399 | "1.8.24":15, 400 | "1.8.28":1, 401 | "1.8.29":17, 402 | "1.8.30":1, 403 | "2.0.14":27, 404 | "2.0.14.1":1280, 405 | "2.1.11":36, 406 | "2.2.0":1, 407 | "2.2.2":104, 408 | "2.2.5":86, 409 | "2.4.3":2, 410 | "2.4.4":241, 411 | "2.4.5":3, 412 | "2.4.5.1":41, 413 | "2.4.5.2":1, 414 | "2.4.5.3":3, 415 | "2.4.5.4":3, 416 | "2.4.5.5":6, 417 | "2.4.7":45, 418 | "2.4.8":954, 419 | "2.4.8.1.acquia":9, 420 | "2.5.1":248, 421 | "2.5.2":473, 422 | "2.5.2.1":2666, 423 | "2.5.2.2":2, 424 | "2.5.2.3":140, 425 | "2.6.0":1, 426 | "2.6.10":1300, 427 | "2.6.11":732, 428 | "2.6.12":188, 429 | "2.6.13":2844, 430 | "2.6.14":24933, 431 | "2.6.14.1":37, 432 | "2.6.14.3":4, 433 | "2.6.14.4":1248, 434 | "2.6.2":1, 435 | "2.6.3":46, 436 | "2.6.4":83, 437 | "2.6.6":1454, 438 | "2.6.7":622, 439 | "2.6.8":4030, 440 | "2.7.0":202, 441 | "2.7.10":106, 442 | "2.7.11":197, 443 | "2.7.3":222, 444 | "2.7.4":4, 445 | "2.7.5":2, 446 | "2.7.6":9481, 447 | "2.7.6.1":1, 448 | "2.7.6.2":1341, 449 | "2.7.6.3":211, 450 | "2.7.7":267, 451 | "2.7.8":499, 452 | "2.7.9":5552, 453 | "3.0.0":86, 454 | "3.0.1":56, 455 | "3.0.2":9, 456 | "3.0.3":7521, 457 | "3.0.3.1":1172, 458 | "3.0.4":715, 459 | "3.0.6":793, 460 | "3.0.8":335, 461 | "3.0.9":78, 462 | "3.1.1":1, 463 | "3.1.2":1573, 464 | "3.1.3":144, 465 | "3.1.4":1831, 466 | "3.1.6":3173, 467 | "3.2.11":11, 468 | "3.2.12":1, 469 | "3.2.15":565, 470 | "3.2.16":16, 471 | "3.2.17":13, 472 | "3.2.18":1, 473 | "3.2.19":1, 474 | "3.2.2":15, 475 | "3.2.20":441, 476 | "3.2.21":1, 477 | "3.2.22":777, 478 | "3.2.24":5, 479 | "3.2.25":2, 480 | "3.2.26":4, 481 | "3.2.27":11, 482 | "3.2.28":9, 483 | "3.2.29":6, 484 | "3.2.3":21, 485 | "3.2.30":10, 486 | "3.2.31":100, 487 | "3.2.32":495, 488 | "3.2.33":20, 489 | "3.2.4":1, 490 | "3.2.5":51, 491 | "3.2.6":1, 492 | "3.2.7":9, 493 | "3.2.8":29, 494 | "3.3.0":2, 495 | "3.3.1":1, 496 | "3.3.3":47, 497 | "3.3.4":63, 498 | "3.3.5":53, 499 | "3.3.6":177, 500 | "3.4.0.dev":2 501 | }, 502 | "server_region":{ 503 | 504 | }, 505 | "tls_cipher":{ 506 | "":464, 507 | "AES128-GCM":1625, 508 | "AES256-GCM":10072, 509 | "ECDHE-RSA-AES128-GCM-SHA256":72325, 510 | "ECDHE-RSA-AES128-SHA":101, 511 | "ECDHE-RSA-AES128-SHA256":93 512 | } 513 | } 514 | } -------------------------------------------------------------------------------- /spec/support/new_stats.json: -------------------------------------------------------------------------------- 1 | { 2 | "2022-01-31": { 3 | "bundler": { 4 | "1.12.5": { 5 | "total": 120, 6 | "unique": 53 7 | }, 8 | "1.15.4": { 9 | "total": 5, 10 | "unique": 2 11 | }, 12 | "1.16.1": { 13 | "total": 33, 14 | "unique": 4 15 | }, 16 | "1.16.2": { 17 | "total": 1, 18 | "unique": 1 19 | }, 20 | "1.16.6": { 21 | "total": 8, 22 | "unique": 4 23 | }, 24 | "1.17.2": { 25 | "total": 4, 26 | "unique": 1 27 | }, 28 | "1.17.3": { 29 | "total": 7, 30 | "unique": 3 31 | }, 32 | "1.9.10": { 33 | "total": 1, 34 | "unique": 1 35 | }, 36 | "2.1.4": { 37 | "total": 13, 38 | "unique": 5 39 | }, 40 | "2.2.15": { 41 | "total": 6, 42 | "unique": 2 43 | }, 44 | "2.2.16": { 45 | "total": 2, 46 | "unique": 1 47 | }, 48 | "2.2.22": { 49 | "total": 3, 50 | "unique": 1 51 | }, 52 | "2.2.33": { 53 | "total": 1, 54 | "unique": 1 55 | }, 56 | "2.2.7": { 57 | "total": 1, 58 | "unique": 1 59 | }, 60 | "2.3.6": { 61 | "total": 3, 62 | "unique": 1 63 | } 64 | }, 65 | "ci": { 66 | "ci": { 67 | "total": 3, 68 | "unique": 1 69 | }, 70 | "circle,ci": { 71 | "total": 1, 72 | "unique": 1 73 | }, 74 | "gitlab,ci": { 75 | "total": 2, 76 | "unique": 1 77 | } 78 | }, 79 | "gemstash": { 80 | }, 81 | "platform": { 82 | "aarch64-linux": { 83 | "total": 2, 84 | "unique": 1 85 | }, 86 | "universal-darwin-20": { 87 | "total": 4, 88 | "unique": 2 89 | }, 90 | "universal-java-1.8": { 91 | "total": 1, 92 | "unique": 1 93 | }, 94 | "universal-java-11": { 95 | "total": 4, 96 | "unique": 2 97 | }, 98 | "x86_64-Oracle Corporation-linux": { 99 | "total": 2, 100 | "unique": 1 101 | }, 102 | "x86_64-darwin-19": { 103 | "total": 3, 104 | "unique": 1 105 | }, 106 | "x86_64-darwin-21": { 107 | "total": 1, 108 | "unique": 1 109 | }, 110 | "x86_64-linux": { 111 | "total": 152, 112 | "unique": 28 113 | }, 114 | "x86_64-pc-linux": { 115 | "total": 14, 116 | "unique": 6 117 | }, 118 | "x86_64-pc-linux-gnu": { 119 | "total": 185, 120 | "unique": 33 121 | }, 122 | "x86_64-w64-mingw32": { 123 | "total": 7, 124 | "unique": 3 125 | } 126 | }, 127 | "ruby": { 128 | "1.8.7": { 129 | "total": 1, 130 | "unique": 1 131 | }, 132 | "1.9.3": { 133 | "total": 4, 134 | "unique": 2 135 | }, 136 | "2.0.0": { 137 | "total": 9, 138 | "unique": 3 139 | }, 140 | "2.1.10": { 141 | "total": 1, 142 | "unique": 1 143 | }, 144 | "2.1.6": { 145 | "total": 7, 146 | "unique": 2 147 | }, 148 | "2.3.0": { 149 | "total": 8, 150 | "unique": 2 151 | }, 152 | "2.3.1": { 153 | "total": 21, 154 | "unique": 3 155 | }, 156 | "2.3.3": { 157 | "total": 12, 158 | "unique": 4 159 | }, 160 | "2.3.5": { 161 | "total": 13, 162 | "unique": 7 163 | }, 164 | "2.3.6": { 165 | "total": 97, 166 | "unique": 16 167 | }, 168 | "2.4.10": { 169 | "total": 3, 170 | "unique": 1 171 | }, 172 | "2.4.3": { 173 | "total": 8, 174 | "unique": 3 175 | }, 176 | "2.4.4": { 177 | "total": 5, 178 | "unique": 2 179 | }, 180 | "2.4.5": { 181 | "total": 11, 182 | "unique": 2 183 | }, 184 | "2.4.7": { 185 | "total": 5, 186 | "unique": 3 187 | }, 188 | "2.4.9": { 189 | "total": 1, 190 | "unique": 1 191 | }, 192 | "2.5.1": { 193 | "total": 12, 194 | "unique": 5 195 | }, 196 | "2.5.3": { 197 | "total": 42, 198 | "unique": 12 199 | }, 200 | "2.5.5": { 201 | "total": 16, 202 | "unique": 7 203 | }, 204 | "2.5.7": { 205 | "total": 14, 206 | "unique": 5 207 | }, 208 | "2.5.8": { 209 | "total": 2, 210 | "unique": 1 211 | }, 212 | "2.5.9": { 213 | "total": 2, 214 | "unique": 1 215 | }, 216 | "2.6.2": { 217 | "total": 1, 218 | "unique": 1 219 | }, 220 | "2.6.3": { 221 | "total": 8, 222 | "unique": 2 223 | }, 224 | "2.6.4": { 225 | "total": 1, 226 | "unique": 1 227 | }, 228 | "2.6.5": { 229 | "total": 17, 230 | "unique": 7 231 | }, 232 | "2.6.6": { 233 | "total": 5, 234 | "unique": 3 235 | }, 236 | "2.6.7": { 237 | "total": 3, 238 | "unique": 1 239 | }, 240 | "2.6.8": { 241 | "total": 3, 242 | "unique": 2 243 | }, 244 | "2.7.0": { 245 | "total": 4, 246 | "unique": 1 247 | }, 248 | "2.7.1": { 249 | "total": 5, 250 | "unique": 1 251 | }, 252 | "2.7.2": { 253 | "total": 11, 254 | "unique": 4 255 | }, 256 | "2.7.3": { 257 | "total": 3, 258 | "unique": 2 259 | }, 260 | "2.7.4": { 261 | "total": 2, 262 | "unique": 1 263 | }, 264 | "2.7.5": { 265 | "total": 9, 266 | "unique": 1 267 | }, 268 | "3.0.1": { 269 | "total": 5, 270 | "unique": 2 271 | }, 272 | "3.0.2": { 273 | "total": 3, 274 | "unique": 2 275 | }, 276 | "3.0.3": { 277 | "total": 1, 278 | "unique": 1 279 | } 280 | }, 281 | "rubygems": { 282 | "1.3.7": { 283 | "total": 1, 284 | "unique": 1 285 | }, 286 | "1.8.23": { 287 | "total": 1, 288 | "unique": 1 289 | }, 290 | "2.0.14.1": { 291 | "total": 9, 292 | "unique": 2 293 | }, 294 | "2.4.8": { 295 | "total": 7, 296 | "unique": 3 297 | }, 298 | "2.5.2": { 299 | "total": 2, 300 | "unique": 1 301 | }, 302 | "2.5.2.1": { 303 | "total": 13, 304 | "unique": 4 305 | }, 306 | "2.6.10": { 307 | "total": 4, 308 | "unique": 2 309 | }, 310 | "2.6.11": { 311 | "total": 3, 312 | "unique": 1 313 | }, 314 | "2.6.13": { 315 | "total": 7, 316 | "unique": 3 317 | }, 318 | "2.6.14": { 319 | "total": 116, 320 | "unique": 37 321 | }, 322 | "2.6.14.4": { 323 | "total": 9, 324 | "unique": 4 325 | }, 326 | "2.6.6": { 327 | "total": 11, 328 | "unique": 5 329 | }, 330 | "2.6.7": { 331 | "total": 1, 332 | "unique": 1 333 | }, 334 | "2.6.8": { 335 | "total": 8, 336 | "unique": 3 337 | }, 338 | "2.7.10": { 339 | "total": 1, 340 | "unique": 1 341 | }, 342 | "2.7.11": { 343 | "total": 1, 344 | "unique": 1 345 | }, 346 | "2.7.6": { 347 | "total": 52, 348 | "unique": 13 349 | }, 350 | "2.7.6.2": { 351 | "total": 9, 352 | "unique": 4 353 | }, 354 | "2.7.6.3": { 355 | "total": 2, 356 | "unique": 1 357 | }, 358 | "2.7.7": { 359 | "total": 2, 360 | "unique": 1 361 | }, 362 | "2.7.9": { 363 | "total": 24, 364 | "unique": 3 365 | }, 366 | "3.0.3": { 367 | "total": 37, 368 | "unique": 13 369 | }, 370 | "3.0.3.1": { 371 | "total": 5, 372 | "unique": 2 373 | }, 374 | "3.0.6": { 375 | "total": 4, 376 | "unique": 1 377 | }, 378 | "3.0.8": { 379 | "total": 1, 380 | "unique": 1 381 | }, 382 | "3.1.2": { 383 | "total": 9, 384 | "unique": 4 385 | }, 386 | "3.1.4": { 387 | "total": 10, 388 | "unique": 5 389 | }, 390 | "3.1.6": { 391 | "total": 13, 392 | "unique": 5 393 | }, 394 | "3.2.15": { 395 | "total": 5, 396 | "unique": 2 397 | }, 398 | "3.2.20": { 399 | "total": 1, 400 | "unique": 1 401 | }, 402 | "3.2.22": { 403 | "total": 3, 404 | "unique": 1 405 | }, 406 | "3.2.29": { 407 | "total": 1, 408 | "unique": 1 409 | }, 410 | "3.2.32": { 411 | "total": 1, 412 | "unique": 1 413 | }, 414 | "3.2.7": { 415 | "total": 1, 416 | "unique": 1 417 | }, 418 | "3.3.6": { 419 | "total": 1, 420 | "unique": 1 421 | } 422 | }, 423 | "server_region": { 424 | }, 425 | "tls_cipher": { 426 | "": { 427 | "total": 2, 428 | "unique": 1 429 | }, 430 | "AES128-GCM": { 431 | "total": 3, 432 | "unique": 1 433 | }, 434 | "AES256-GCM": { 435 | "total": 54, 436 | "unique": 9 437 | }, 438 | "ECDHE-RSA-AES128-GCM-SHA256": { 439 | "total": 321, 440 | "unique": 142 441 | }, 442 | "ECDHE-RSA-AES128-SHA256": { 443 | "total": 1, 444 | "unique": 1 445 | } 446 | } 447 | }, 448 | "2022-02-01": { 449 | "bundler": { 450 | "1.10.6": { 451 | "total": 10, 452 | "unique": 4 453 | }, 454 | "1.11.2": { 455 | "total": 79, 456 | "unique": 14 457 | }, 458 | "1.12.5": { 459 | "total": 29054, 460 | "unique": 5240 461 | }, 462 | "1.13.5": { 463 | "total": 11, 464 | "unique": 4 465 | }, 466 | "1.13.6": { 467 | "total": 18, 468 | "unique": 7 469 | }, 470 | "1.13.7": { 471 | "total": 41, 472 | "unique": 6 473 | }, 474 | "1.14.3": { 475 | "total": 1, 476 | "unique": 1 477 | }, 478 | "1.14.4": { 479 | "total": 1, 480 | "unique": 1 481 | }, 482 | "1.14.5": { 483 | "total": 1, 484 | "unique": 1 485 | }, 486 | "1.14.6": { 487 | "total": 68, 488 | "unique": 10 489 | }, 490 | "1.15.0": { 491 | "total": 6, 492 | "unique": 3 493 | }, 494 | "1.15.1": { 495 | "total": 61, 496 | "unique": 20 497 | }, 498 | "1.15.3": { 499 | "total": 3, 500 | "unique": 1 501 | }, 502 | "1.15.4": { 503 | "total": 1890, 504 | "unique": 413 505 | }, 506 | "1.16.0": { 507 | "total": 15, 508 | "unique": 6 509 | }, 510 | "1.16.1": { 511 | "total": 5638, 512 | "unique": 1911 513 | }, 514 | "1.16.2": { 515 | "total": 149, 516 | "unique": 72 517 | }, 518 | "1.16.3": { 519 | "total": 8, 520 | "unique": 4 521 | }, 522 | "1.16.5": { 523 | "total": 39, 524 | "unique": 19 525 | }, 526 | "1.16.6": { 527 | "total": 2506, 528 | "unique": 773 529 | }, 530 | "1.17.1": { 531 | "total": 16, 532 | "unique": 3 533 | }, 534 | "1.17.2": { 535 | "total": 2148, 536 | "unique": 948 537 | }, 538 | "1.17.3": { 539 | "total": 2863, 540 | "unique": 1337 541 | }, 542 | "1.17.3.ol.0.1.0": { 543 | "total": 1, 544 | "unique": 1 545 | }, 546 | "1.5.3": { 547 | "total": 5, 548 | "unique": 1 549 | }, 550 | "1.7.3": { 551 | "total": 2, 552 | "unique": 1 553 | }, 554 | "1.7.4": { 555 | "total": 3, 556 | "unique": 2 557 | }, 558 | "1.7.5": { 559 | "total": 1, 560 | "unique": 1 561 | }, 562 | "1.9.10": { 563 | "total": 10, 564 | "unique": 2 565 | }, 566 | "1.9.4": { 567 | "total": 1, 568 | "unique": 1 569 | }, 570 | "2.0.1": { 571 | "total": 16, 572 | "unique": 6 573 | }, 574 | "2.0.2": { 575 | "total": 21, 576 | "unique": 11 577 | }, 578 | "2.1.1": { 579 | "total": 1, 580 | "unique": 1 581 | }, 582 | "2.1.2": { 583 | "total": 17, 584 | "unique": 9 585 | }, 586 | "2.1.4": { 587 | "total": 2763, 588 | "unique": 367 589 | }, 590 | "2.2.0": { 591 | "total": 2, 592 | "unique": 1 593 | }, 594 | "2.2.10": { 595 | "total": 1, 596 | "unique": 1 597 | }, 598 | "2.2.11": { 599 | "total": 10, 600 | "unique": 3 601 | }, 602 | "2.2.12": { 603 | "total": 1, 604 | "unique": 1 605 | }, 606 | "2.2.13": { 607 | "total": 1, 608 | "unique": 1 609 | }, 610 | "2.2.15": { 611 | "total": 573, 612 | "unique": 209 613 | }, 614 | "2.2.16": { 615 | "total": 33, 616 | "unique": 15 617 | }, 618 | "2.2.17": { 619 | "total": 17, 620 | "unique": 9 621 | }, 622 | "2.2.18": { 623 | "total": 1, 624 | "unique": 1 625 | }, 626 | "2.2.19": { 627 | "total": 8, 628 | "unique": 4 629 | }, 630 | "2.2.2": { 631 | "total": 3, 632 | "unique": 1 633 | }, 634 | "2.2.20": { 635 | "total": 64, 636 | "unique": 13 637 | }, 638 | "2.2.21": { 639 | "total": 2, 640 | "unique": 1 641 | }, 642 | "2.2.22": { 643 | "total": 755, 644 | "unique": 101 645 | }, 646 | "2.2.23": { 647 | "total": 2, 648 | "unique": 1 649 | }, 650 | "2.2.24": { 651 | "total": 14, 652 | "unique": 4 653 | }, 654 | "2.2.25": { 655 | "total": 11, 656 | "unique": 4 657 | }, 658 | "2.2.26": { 659 | "total": 45, 660 | "unique": 11 661 | }, 662 | "2.2.27": { 663 | "total": 57, 664 | "unique": 24 665 | }, 666 | "2.2.28": { 667 | "total": 37, 668 | "unique": 8 669 | }, 670 | "2.2.29": { 671 | "total": 48, 672 | "unique": 22 673 | }, 674 | "2.2.3": { 675 | "total": 15, 676 | "unique": 3 677 | }, 678 | "2.2.30": { 679 | "total": 19, 680 | "unique": 8 681 | }, 682 | "2.2.31": { 683 | "total": 116, 684 | "unique": 19 685 | }, 686 | "2.2.32": { 687 | "total": 408, 688 | "unique": 193 689 | }, 690 | "2.2.33": { 691 | "total": 389, 692 | "unique": 167 693 | }, 694 | "2.2.4": { 695 | "total": 3, 696 | "unique": 2 697 | }, 698 | "2.2.5": { 699 | "total": 4, 700 | "unique": 2 701 | }, 702 | "2.2.6": { 703 | "total": 5, 704 | "unique": 2 705 | }, 706 | "2.2.7": { 707 | "total": 2, 708 | "unique": 1 709 | }, 710 | "2.2.8": { 711 | "total": 34, 712 | "unique": 14 713 | }, 714 | "2.2.9": { 715 | "total": 10, 716 | "unique": 2 717 | }, 718 | "2.3.0": { 719 | "total": 3, 720 | "unique": 1 721 | }, 722 | "2.3.1": { 723 | "total": 1, 724 | "unique": 1 725 | }, 726 | "2.3.2": { 727 | "total": 2, 728 | "unique": 1 729 | }, 730 | "2.3.3": { 731 | "total": 23, 732 | "unique": 6 733 | }, 734 | "2.3.4": { 735 | "total": 52, 736 | "unique": 26 737 | }, 738 | "2.3.5": { 739 | "total": 33, 740 | "unique": 14 741 | }, 742 | "2.3.6": { 743 | "total": 204, 744 | "unique": 72 745 | }, 746 | "2.4.0.dev": { 747 | "total": 2, 748 | "unique": 1 749 | } 750 | }, 751 | "ci": { 752 | "aws-code-build,ci": { 753 | "total": 3, 754 | "unique": 1 755 | }, 756 | "ci": { 757 | "total": 479, 758 | "unique": 152 759 | }, 760 | "circle,ci": { 761 | "total": 206, 762 | "unique": 67 763 | }, 764 | "circle-ci": { 765 | "total": 1, 766 | "unique": 1 767 | }, 768 | "gitlab,ci": { 769 | "total": 47, 770 | "unique": 8 771 | }, 772 | "jenkins": { 773 | "total": 72, 774 | "unique": 36 775 | }, 776 | "jenkins,ci": { 777 | "total": 84, 778 | "unique": 36 779 | }, 780 | "semaphore,ci": { 781 | "total": 24, 782 | "unique": 5 783 | }, 784 | "travis,ci": { 785 | "total": 10, 786 | "unique": 3 787 | } 788 | }, 789 | "gemstash": { 790 | }, 791 | "platform": { 792 | "aarch64-apple-darwin21.2.0": { 793 | "total": 1, 794 | "unique": 1 795 | }, 796 | "aarch64-linux": { 797 | "total": 305, 798 | "unique": 124 799 | }, 800 | "aarch64-linux-android": { 801 | "total": 2, 802 | "unique": 1 803 | }, 804 | "aarch64-unknown-linux": { 805 | "total": 16, 806 | "unique": 4 807 | }, 808 | "aarch64-unknown-linux-gnu": { 809 | "total": 135, 810 | "unique": 16 811 | }, 812 | "aarch64-unknown-linux-musl": { 813 | "total": 3, 814 | "unique": 2 815 | }, 816 | "amd64-freebsd-12": { 817 | "total": 1, 818 | "unique": 1 819 | }, 820 | "arm-apple-darwin20.2.0": { 821 | "total": 1, 822 | "unique": 1 823 | }, 824 | "arm-apple-darwin20.6.0": { 825 | "total": 3, 826 | "unique": 2 827 | }, 828 | "arm-apple-darwin21.2.0": { 829 | "total": 2, 830 | "unique": 1 831 | }, 832 | "arm-apple-darwin21.3.0": { 833 | "total": 1, 834 | "unique": 1 835 | }, 836 | "arm-linux": { 837 | "total": 11, 838 | "unique": 2 839 | }, 840 | "arm-linux-androideabi": { 841 | "total": 1, 842 | "unique": 1 843 | }, 844 | "arm64-apple-darwin20": { 845 | "total": 27, 846 | "unique": 10 847 | }, 848 | "arm64-apple-darwin21": { 849 | "total": 16, 850 | "unique": 3 851 | }, 852 | "arm64-darwin-20": { 853 | "total": 5, 854 | "unique": 3 855 | }, 856 | "arm64-darwin-21": { 857 | "total": 11, 858 | "unique": 3 859 | }, 860 | "armv7l-linux": { 861 | "total": 3, 862 | "unique": 2 863 | }, 864 | "armv7l-linux-eabihf": { 865 | "total": 4, 866 | "unique": 2 867 | }, 868 | "armv7l-unknown-linux-eabihf": { 869 | "total": 2, 870 | "unique": 1 871 | }, 872 | "armv7l-unknown-linux-gnueabihf": { 873 | "total": 3, 874 | "unique": 2 875 | }, 876 | "i686-pc-linux-gnu": { 877 | "total": 1, 878 | "unique": 1 879 | }, 880 | "i686-w64-mingw32": { 881 | "total": 14, 882 | "unique": 2 883 | }, 884 | "powerpc-ibm-aix6.1.0.0": { 885 | "total": 8, 886 | "unique": 2 887 | }, 888 | "powerpc-ibm-aix7.1.5.0": { 889 | "total": 5, 890 | "unique": 1 891 | }, 892 | "powerpc64le-unknown-linux": { 893 | "total": 1, 894 | "unique": 1 895 | }, 896 | "sparc-sun-solaris2.11": { 897 | "total": 9, 898 | "unique": 4 899 | }, 900 | "universal-darwin-16": { 901 | "total": 6, 902 | "unique": 2 903 | }, 904 | "universal-darwin-17": { 905 | "total": 15, 906 | "unique": 6 907 | }, 908 | "universal-darwin-18": { 909 | "total": 1, 910 | "unique": 1 911 | }, 912 | "universal-darwin-19": { 913 | "total": 52, 914 | "unique": 10 915 | }, 916 | "universal-darwin-20": { 917 | "total": 325, 918 | "unique": 66 919 | }, 920 | "universal-darwin-21": { 921 | "total": 44, 922 | "unique": 11 923 | }, 924 | "universal-java-1.8": { 925 | "total": 167, 926 | "unique": 67 927 | }, 928 | "universal-java-11": { 929 | "total": 751, 930 | "unique": 227 931 | }, 932 | "x64-mingw32": { 933 | "total": 358, 934 | "unique": 78 935 | }, 936 | "x64-w64-mingw32": { 937 | "total": 252, 938 | "unique": 102 939 | }, 940 | "x86-linux": { 941 | "total": 6, 942 | "unique": 2 943 | }, 944 | "x86-mingw32": { 945 | "total": 258, 946 | "unique": 106 947 | }, 948 | "x86-solaris-2.10": { 949 | "total": 6, 950 | "unique": 3 951 | }, 952 | "x86_64-AdoptOpenJDK-linux": { 953 | "total": 42, 954 | "unique": 13 955 | }, 956 | "x86_64-Amazon.com Inc.-linux": { 957 | "total": 1, 958 | "unique": 1 959 | }, 960 | "x86_64-Azul Systems, Inc.-linux": { 961 | "total": 4, 962 | "unique": 2 963 | }, 964 | "x86_64-Eclipse Adoptium-linux": { 965 | "total": 17, 966 | "unique": 9 967 | }, 968 | "x86_64-Oracle Corporation-linux": { 969 | "total": 37, 970 | "unique": 9 971 | }, 972 | "x86_64-Private Build-linux": { 973 | "total": 3, 974 | "unique": 1 975 | }, 976 | "x86_64-Red Hat, Inc.-linux": { 977 | "total": 8, 978 | "unique": 4 979 | }, 980 | "x86_64-Temurin-linux": { 981 | "total": 3, 982 | "unique": 1 983 | }, 984 | "x86_64-Ubuntu-linux": { 985 | "total": 10, 986 | "unique": 3 987 | }, 988 | "x86_64-alpine-linux-musl": { 989 | "total": 17, 990 | "unique": 4 991 | }, 992 | "x86_64-apple-darwin13.4.0": { 993 | "total": 1, 994 | "unique": 1 995 | }, 996 | "x86_64-apple-darwin14.5.0": { 997 | "total": 12, 998 | "unique": 2 999 | }, 1000 | "x86_64-apple-darwin15.6.0": { 1001 | "total": 8, 1002 | "unique": 3 1003 | }, 1004 | "x86_64-apple-darwin16.7.0": { 1005 | "total": 5, 1006 | "unique": 3 1007 | }, 1008 | "x86_64-apple-darwin17.7.0": { 1009 | "total": 5, 1010 | "unique": 1 1011 | }, 1012 | "x86_64-apple-darwin18": { 1013 | "total": 3, 1014 | "unique": 2 1015 | }, 1016 | "x86_64-apple-darwin18.6.0": { 1017 | "total": 5, 1018 | "unique": 2 1019 | }, 1020 | "x86_64-apple-darwin18.7.0": { 1021 | "total": 2, 1022 | "unique": 1 1023 | }, 1024 | "x86_64-apple-darwin19": { 1025 | "total": 65, 1026 | "unique": 15 1027 | }, 1028 | "x86_64-apple-darwin19.3.0": { 1029 | "total": 1, 1030 | "unique": 1 1031 | }, 1032 | "x86_64-apple-darwin19.4.0": { 1033 | "total": 2, 1034 | "unique": 1 1035 | }, 1036 | "x86_64-apple-darwin19.5.0": { 1037 | "total": 1, 1038 | "unique": 1 1039 | }, 1040 | "x86_64-apple-darwin19.6.0": { 1041 | "total": 18, 1042 | "unique": 4 1043 | }, 1044 | "x86_64-apple-darwin20": { 1045 | "total": 69, 1046 | "unique": 25 1047 | }, 1048 | "x86_64-apple-darwin20.1.0": { 1049 | "total": 1, 1050 | "unique": 1 1051 | }, 1052 | "x86_64-apple-darwin20.5.0": { 1053 | "total": 3, 1054 | "unique": 1 1055 | }, 1056 | "x86_64-apple-darwin20.6.0": { 1057 | "total": 31, 1058 | "unique": 15 1059 | }, 1060 | "x86_64-apple-darwin21": { 1061 | "total": 9, 1062 | "unique": 5 1063 | }, 1064 | "x86_64-apple-darwin21.1.0": { 1065 | "total": 9, 1066 | "unique": 2 1067 | }, 1068 | "x86_64-apple-darwin21.2.0": { 1069 | "total": 5, 1070 | "unique": 2 1071 | }, 1072 | "x86_64-apple-darwin21.3.0": { 1073 | "total": 1, 1074 | "unique": 1 1075 | }, 1076 | "x86_64-cygwin": { 1077 | "total": 5, 1078 | "unique": 2 1079 | }, 1080 | "x86_64-darwin-13": { 1081 | "total": 15, 1082 | "unique": 5 1083 | }, 1084 | "x86_64-darwin-14": { 1085 | "total": 4, 1086 | "unique": 2 1087 | }, 1088 | "x86_64-darwin-15": { 1089 | "total": 10, 1090 | "unique": 3 1091 | }, 1092 | "x86_64-darwin-17": { 1093 | "total": 1, 1094 | "unique": 1 1095 | }, 1096 | "x86_64-darwin-18": { 1097 | "total": 16, 1098 | "unique": 8 1099 | }, 1100 | "x86_64-darwin-19": { 1101 | "total": 164, 1102 | "unique": 28 1103 | }, 1104 | "x86_64-darwin-20": { 1105 | "total": 109, 1106 | "unique": 52 1107 | }, 1108 | "x86_64-darwin-21": { 1109 | "total": 21, 1110 | "unique": 6 1111 | }, 1112 | "x86_64-koji-linux-gnu": { 1113 | "total": 15, 1114 | "unique": 8 1115 | }, 1116 | "x86_64-linux": { 1117 | "total": 29383, 1118 | "unique": 8338 1119 | }, 1120 | "x86_64-linux-musl": { 1121 | "total": 57, 1122 | "unique": 7 1123 | }, 1124 | "x86_64-pc-linux": { 1125 | "total": 2471, 1126 | "unique": 516 1127 | }, 1128 | "x86_64-pc-linux-gnu": { 1129 | "total": 43658, 1130 | "unique": 16716 1131 | }, 1132 | "x86_64-pc-linux-musl": { 1133 | "total": 61, 1134 | "unique": 31 1135 | }, 1136 | "x86_64-redhat-linux": { 1137 | "total": 4, 1138 | "unique": 2 1139 | }, 1140 | "x86_64-redhat-linux-gnu": { 1141 | "total": 6, 1142 | "unique": 2 1143 | }, 1144 | "x86_64-solaris-2.11": { 1145 | "total": 4, 1146 | "unique": 2 1147 | }, 1148 | "x86_64-unknown-freebsd10.3": { 1149 | "total": 43, 1150 | "unique": 8 1151 | }, 1152 | "x86_64-unknown-freebsd11.3": { 1153 | "total": 1, 1154 | "unique": 1 1155 | }, 1156 | "x86_64-unknown-freebsd11.4": { 1157 | "total": 2, 1158 | "unique": 1 1159 | }, 1160 | "x86_64-unknown-linux": { 1161 | "total": 1, 1162 | "unique": 1 1163 | }, 1164 | "x86_64-unknown-linux-gnu": { 1165 | "total": 89, 1166 | "unique": 42 1167 | }, 1168 | "x86_64-w64-mingw32": { 1169 | "total": 3228, 1170 | "unique": 1402 1171 | } 1172 | }, 1173 | "ruby": { 1174 | "1.8.7": { 1175 | "total": 52, 1176 | "unique": 26 1177 | }, 1178 | "1.9.3": { 1179 | "total": 379, 1180 | "unique": 182 1181 | }, 1182 | "2.0.0": { 1183 | "total": 1456, 1184 | "unique": 441 1185 | }, 1186 | "2.1.0": { 1187 | "total": 4, 1188 | "unique": 2 1189 | }, 1190 | "2.1.1": { 1191 | "total": 2, 1192 | "unique": 1 1193 | }, 1194 | "2.1.10": { 1195 | "total": 266, 1196 | "unique": 77 1197 | }, 1198 | "2.1.2": { 1199 | "total": 4, 1200 | "unique": 1 1201 | }, 1202 | "2.1.3": { 1203 | "total": 11, 1204 | "unique": 2 1205 | }, 1206 | "2.1.4": { 1207 | "total": 52, 1208 | "unique": 18 1209 | }, 1210 | "2.1.5": { 1211 | "total": 34, 1212 | "unique": 4 1213 | }, 1214 | "2.1.6": { 1215 | "total": 1254, 1216 | "unique": 420 1217 | }, 1218 | "2.1.7": { 1219 | "total": 2, 1220 | "unique": 1 1221 | }, 1222 | "2.1.8": { 1223 | "total": 292, 1224 | "unique": 38 1225 | }, 1226 | "2.1.9": { 1227 | "total": 140, 1228 | "unique": 64 1229 | }, 1230 | "2.2.0": { 1231 | "total": 21, 1232 | "unique": 4 1233 | }, 1234 | "2.2.10": { 1235 | "total": 15, 1236 | "unique": 3 1237 | }, 1238 | "2.2.2": { 1239 | "total": 50, 1240 | "unique": 21 1241 | }, 1242 | "2.2.3": { 1243 | "total": 18, 1244 | "unique": 7 1245 | }, 1246 | "2.2.4": { 1247 | "total": 46, 1248 | "unique": 14 1249 | }, 1250 | "2.2.5": { 1251 | "total": 2, 1252 | "unique": 1 1253 | }, 1254 | "2.2.6": { 1255 | "total": 13, 1256 | "unique": 4 1257 | }, 1258 | "2.2.7": { 1259 | "total": 39, 1260 | "unique": 19 1261 | }, 1262 | "2.2.8": { 1263 | "total": 1, 1264 | "unique": 1 1265 | }, 1266 | "2.2.9": { 1267 | "total": 3, 1268 | "unique": 1 1269 | }, 1270 | "2.3.0": { 1271 | "total": 972, 1272 | "unique": 421 1273 | }, 1274 | "2.3.1": { 1275 | "total": 7730, 1276 | "unique": 3157 1277 | }, 1278 | "2.3.3": { 1279 | "total": 2588, 1280 | "unique": 663 1281 | }, 1282 | "2.3.4": { 1283 | "total": 118, 1284 | "unique": 55 1285 | }, 1286 | "2.3.5": { 1287 | "total": 3484, 1288 | "unique": 1136 1289 | }, 1290 | "2.3.6": { 1291 | "total": 21210, 1292 | "unique": 5113 1293 | }, 1294 | "2.3.7": { 1295 | "total": 72, 1296 | "unique": 27 1297 | }, 1298 | "2.3.8": { 1299 | "total": 199, 1300 | "unique": 46 1301 | }, 1302 | "2.4.0": { 1303 | "total": 25, 1304 | "unique": 12 1305 | }, 1306 | "2.4.1": { 1307 | "total": 204, 1308 | "unique": 47 1309 | }, 1310 | "2.4.10": { 1311 | "total": 398, 1312 | "unique": 54 1313 | }, 1314 | "2.4.2": { 1315 | "total": 736, 1316 | "unique": 297 1317 | }, 1318 | "2.4.3": { 1319 | "total": 1045, 1320 | "unique": 401 1321 | }, 1322 | "2.4.4": { 1323 | "total": 926, 1324 | "unique": 256 1325 | }, 1326 | "2.4.5": { 1327 | "total": 1503, 1328 | "unique": 678 1329 | }, 1330 | "2.4.6": { 1331 | "total": 11, 1332 | "unique": 2 1333 | }, 1334 | "2.4.7": { 1335 | "total": 1023, 1336 | "unique": 120 1337 | }, 1338 | "2.4.9": { 1339 | "total": 85, 1340 | "unique": 32 1341 | }, 1342 | "2.5.0": { 1343 | "total": 52, 1344 | "unique": 19 1345 | }, 1346 | "2.5.1": { 1347 | "total": 3621, 1348 | "unique": 1343 1349 | }, 1350 | "2.5.2": { 1351 | "total": 2, 1352 | "unique": 1 1353 | }, 1354 | "2.5.3": { 1355 | "total": 6836, 1356 | "unique": 1935 1357 | }, 1358 | "2.5.4": { 1359 | "total": 9, 1360 | "unique": 3 1361 | }, 1362 | "2.5.5": { 1363 | "total": 2378, 1364 | "unique": 784 1365 | }, 1366 | "2.5.7": { 1367 | "total": 2390, 1368 | "unique": 852 1369 | }, 1370 | "2.5.8": { 1371 | "total": 1072, 1372 | "unique": 321 1373 | }, 1374 | "2.5.9": { 1375 | "total": 264, 1376 | "unique": 132 1377 | }, 1378 | "2.6.0": { 1379 | "total": 16, 1380 | "unique": 4 1381 | }, 1382 | "2.6.1": { 1383 | "total": 236, 1384 | "unique": 112 1385 | }, 1386 | "2.6.2": { 1387 | "total": 59, 1388 | "unique": 24 1389 | }, 1390 | "2.6.3": { 1391 | "total": 1524, 1392 | "unique": 324 1393 | }, 1394 | "2.6.4": { 1395 | "total": 96, 1396 | "unique": 13 1397 | }, 1398 | "2.6.5": { 1399 | "total": 5671, 1400 | "unique": 2036 1401 | }, 1402 | "2.6.6": { 1403 | "total": 1358, 1404 | "unique": 497 1405 | }, 1406 | "2.6.7": { 1407 | "total": 651, 1408 | "unique": 287 1409 | }, 1410 | "2.6.8": { 1411 | "total": 844, 1412 | "unique": 139 1413 | }, 1414 | "2.6.9": { 1415 | "total": 179, 1416 | "unique": 36 1417 | }, 1418 | "2.7.0": { 1419 | "total": 518, 1420 | "unique": 201 1421 | }, 1422 | "2.7.1": { 1423 | "total": 1082, 1424 | "unique": 450 1425 | }, 1426 | "2.7.2": { 1427 | "total": 1827, 1428 | "unique": 756 1429 | }, 1430 | "2.7.3": { 1431 | "total": 788, 1432 | "unique": 167 1433 | }, 1434 | "2.7.4": { 1435 | "total": 845, 1436 | "unique": 353 1437 | }, 1438 | "2.7.5": { 1439 | "total": 2089, 1440 | "unique": 418 1441 | }, 1442 | "3.0.0": { 1443 | "total": 18, 1444 | "unique": 9 1445 | }, 1446 | "3.0.1": { 1447 | "total": 560, 1448 | "unique": 250 1449 | }, 1450 | "3.0.2": { 1451 | "total": 768, 1452 | "unique": 325 1453 | }, 1454 | "3.0.3": { 1455 | "total": 308, 1456 | "unique": 79 1457 | }, 1458 | "3.1.0": { 1459 | "total": 50, 1460 | "unique": 12 1461 | }, 1462 | "3.2.0": { 1463 | "total": 2, 1464 | "unique": 1 1465 | } 1466 | }, 1467 | "rubygems": { 1468 | "1.3.7": { 1469 | "total": 46, 1470 | "unique": 23 1471 | }, 1472 | "1.6.2": { 1473 | "total": 2, 1474 | "unique": 1 1475 | }, 1476 | "1.8.11": { 1477 | "total": 3, 1478 | "unique": 1 1479 | }, 1480 | "1.8.23": { 1481 | "total": 85, 1482 | "unique": 42 1483 | }, 1484 | "1.8.23.2": { 1485 | "total": 1, 1486 | "unique": 1 1487 | }, 1488 | "1.8.24": { 1489 | "total": 15, 1490 | "unique": 6 1491 | }, 1492 | "1.8.28": { 1493 | "total": 1, 1494 | "unique": 1 1495 | }, 1496 | "1.8.29": { 1497 | "total": 17, 1498 | "unique": 7 1499 | }, 1500 | "1.8.30": { 1501 | "total": 1, 1502 | "unique": 1 1503 | }, 1504 | "2.0.14": { 1505 | "total": 27, 1506 | "unique": 4 1507 | }, 1508 | "2.0.14.1": { 1509 | "total": 1280, 1510 | "unique": 152 1511 | }, 1512 | "2.1.11": { 1513 | "total": 36, 1514 | "unique": 16 1515 | }, 1516 | "2.2.0": { 1517 | "total": 1, 1518 | "unique": 1 1519 | }, 1520 | "2.2.2": { 1521 | "total": 104, 1522 | "unique": 19 1523 | }, 1524 | "2.2.5": { 1525 | "total": 86, 1526 | "unique": 26 1527 | }, 1528 | "2.4.3": { 1529 | "total": 2, 1530 | "unique": 1 1531 | }, 1532 | "2.4.4": { 1533 | "total": 241, 1534 | "unique": 108 1535 | }, 1536 | "2.4.5": { 1537 | "total": 3, 1538 | "unique": 1 1539 | }, 1540 | "2.4.5.1": { 1541 | "total": 41, 1542 | "unique": 7 1543 | }, 1544 | "2.4.5.2": { 1545 | "total": 1, 1546 | "unique": 1 1547 | }, 1548 | "2.4.5.3": { 1549 | "total": 3, 1550 | "unique": 1 1551 | }, 1552 | "2.4.5.4": { 1553 | "total": 3, 1554 | "unique": 1 1555 | }, 1556 | "2.4.5.5": { 1557 | "total": 6, 1558 | "unique": 3 1559 | }, 1560 | "2.4.7": { 1561 | "total": 45, 1562 | "unique": 6 1563 | }, 1564 | "2.4.8": { 1565 | "total": 954, 1566 | "unique": 119 1567 | }, 1568 | "2.4.8.1.acquia": { 1569 | "total": 9, 1570 | "unique": 3 1571 | }, 1572 | "2.5.1": { 1573 | "total": 248, 1574 | "unique": 103 1575 | }, 1576 | "2.5.2": { 1577 | "total": 473, 1578 | "unique": 157 1579 | }, 1580 | "2.5.2.1": { 1581 | "total": 2666, 1582 | "unique": 1262 1583 | }, 1584 | "2.5.2.2": { 1585 | "total": 2, 1586 | "unique": 1 1587 | }, 1588 | "2.5.2.3": { 1589 | "total": 140, 1590 | "unique": 39 1591 | }, 1592 | "2.6.0": { 1593 | "total": 1, 1594 | "unique": 1 1595 | }, 1596 | "2.6.10": { 1597 | "total": 1300, 1598 | "unique": 638 1599 | }, 1600 | "2.6.11": { 1601 | "total": 732, 1602 | "unique": 265 1603 | }, 1604 | "2.6.12": { 1605 | "total": 188, 1606 | "unique": 68 1607 | }, 1608 | "2.6.13": { 1609 | "total": 2844, 1610 | "unique": 767 1611 | }, 1612 | "2.6.14": { 1613 | "total": 24933, 1614 | "unique": 11294 1615 | }, 1616 | "2.6.14.1": { 1617 | "total": 37, 1618 | "unique": 9 1619 | }, 1620 | "2.6.14.3": { 1621 | "total": 4, 1622 | "unique": 1 1623 | }, 1624 | "2.6.14.4": { 1625 | "total": 1248, 1626 | "unique": 541 1627 | }, 1628 | "2.6.2": { 1629 | "total": 1, 1630 | "unique": 1 1631 | }, 1632 | "2.6.3": { 1633 | "total": 46, 1634 | "unique": 19 1635 | }, 1636 | "2.6.4": { 1637 | "total": 83, 1638 | "unique": 36 1639 | }, 1640 | "2.6.6": { 1641 | "total": 1454, 1642 | "unique": 426 1643 | }, 1644 | "2.6.7": { 1645 | "total": 622, 1646 | "unique": 239 1647 | }, 1648 | "2.6.8": { 1649 | "total": 4030, 1650 | "unique": 1039 1651 | }, 1652 | "2.7.0": { 1653 | "total": 202, 1654 | "unique": 73 1655 | }, 1656 | "2.7.10": { 1657 | "total": 106, 1658 | "unique": 45 1659 | }, 1660 | "2.7.11": { 1661 | "total": 197, 1662 | "unique": 30 1663 | }, 1664 | "2.7.3": { 1665 | "total": 222, 1666 | "unique": 45 1667 | }, 1668 | "2.7.4": { 1669 | "total": 4, 1670 | "unique": 2 1671 | }, 1672 | "2.7.5": { 1673 | "total": 2, 1674 | "unique": 1 1675 | }, 1676 | "2.7.6": { 1677 | "total": 9481, 1678 | "unique": 4295 1679 | }, 1680 | "2.7.6.1": { 1681 | "total": 1, 1682 | "unique": 1 1683 | }, 1684 | "2.7.6.2": { 1685 | "total": 1341, 1686 | "unique": 456 1687 | }, 1688 | "2.7.6.3": { 1689 | "total": 211, 1690 | "unique": 47 1691 | }, 1692 | "2.7.7": { 1693 | "total": 267, 1694 | "unique": 58 1695 | }, 1696 | "2.7.8": { 1697 | "total": 499, 1698 | "unique": 188 1699 | }, 1700 | "2.7.9": { 1701 | "total": 5552, 1702 | "unique": 2647 1703 | }, 1704 | "3.0.0": { 1705 | "total": 86, 1706 | "unique": 41 1707 | }, 1708 | "3.0.1": { 1709 | "total": 56, 1710 | "unique": 22 1711 | }, 1712 | "3.0.2": { 1713 | "total": 9, 1714 | "unique": 2 1715 | }, 1716 | "3.0.3": { 1717 | "total": 7521, 1718 | "unique": 2813 1719 | }, 1720 | "3.0.3.1": { 1721 | "total": 1172, 1722 | "unique": 560 1723 | }, 1724 | "3.0.4": { 1725 | "total": 715, 1726 | "unique": 286 1727 | }, 1728 | "3.0.6": { 1729 | "total": 793, 1730 | "unique": 248 1731 | }, 1732 | "3.0.8": { 1733 | "total": 335, 1734 | "unique": 40 1735 | }, 1736 | "3.0.9": { 1737 | "total": 78, 1738 | "unique": 37 1739 | }, 1740 | "3.1.1": { 1741 | "total": 1, 1742 | "unique": 1 1743 | }, 1744 | "3.1.2": { 1745 | "total": 1573, 1746 | "unique": 581 1747 | }, 1748 | "3.1.3": { 1749 | "total": 144, 1750 | "unique": 17 1751 | }, 1752 | "3.1.4": { 1753 | "total": 1831, 1754 | "unique": 911 1755 | }, 1756 | "3.1.6": { 1757 | "total": 3173, 1758 | "unique": 879 1759 | }, 1760 | "3.2.11": { 1761 | "total": 11, 1762 | "unique": 2 1763 | }, 1764 | "3.2.12": { 1765 | "total": 1, 1766 | "unique": 1 1767 | }, 1768 | "3.2.15": { 1769 | "total": 565, 1770 | "unique": 199 1771 | }, 1772 | "3.2.16": { 1773 | "total": 16, 1774 | "unique": 8 1775 | }, 1776 | "3.2.17": { 1777 | "total": 13, 1778 | "unique": 3 1779 | }, 1780 | "3.2.18": { 1781 | "total": 1, 1782 | "unique": 1 1783 | }, 1784 | "3.2.19": { 1785 | "total": 1, 1786 | "unique": 1 1787 | }, 1788 | "3.2.2": { 1789 | "total": 15, 1790 | "unique": 4 1791 | }, 1792 | "3.2.20": { 1793 | "total": 441, 1794 | "unique": 203 1795 | }, 1796 | "3.2.21": { 1797 | "total": 1, 1798 | "unique": 1 1799 | }, 1800 | "3.2.22": { 1801 | "total": 777, 1802 | "unique": 191 1803 | }, 1804 | "3.2.24": { 1805 | "total": 5, 1806 | "unique": 1 1807 | }, 1808 | "3.2.25": { 1809 | "total": 2, 1810 | "unique": 1 1811 | }, 1812 | "3.2.26": { 1813 | "total": 4, 1814 | "unique": 1 1815 | }, 1816 | "3.2.27": { 1817 | "total": 11, 1818 | "unique": 2 1819 | }, 1820 | "3.2.28": { 1821 | "total": 9, 1822 | "unique": 2 1823 | }, 1824 | "3.2.29": { 1825 | "total": 6, 1826 | "unique": 3 1827 | }, 1828 | "3.2.3": { 1829 | "total": 21, 1830 | "unique": 8 1831 | }, 1832 | "3.2.30": { 1833 | "total": 10, 1834 | "unique": 5 1835 | }, 1836 | "3.2.31": { 1837 | "total": 100, 1838 | "unique": 41 1839 | }, 1840 | "3.2.32": { 1841 | "total": 495, 1842 | "unique": 140 1843 | }, 1844 | "3.2.33": { 1845 | "total": 20, 1846 | "unique": 10 1847 | }, 1848 | "3.2.4": { 1849 | "total": 1, 1850 | "unique": 1 1851 | }, 1852 | "3.2.5": { 1853 | "total": 51, 1854 | "unique": 14 1855 | }, 1856 | "3.2.6": { 1857 | "total": 1, 1858 | "unique": 1 1859 | }, 1860 | "3.2.7": { 1861 | "total": 9, 1862 | "unique": 5 1863 | }, 1864 | "3.2.8": { 1865 | "total": 29, 1866 | "unique": 5 1867 | }, 1868 | "3.3.0": { 1869 | "total": 2, 1870 | "unique": 1 1871 | }, 1872 | "3.3.1": { 1873 | "total": 1, 1874 | "unique": 1 1875 | }, 1876 | "3.3.3": { 1877 | "total": 47, 1878 | "unique": 8 1879 | }, 1880 | "3.3.4": { 1881 | "total": 63, 1882 | "unique": 16 1883 | }, 1884 | "3.3.5": { 1885 | "total": 53, 1886 | "unique": 15 1887 | }, 1888 | "3.3.6": { 1889 | "total": 177, 1890 | "unique": 37 1891 | }, 1892 | "3.4.0.dev": { 1893 | "total": 2, 1894 | "unique": 1 1895 | } 1896 | }, 1897 | "server_region": { 1898 | }, 1899 | "tls_cipher": { 1900 | "": { 1901 | "total": 464, 1902 | "unique": 116 1903 | }, 1904 | "AES128-GCM": { 1905 | "total": 1625, 1906 | "unique": 202 1907 | }, 1908 | "AES256-GCM": { 1909 | "total": 10072, 1910 | "unique": 1447 1911 | }, 1912 | "ECDHE-RSA-AES128-GCM-SHA256": { 1913 | "total": 72325, 1914 | "unique": 10737 1915 | }, 1916 | "ECDHE-RSA-AES128-SHA": { 1917 | "total": 101, 1918 | "unique": 40 1919 | }, 1920 | "ECDHE-RSA-AES128-SHA256": { 1921 | "total": 93, 1922 | "unique": 11 1923 | } 1924 | } 1925 | } 1926 | } 1927 | -------------------------------------------------------------------------------- /spec/workers/import_stats_day_worker_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe ImportStatsDayWorker, type: :worker do 4 | let(:start_date) { Date.new(2022, 1, 31) } 5 | let(:end_date) { Date.new(2022, 2, 1) } 6 | 7 | it "handle old format" do 8 | Stat.delete_all 9 | ImportStatus.delete_all 10 | ImportStatus.import("test", Rails.root.join("spec/support/legacy_stats.json").read) 11 | 12 | worker = ImportStatsDayWorker.new 13 | 14 | expect do 15 | worker.perform(end_date) 16 | end.to change { Stat.count } 17 | 18 | expect(Stat.daily_totals("rubygems", start_date)).to be == { end_date => 82598 } 19 | end 20 | 21 | it "handle new format" do 22 | Stat.delete_all 23 | ImportStatus.delete_all 24 | 25 | ImportStatus.import("test", Rails.root.join("spec/support/new_stats.json").read) 26 | worker = ImportStatsDayWorker.new 27 | 28 | expect do 29 | worker.perform(end_date) 30 | end.to change { Stat.count } 31 | 32 | expect(Stat.daily_totals("rubygems", start_date)).to be == { end_date => 82598 } 33 | expect(Stat.daily_totals("rubygems_unique", start_date)).to be == { end_date => 32717 } 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/workers/import_stats_worker_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | RSpec.describe ImportStatsWorker, type: :worker do 3 | pending "add some examples to (or delete) #{__FILE__}" 4 | end 5 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubytogether/ecosystem/b86ae695fe264445bbcf2e3c3979e007d0d4b130/vendor/.keep --------------------------------------------------------------------------------