diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23d0a1da0..796fc4ed6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,6 +95,13 @@ $ echo GITHUB_APP_SECRET= >> .env $ echo PORT=3000 >> .env ``` +#### Optional GitHub API Configuration + +```shell +# Number of issues to fetch per API request (default: 100, max: 100) +$ echo GITHUB_ISSUES_PER_PAGE=100 >> .env +``` + ### Running the app Start your app using [Heroku Local](https://devcenter.heroku.com/articles/heroku-local) diff --git a/Gemfile b/Gemfile index bba751ac0..ee5591af7 100644 --- a/Gemfile +++ b/Gemfile @@ -119,7 +119,7 @@ end group :test do gem "capybara" gem "launchy" # Not essential but helpful for save_and_open_page - gem "minitest" + gem "minitest", "~> 5.0" # Version 6.0+ removed minitest/mock gem "mocha", require: false gem "rails-controller-testing" gem "simplecov", require: false diff --git a/Gemfile.lock b/Gemfile.lock index aab5a670f..88bb12627 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,50 +66,56 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.5) - public_suffix (>= 2.0.2, < 6.0) - ast (2.4.2) - autoprefixer-rails (10.2.5.1) - execjs (> 0) - aws-eventstream (1.2.0) - aws-partitions (1.648.0) - aws-sdk-core (3.162.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + ast (2.4.3) + autoprefixer-rails (10.4.21.0) + execjs (~> 2) + aws-eventstream (1.4.0) + aws-partitions (1.1207.0) + aws-sdk-core (3.241.4) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.58.0) - aws-sdk-core (~> 3, >= 3.127.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.115.0) - aws-sdk-core (~> 3, >= 3.127.0) + logger + aws-sdk-kms (1.121.0) + aws-sdk-core (~> 3, >= 3.241.4) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.212.0) + aws-sdk-core (~> 3, >= 3.241.4) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.2) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) execjs (~> 2.0) - base64 (0.1.1) - bcrypt (3.1.19) - benchmark-ips (2.9.1) + base64 (0.3.0) + bcrypt (3.1.21) + benchmark-ips (2.14.0) + bigdecimal (3.3.1) bindex (0.8.1) bluecloth (2.2.0) - bootsnap (1.13.0) + bootsnap (1.21.1) msgpack (~> 1.2) bourbon (7.3.0) thor (~> 1.0) - builder (3.2.4) - capybara (3.37.1) + builder (3.3.0) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + childprocess (5.1.0) + logger (~> 1.5) coderay (1.1.3) coffee-rails (5.0.0) coffee-script (>= 2.2.0) @@ -118,85 +124,114 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.2.2) - connection_pool (2.4.1) - crack (0.4.5) + concurrent-ruby (1.3.6) + connection_pool (2.5.5) + crack (1.0.1) + bigdecimal rexml crass (1.0.6) - css_parser (1.7.1) + css_parser (1.21.1) addressable - dalli (3.2.6) - dead_end (4.0.0) - derailed_benchmarks (2.1.1) + dalli (4.0.0) + logger + date (3.5.1) + derailed_benchmarks (2.2.1) + base64 benchmark-ips (~> 2) - dead_end - get_process_mem (~> 0) + bigdecimal + drb + get_process_mem heapy (~> 0) + logger memory_profiler (>= 0, < 2) mini_histogram (>= 0.3.0) + mutex_m + ostruct rack (>= 1) rack-test rake (> 10, < 14) - ruby-statistics (>= 2.1) + ruby-statistics (>= 4.0.1) + ruby2_keywords thor (>= 0.19, < 2) - devise (4.9.3) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) responders warden (~> 1.2.3) - docile (1.3.5) - dotenv (2.8.1) - dotenv-rails (2.8.1) - dotenv (= 2.8.1) - railties (>= 3.2) - erubi (1.12.0) - excon (0.71.0) - execjs (2.8.1) - faker (2.23.0) + docile (1.4.1) + dotenv (3.2.0) + dotenv-rails (3.2.0) + dotenv (= 3.2.0) + railties (>= 6.1) + drb (2.2.3) + erubi (1.13.1) + excon (1.3.2) + logger + execjs (2.10.0) + faker (3.5.3) i18n (>= 1.8.11, < 2) - faraday (0.17.6) - multipart-post (>= 1.2, < 3) - ffi (1.17.2) + faraday (2.14.0) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.2) + net-http (~> 0.5) + ffi (1.17.3-arm64-darwin) + ffi (1.17.3-x86_64-linux-gnu) flamegraph (0.9.5) - foreman (0.87.2) - get_process_mem (0.2.7) + foreman (0.90.0) + thor (~> 1.4) + get_process_mem (1.0.0) + bigdecimal (>= 2.0) ffi (~> 1.0) git_hub_bub (1.0.1) excon rrrretry - globalid (1.1.0) - activesupport (>= 5.0) - hashdiff (1.0.1) - hashie (5.0.0) + globalid (1.3.0) + activesupport (>= 6.1) + hashdiff (1.2.1) + hashie (5.1.0) + logger heapy (0.2.0) thor - htmlentities (4.3.4) - i18n (1.14.1) + htmlentities (4.4.2) + i18n (1.14.8) concurrent-ruby (~> 1.0) - jmespath (1.6.1) - jquery-rails (4.6.0) + io-console (0.8.2) + jmespath (1.6.2) + jquery-rails (4.6.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.6.3) - jwt (2.7.1) - kramdown (2.4.0) - rexml + json (2.18.0) + judoscale-rails (1.12.2) + judoscale-ruby (= 1.12.2) + railties + judoscale-ruby (1.12.2) + jwt (3.1.2) + base64 + kramdown (2.5.2) + rexml (>= 3.4.4) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - language_server-protocol (3.17.0.3) - launchy (2.5.0) - addressable (~> 2.7) + language_server-protocol (3.17.0.5) + launchy (3.1.1) + addressable (~> 2.8) + childprocess (~> 5.0) + logger (~> 1.6) lint_roller (1.1.0) - listen (3.7.0) + listen (3.10.0) + logger rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - local_time (2.1.0) - loofah (2.21.4) + local_time (3.0.3) + logger (1.7.0) + loofah (2.25.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mail (2.8.1) + mail (2.9.0) + logger mini_mime (>= 0.1.1) net-imap net-pop @@ -204,101 +239,117 @@ GEM maildown (3.3.1) actionmailer (>= 4.0.0) kramdown-parser-gfm - marcel (1.0.2) - matrix (0.4.2) - memory_profiler (1.0.0) - method_source (1.0.0) - mime-types (3.3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) + marcel (1.1.0) + matrix (0.4.3) + memory_profiler (1.1.0) + method_source (1.1.0) + mime-types (3.7.0) + logger + mime-types-data (~> 3.2025, >= 3.2025.0507) + mime-types-data (3.2026.0113) mini_histogram (0.3.1) mini_mime (1.1.5) - mini_portile2 (2.8.5) - minitest (5.20.0) - mocha (2.1.0) + minitest (5.27.0) + mocha (3.0.1) ruby2_keywords (>= 0.0.5) msgpack (1.8.0) - multi_xml (0.6.0) - multipart-post (2.3.0) - mustermann (3.0.0) + multi_xml (0.8.1) + bigdecimal (>= 3.1, < 5) + mustermann (3.0.4) ruby2_keywords (~> 0.0.1) + mutex_m (0.3.0) neat (1.7.4) bourbon (>= 4.0) sass (>= 3.3) - net-imap (0.3.1) + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.6.2) + date net-protocol net-pop (0.1.2) net-protocol - net-protocol (0.1.3) + net-protocol (0.2.2) timeout - net-smtp (0.3.2) + net-smtp (0.5.1) net-protocol - nio4r (2.5.9) - nokogiri (1.15.4) - mini_portile2 (~> 2.8.2) + nio4r (2.7.5) + nokogiri (1.19.0-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.0-x86_64-linux-gnu) racc (~> 1.4) normalize-rails (8.0.1) - oauth2 (2.0.9) - faraday (>= 0.17.3, < 3.0) - jwt (>= 1.0, < 3.0) + oauth2 (2.0.18) + faraday (>= 0.17.3, < 4.0) + jwt (>= 1.0, < 4.0) + logger (~> 1.2) multi_xml (~> 0.5) rack (>= 1.2, < 4) - snaky_hash (~> 2.0) - version_gem (~> 1.1) - oj (3.13.21) - omniauth (2.1.1) + snaky_hash (~> 2.0, >= 2.0.3) + version_gem (~> 1.1, >= 1.1.9) + oj (3.16.13) + bigdecimal (>= 3.0) + ostruct (>= 0.2) + omniauth (2.1.4) hashie (>= 3.4.6) + logger rack (>= 2.2.3) rack-protection omniauth-github (2.0.1) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) - omniauth-oauth2 (1.8.0) - oauth2 (>= 1.4, < 3) + omniauth-oauth2 (1.9.0) + oauth2 (>= 2.0.2, < 3) omniauth (~> 2.0) - omniauth-rails_csrf_protection (1.0.1) + omniauth-rails_csrf_protection (2.0.1) actionpack (>= 4.2) omniauth (~> 2.0) optimist (3.2.1) orm_adapter (0.5.0) - parallel (1.23.0) - parser (3.2.2.4) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.10.1) ast (~> 2.4.1) racc - pdf-core (0.9.0) - pg (1.6.1) - prawn (2.4.0) - pdf-core (~> 0.9.0) - ttfunk (~> 1.7) - premailer (1.11.1) + pdf-core (0.10.0) + pg (1.6.3-arm64-darwin) + pg (1.6.3-x86_64-linux) + prawn (2.5.0) + matrix (~> 0.4) + pdf-core (~> 0.10.0) + ttfunk (~> 1.8) + premailer (1.27.0) addressable - css_parser (>= 1.6.0) + css_parser (>= 1.19.0) htmlentities (>= 4.0.0) premailer-rails (1.12.0) actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) - pry (0.14.1) + prism (1.8.0) + pry (0.16.0) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (5.0.3) + reline (>= 0.6.0) + public_suffix (7.0.2) puma (7.0.0.pre1) nio4r (~> 2.0) - puma_worker_killer (0.3.1) - get_process_mem (~> 0.2) + puma_worker_killer (1.0.0) + bigdecimal (>= 2.0) + get_process_mem (>= 0.2) puma (>= 2.7) - racc (1.7.1) - rack (2.2.8) - rack-canonical-host (1.2.0) + racc (1.8.1) + rack (2.2.21) + rack-canonical-host (1.3.0) addressable (> 0, < 3) - rack (>= 1, < 4) - rack-mini-profiler (3.1.1) + rack (>= 1.6, < 4) + rack-mini-profiler (4.0.1) rack (>= 1.2.0) - rack-protection (3.1.0) + rack-protection (3.2.0) + base64 (>= 0.1.0) rack (~> 2.2, >= 2.2.4) - rack-test (2.1.0) + rack-test (2.2.0) rack (>= 1.3) - rack-timeout (0.6.3) + rack-timeout (0.7.0) rails (7.0.8) actioncable (= 7.0.8) actionmailbox (= 7.0.8) @@ -313,21 +364,19 @@ GEM activesupport (= 7.0.8) bundler (>= 1.15.0) railties (= 7.0.8) - rails-autoscale-core (1.1.0) - rails-autoscale-web (1.1.0) - rails-autoscale-core - railties + rails-autoscale-web (1.12.2) + judoscale-rails (= 1.12.2) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.2.0) + rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) railties (7.0.8) actionpack (= 7.0.8) activesupport (= 7.0.8) @@ -336,42 +385,45 @@ GEM thor (~> 1.0) zeitwerk (~> 2.5) rainbow (3.1.1) - rake (13.1.0) - rb-fsevent (0.11.0) - rb-inotify (0.10.1) + rake (13.3.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) ffi (~> 1.0) - rbtrace (0.5.2) + rbtrace (0.5.3) ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) - redis-client (0.18.0) + redis-client (0.26.3) connection_pool - regexp_parser (2.8.2) + regexp_parser (2.11.3) + reline (0.6.3) + io-console (~> 0.5) render_async (2.1.11) - responders (3.1.1) - actionpack (>= 5.2) - railties (>= 5.2) - rexml (3.2.6) + responders (3.2.0) + actionpack (>= 7.0) + railties (>= 7.0) + rexml (3.4.4) rrrretry (1.0.0) - rubocop (1.56.4) - base64 (~> 0.1.1) + rubocop (1.82.1) json (~> 2.3) - language_server-protocol (>= 3.17.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) - rubocop-performance (1.19.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.0) + parser (>= 3.3.7.2) + prism (~> 1.7) + rubocop-performance (1.26.1) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (1.13.0) - ruby-statistics (2.1.3) + ruby-statistics (4.1.0) ruby2_keywords (0.0.5) sass (3.7.4) sass-listen (~> 4.0.0) @@ -386,84 +438,86 @@ GEM sprockets (> 3.0) sprockets-rails tilt - scout_apm (5.3.1) + scout_apm (6.0.2) parser - sentry-raven (2.13.0) - faraday (>= 0.7.6, < 1.0) - sidekiq (7.1.6) - concurrent-ruby (< 2) - connection_pool (>= 2.3.0) - rack (>= 2.2.4) - redis-client (>= 0.14.0) - simplecov (0.21.2) + sentry-raven (3.1.2) + faraday (>= 1.0) + sidekiq (7.3.10) + base64 + connection_pool (>= 2.3.0, < 3) + logger + rack (>= 2.2.4, < 3.3) + redis-client (>= 0.23.0, < 1) + simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) - simplecov_json_formatter (0.1.2) - simpleidn (0.2.1) - unf (~> 0.1.4) - sinatra (3.1.0) + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) + simpleidn (0.2.3) + sinatra (3.2.0) mustermann (~> 3.0) rack (~> 2.2, >= 2.2.4) - rack-protection (= 3.1.0) + rack-protection (= 3.2.0) tilt (~> 2.0) - sitemap_generator (6.1.2) + sitemap_generator (6.3.0) builder (~> 3.0) - skylight (6.0.1) + skylight (6.0.4) activesupport (>= 5.2.0) - slim (4.1.0) - temple (>= 0.7.6, < 0.9) - tilt (>= 2.0.6, < 2.1) - slim-rails (3.6.3) + slim (5.2.1) + temple (~> 0.10.0) + tilt (>= 2.1.0) + slim-rails (4.0.0) actionpack (>= 3.1) railties (>= 3.1) slim (>= 3.0, < 6.0, != 5.0.0) - snaky_hash (2.0.1) - hashie - version_gem (~> 1.1, >= 1.1.1) - sprockets (4.1.1) + snaky_hash (2.0.3) + hashie (>= 0.1.0, < 6) + version_gem (>= 1.1.8, < 3) + sprockets (4.2.2) concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.5.2) + actionpack (>= 6.1) + activesupport (>= 6.1) sprockets (>= 3.0.0) - stackprof (0.2.17) - standard (1.31.2) + stackprof (0.2.27) + standard (1.53.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) - rubocop (~> 1.56.4) + rubocop (~> 1.82.0) standard-custom (~> 1.0.0) - standard-performance (~> 1.2) + standard-performance (~> 1.8) standard-custom (1.0.2) lint_roller (~> 1.0) rubocop (~> 1.50) - standard-performance (1.2.1) + standard-performance (1.9.0) lint_roller (~> 1.1) - rubocop-performance (~> 1.19.1) + rubocop-performance (~> 1.26.0) standardrb (1.0.1) standard - temple (0.8.2) - test-prof (1.0.10) - thor (1.3.0) - tilt (2.0.11) - timeout (0.3.0) - ttfunk (1.7.0) + temple (0.10.4) + test-prof (1.5.0) + thor (1.5.0) + tilt (2.7.0) + timeout (0.6.0) + ttfunk (1.8.0) + bigdecimal (~> 3.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - uglifier (4.2.0) + uglifier (4.2.1) execjs (>= 0.3.0, < 3) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) - unicode-display_width (2.5.0) - valid_email (0.1.4) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + uri (1.1.1) + valid_email (0.2.1) activemodel mail (>= 2.6.1) simpleidn - vcr (6.1.0) - version_gem (1.1.3) + vcr (6.4.0) + version_gem (1.1.9) warden (1.2.9) rack (>= 2.0.9) web-console (4.2.1) @@ -471,25 +525,25 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.18.1) + webmock (3.26.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.7.0) - websocket-driver (0.7.6) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) - wicked (1.3.4) + wicked (2.0.0) railties (>= 3.0.7) - will_paginate (4.0.0) + will_paginate (4.0.1) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.28) - webrick (~> 1.7.0) - zeitwerk (2.6.12) + yard (0.9.38) + zeitwerk (2.7.4) PLATFORMS - ruby + arm64-darwin-25 + x86_64-linux DEPENDENCIES autoprefixer-rails @@ -517,7 +571,7 @@ DEPENDENCIES matrix memory_profiler mime-types - minitest + minitest (~> 5.0) mocha neat (~> 1.7) normalize-rails @@ -567,7 +621,7 @@ DEPENDENCIES yard (~> 0.9.28) RUBY VERSION - ruby 3.2.9p265 + ruby 3.2.0p0 BUNDLED WITH - 2.7.1 + 2.4.1 diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index 980ee8df8..47779dc69 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -12,7 +12,7 @@ // //= require jquery //= require jquery_ujs -//= require local-time +//= require local-time.es2017-umd //= require bootstrap.min //= require bootstrap_custom //= require coffee_script_utilities diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index fbff852d3..c0c20dabe 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,6 +2,7 @@ class ApplicationController < ActionController::Base include ActionView::Helpers::NumberHelper + protect_from_forgery before_action do diff --git a/app/controllers/repos_controller.rb b/app/controllers/repos_controller.rb index 0b88d3a7c..d1896541c 100644 --- a/app/controllers/repos_controller.rb +++ b/app/controllers/repos_controller.rb @@ -8,7 +8,7 @@ class ReposController < RepoBasedController def new @repo = Repo.new(user_name: params[:user_name], name: params[:name]) - @full_name = (@repo.name && @repo.user_name) ? +"#{@repo.user_name}/#{@repo.name}" : nil + @full_name = (@repo.name && @repo.user_name) ? "#{@repo.user_name}/#{@repo.name}" : nil @full_name&.prepend("https://github.com/") @repo_sub = RepoSubscription.new end diff --git a/app/jobs/populate_issues_job.rb b/app/jobs/populate_issues_job.rb index 416e7aadc..c35231ef5 100644 --- a/app/jobs/populate_issues_job.rb +++ b/app/jobs/populate_issues_job.rb @@ -11,6 +11,9 @@ def populate_multi_issues! page_number = 1 while populate_issues(page_number) page_number += 1 + # Sleep only when approaching GitHub API rate limit (remaining < 1000) + # Uses X-RateLimit headers to calculate optimal sleep time + @last_response&.rate_limit_sleep! end @repo.force_issues_count_sync! end @@ -36,6 +39,7 @@ def populate_issues(page_number) fetcher.page = page_number fetcher.call(retry_on_bad_token: 3) + @last_response = fetcher.response if fetcher.error? repo.update(github_error_msg: fetcher.error_message) diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 16281ec78..53bf7b6ec 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -4,6 +4,7 @@ class UserMailer < ActionMailer::Base class AbortDeliveryError < StandardError; end include ActionView::Helpers::DateHelper + default from: "CodeTriage " layout "mail_layout" @@ -17,7 +18,6 @@ def send_daily_triage( read_doc_ids: [], write_doc_ids: [] ) - user = User.find(user_id) return unless set_and_check_user(user) diff --git a/app/models/doc_mailer_maker.rb b/app/models/doc_mailer_maker.rb index acf07de95..59e8a6e1f 100644 --- a/app/models/doc_mailer_maker.rb +++ b/app/models/doc_mailer_maker.rb @@ -17,7 +17,7 @@ def initialize(user, subs, _options = {}, &send_next) @subs = subs @write_docs = [] @read_docs = [] - assign_docs(&(send_next || READY_FOR_NEXT_DEFAULT)) + assign_docs(&send_next || READY_FOR_NEXT_DEFAULT) end def empty? diff --git a/app/models/git_branchname_generator.rb b/app/models/git_branchname_generator.rb index fdbcf98fd..7d8459133 100644 --- a/app/models/git_branchname_generator.rb +++ b/app/models/git_branchname_generator.rb @@ -8,6 +8,6 @@ def initialize(username:, doc_path:) end def branchname - "#{@username}-update-docs-#{@doc_path}-for-pr".gsub(/[^a-zA-Z0-9\_]/, "-") + "#{@username}-update-docs-#{@doc_path}-for-pr".gsub(/[^a-zA-Z0-9_]/, "-") end end diff --git a/app/models/github_fetcher/issues.rb b/app/models/github_fetcher/issues.rb index b19a4bb88..150d1669e 100644 --- a/app/models/github_fetcher/issues.rb +++ b/app/models/github_fetcher/issues.rb @@ -14,6 +14,7 @@ def initialize(options) options[:direction] ||= "desc" options[:state] ||= "open" options[:page] ||= 1 + options[:per_page] ||= GitHubConfig::ISSUES_PER_PAGE super end diff --git a/app/models/github_fetcher/resource.rb b/app/models/github_fetcher/resource.rb index 832c788b2..a59b7299d 100644 --- a/app/models/github_fetcher/resource.rb +++ b/app/models/github_fetcher/resource.rb @@ -82,6 +82,11 @@ def page=(number) end def last_page? + # If no next_url, we're on the last page + return true if response.next_url.nil? + # If next_url exists but last_url is nil, there are more pages + # (git_hub_bub gem bug - it tries to parse nil last_url) + return false if response.last_url.nil? response.last_page? end diff --git a/app/models/issue.rb b/app/models/issue.rb index 93461e64b..c14880c0b 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -56,6 +56,37 @@ def open? state == OPEN end + # GitHub's Issues API (/repos/{owner}/{repo}/issues) returns both issues AND pull requests. + # The API response includes a `pull_request` key with URLs when the item is a PR: + # + # Issue: { "pull_request": null } + # Pull Request: { "pull_request": { "html_url": "...", "diff_url": "...", "patch_url": "..." } } + # + # The `pr_attached` column stores whether the `pull_request` key had non-null values, + # meaning the record IS a pull request (not an issue with a PR linked to it). + # + # These methods provide clearer semantics: + + # Returns true if this record is a Pull Request (not a regular issue) + def is_pull_request? + pr_attached == true + end + + # Returns true if this record is a regular Issue (not a pull request) + def is_issue? + pr_attached == false + end + + # Scope: Returns only Pull Requests + def self.pull_requests + where(pr_attached: true) + end + + # Scope: Returns only regular Issues (excludes PRs) + def self.issues_only + where(pr_attached: false) + end + def repo_name repo.name end diff --git a/app/models/repo.rb b/app/models/repo.rb index 8ac9294fa..9a0ad20be 100644 --- a/app/models/repo.rb +++ b/app/models/repo.rb @@ -143,7 +143,7 @@ def self.all_languages end def self.repos_needing_help_for_user(user) - if user && user.has_favorite_languages? + if user&.has_favorite_languages? where(language: user.favorite_languages) else self @@ -152,7 +152,7 @@ def self.repos_needing_help_for_user(user) def force_issues_count_sync! update!( - issues_count: issues.where(state: "open").count, + issues_count: issues.open_issues.issues_only.count, docs_subscriber_count: query_docs_subscriber_count ) end diff --git a/config/application.rb b/config/application.rb index ed095d004..615f0d2db 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "boot" +require "logger" require "rails/all" diff --git a/config/boot.rb b/config/boot.rb index aef6d031e..963e4c21d 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -3,4 +3,12 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) require "bundler/setup" # Set up gems listed in the Gemfile. + +# Ruby 3.2+ requires these to be explicitly loaded before bootsnap +require "logger" +if ENV["RAILS_ENV"] == "test" + require "minitest" + require "minitest/mock" +end + require "bootsnap/setup" # Speed up boot time by caching expensive operations. diff --git a/config/initializers/feature_policy.rb b/config/initializers/feature_policy.rb index 4de54a249..21a12cec3 100644 --- a/config/initializers/feature_policy.rb +++ b/config/initializers/feature_policy.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Define an application-wide HTTP feature policy. For further # information see https://developers.google.com/web/updates/2018/06/feature-policy # diff --git a/config/initializers/git_hub_bub.rb b/config/initializers/git_hub_bub.rb index a93e890ec..cac81826c 100644 --- a/config/initializers/git_hub_bub.rb +++ b/config/initializers/git_hub_bub.rb @@ -1,5 +1,12 @@ # frozen_string_literal: true +# GitHub API Configuration +# These can be overridden via environment variables +module GitHubConfig + # Maximum items per page for GitHub API requests (GitHub allows up to 100) + ISSUES_PER_PAGE = Integer(ENV.fetch("GITHUB_ISSUES_PER_PAGE", 100)) +end + # I know it's awful but I don't have time to fix the GitHubBub API and this makes `rails c` not noisey GitHubBub::Request.send(:remove_const, :GITHUB_VERSION) GitHubBub::Request.send(:remove_const, :USER_AGENT) diff --git a/config/initializers/new_framework_defaults_6_0.rb b/config/initializers/new_framework_defaults_6_0.rb index d91d96b11..eae3c399b 100644 --- a/config/initializers/new_framework_defaults_6_0.rb +++ b/config/initializers/new_framework_defaults_6_0.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # # This file contains migration options to ease your Rails 6.0 upgrade. diff --git a/config/initializers/new_framework_defaults_6_1.rb b/config/initializers/new_framework_defaults_6_1.rb index a128fd37b..2b86f608a 100644 --- a/config/initializers/new_framework_defaults_6_1.rb +++ b/config/initializers/new_framework_defaults_6_1.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # # This file contains migration options to ease your Rails 6.1 upgrade. diff --git a/config/initializers/new_framework_defaults_7_0.rb b/config/initializers/new_framework_defaults_7_0.rb index 5aefca516..eb40de828 100644 --- a/config/initializers/new_framework_defaults_7_0.rb +++ b/config/initializers/new_framework_defaults_7_0.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # # This file eases your Rails 7.0 framework defaults upgrade. diff --git a/config/routes.rb b/config/routes.rb index 489d26d95..7fbb8cbfd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -61,7 +61,7 @@ end scope "*full_name" do - constraints full_name: /[-_a-zA-Z0-9]+\/[-_\.a-zA-Z0-9]+/ do + constraints full_name: /[-_a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/ do get "/badges/:badge_type(.:format)", to: "badges#show", as: "badge" get "info(.:format)", to: "api_info#show" diff --git a/test/jobs/populate_issues_job_test.rb b/test/jobs/populate_issues_job_test.rb index fa37ae943..13da2f09c 100644 --- a/test/jobs/populate_issues_job_test.rb +++ b/test/jobs/populate_issues_job_test.rb @@ -9,7 +9,7 @@ class PopulateIssuesJobTest < ActiveJob::TestCase end test "Works when there's no issues" do - stub_request(:any, "https://api.github.com/repos/bemurphy/issue_triage_sandbox/issues?direction=desc&page=1&sort=comments&state=open") + stub_request(:any, "https://api.github.com/repos/bemurphy/issue_triage_sandbox/issues?direction=desc&page=1&per_page=#{GitHubConfig::ISSUES_PER_PAGE}&sort=comments&state=open") .to_return({body: [].to_json, status: 200}) repo = repos(:issue_triage_sandbox) diff --git a/test/test_helper.rb b/test/test_helper.rb index 2a5f4d94b..697d8c9aa 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -22,6 +22,7 @@ class ActiveSupport::TestCase class ActionDispatch::IntegrationTest # https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara include Warden::Test::Helpers + Warden.test_mode! setup do @@ -61,6 +62,25 @@ class ActionDispatch::IntegrationTest sensitive = ENV[secure] ||= secure c.filter_sensitive_data("<#{secure}>") { sensitive } end + + # Custom matcher that ignores per_page parameter + # This allows GITHUB_ISSUES_PER_PAGE env variable to be changed without breaking tests + c.register_request_matcher :uri_without_per_page do |request_1, request_2| + uri_1 = URI(request_1.uri) + uri_2 = URI(request_2.uri) + + # Compare everything except per_page query param + # URI.decode_www_form returns Array of arrays, convert to Hash to use except + params_1 = URI.decode_www_form(uri_1.query || "").to_h.except("per_page").sort + params_2 = URI.decode_www_form(uri_2.query || "").to_h.except("per_page").sort + + uri_1.scheme == uri_2.scheme && + uri_1.host == uri_2.host && + uri_1.path == uri_2.path && + params_1 == params_2 + end + + c.default_cassette_options = {match_requests_on: [:method, :uri_without_per_page]} end module ActionDispatch diff --git a/test/unit/git_hub_authenticator_test.rb b/test/unit/git_hub_authenticator_test.rb index 6e8d9ea98..3664d7ca5 100644 --- a/test/unit/git_hub_authenticator_test.rb +++ b/test/unit/git_hub_authenticator_test.rb @@ -39,7 +39,7 @@ class GitHubAuthenticatorTest < ActiveSupport::TestCase ) emails = Struct.new(:json_body).new ["john.doe@example.com"] - GitHubBub.expects(:get).with("/user/emails", token: "123qwe") + GitHubBub.expects(:get).with("/user/emails", {token: "123qwe"}) .returns emails user = GitHubAuthenticator.authenticate oauth diff --git a/test/unit/github_fetcher/issues_test.rb b/test/unit/github_fetcher/issues_test.rb index 310e25282..d34eddadb 100644 --- a/test/unit/github_fetcher/issues_test.rb +++ b/test/unit/github_fetcher/issues_test.rb @@ -63,14 +63,16 @@ def fetcher(repo) test "#last_page is true when it's the last page" do fetcher = fetcher(repos(:rails_rails)) - GitHubBub.stub(:get, ->(_, _) { OpenStruct.new(last_page?: true) }) do + # When next_url is nil, we're on the last page + GitHubBub.stub(:get, ->(_, _) { OpenStruct.new(next_url: nil, last_url: nil, last_page?: true) }) do assert fetcher.last_page? end end test "#last_page is false when it's not the last page" do fetcher = fetcher(repos(:rails_rails)) - GitHubBub.stub(:get, ->(_, _) { OpenStruct.new(last_page?: false) }) do + # When next_url exists and last_url exists, delegate to response.last_page? + GitHubBub.stub(:get, ->(_, _) { OpenStruct.new(next_url: "http://example.com?page=2", last_url: "http://example.com?page=5", last_page?: false) }) do assert_not fetcher.last_page? end end diff --git a/test/vcr_cassettes/blank_repo.yml b/test/vcr_cassettes/blank_repo.yml index d559312b0..c233bc9cf 100644 --- a/test/vcr_cassettes/blank_repo.yml +++ b/test/vcr_cassettes/blank_repo.yml @@ -254,7 +254,7 @@ http_interactions: recorded_at: Thu, 06 Jul 2017 20:04:39 GMT - request: method: get - uri: https://api.github.com/repos/issues?direction=desc&page=1&sort=comments&state=open + uri: https://api.github.com/repos/issues?direction=desc&page=1&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"state":"open","page":1,"sort":"comments","direction":"desc"}' diff --git a/test/vcr_cassettes/create_duplicate_repo_refinery.yml b/test/vcr_cassettes/create_duplicate_repo_refinery.yml index ef9b1fece..624bcaec7 100644 --- a/test/vcr_cassettes/create_duplicate_repo_refinery.yml +++ b/test/vcr_cassettes/create_duplicate_repo_refinery.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/repos/refinery/refinerycms/issues?direction=desc&page=1&sort=comments&state=open + uri: https://api.github.com/repos/refinery/refinerycms/issues?direction=desc&page=1&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"state":"open","page":1,"sort":"comments","direction":"desc"}' @@ -49,8 +49,8 @@ http_interactions: X-Github-Media-Type: - github.v3; param=3.raw; format=json Link: - - ; - rel="next", ; + - ; + rel="next", ; rel="last" Access-Control-Expose-Headers: - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, diff --git a/test/vcr_cassettes/create_repo_refinery.yml b/test/vcr_cassettes/create_repo_refinery.yml index 971511bc3..132e56d17 100644 --- a/test/vcr_cassettes/create_repo_refinery.yml +++ b/test/vcr_cassettes/create_repo_refinery.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/repos/refinery/refinerycms/issues?direction=desc&page=1&sort=comments&state=open + uri: https://api.github.com/repos/refinery/refinerycms/issues?direction=desc&page=1&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"state":"open","page":1,"sort":"comments","direction":"desc"}' @@ -49,8 +49,8 @@ http_interactions: X-Github-Media-Type: - github.v3; param=3.raw; format=json Link: - - ; - rel="next", ; + - ; + rel="next", ; rel="last" Access-Control-Expose-Headers: - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, diff --git a/test/vcr_cassettes/create_repo_without_issues.yml b/test/vcr_cassettes/create_repo_without_issues.yml index 6a90e2a2b..c7f65e377 100644 --- a/test/vcr_cassettes/create_repo_without_issues.yml +++ b/test/vcr_cassettes/create_repo_without_issues.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/repos/chrisccerami/scene-hub-v2/issues?direction=desc&page=1&sort=comments + uri: https://api.github.com/repos/chrisccerami/scene-hub-v2/issues?direction=desc&page=1&per_page=100&sort=comments body: encoding: UTF-8 string: '{"page":1,"sort":"comments","direction":"desc"}' @@ -72,7 +72,7 @@ http_interactions: recorded_at: Thu, 07 Apr 2016 17:27:00 GMT - request: method: get - uri: https://api.github.com/repos/chrisccerami/scene-hub-v2/issues?direction=desc&page=1&sort=comments&state=open + uri: https://api.github.com/repos/chrisccerami/scene-hub-v2/issues?direction=desc&page=1&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"state":"open","page":1,"sort":"comments","direction":"desc"}' diff --git a/test/vcr_cassettes/issue_triage_sandbox_fetch_issues.yml b/test/vcr_cassettes/issue_triage_sandbox_fetch_issues.yml index e97629d13..ae89b4943 100644 --- a/test/vcr_cassettes/issue_triage_sandbox_fetch_issues.yml +++ b/test/vcr_cassettes/issue_triage_sandbox_fetch_issues.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/repos/bemurphy/issue_triage_sandbox/issues?direction=desc&page=1&sort=comments&state=open + uri: https://api.github.com/repos/bemurphy/issue_triage_sandbox/issues?direction=desc&page=1&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"sort":"comments","direction":"desc","state":"open","page":1}' diff --git a/test/vcr_cassettes/rails_rails_fetch_issues.yml b/test/vcr_cassettes/rails_rails_fetch_issues.yml index 107a3e286..af56b3d75 100644 --- a/test/vcr_cassettes/rails_rails_fetch_issues.yml +++ b/test/vcr_cassettes/rails_rails_fetch_issues.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/repos/rails/rails/issues?direction=desc&page=1&sort=comments&state=open + uri: https://api.github.com/repos/rails/rails/issues?direction=desc&page=1&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"sort":"comments","direction":"desc","state":"open","page":1}' @@ -49,8 +49,8 @@ http_interactions: X-Github-Media-Type: - github.v3; param=3.raw; format=json Link: - - ; - rel="next", ; + - ; + rel="next", ; rel="last" Access-Control-Expose-Headers: - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, diff --git a/test/vcr_cassettes/rails_rails_fetch_issues_page_two.yml b/test/vcr_cassettes/rails_rails_fetch_issues_page_two.yml index ed33f8666..f67ae5740 100644 --- a/test/vcr_cassettes/rails_rails_fetch_issues_page_two.yml +++ b/test/vcr_cassettes/rails_rails_fetch_issues_page_two.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/repos/rails/rails/issues?direction=desc&page=2&sort=comments&state=open + uri: https://api.github.com/repos/rails/rails/issues?direction=desc&page=2&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"page":2,"sort":"comments","direction":"desc","state":"open"}' @@ -49,10 +49,10 @@ http_interactions: X-Github-Media-Type: - github.v3; param=3.raw; format=json Link: - - ; - rel="next", ; - rel="last", ; - rel="first", ; + - ; + rel="next", ; + rel="last", ; + rel="first", ; rel="prev" Access-Control-Expose-Headers: - ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, diff --git a/test/vcr_cassettes/repo_info.yml b/test/vcr_cassettes/repo_info.yml index 665d780a2..e29c234af 100644 --- a/test/vcr_cassettes/repo_info.yml +++ b/test/vcr_cassettes/repo_info.yml @@ -75,7 +75,7 @@ http_interactions: recorded_at: Thu, 07 Apr 2016 17:27:43 GMT - request: method: get - uri: https://api.github.com/repos/refinery/refinerycms/issues?direction=desc&page=1&sort=comments + uri: https://api.github.com/repos/refinery/refinerycms/issues?direction=desc&page=1&per_page=100&sort=comments body: encoding: UTF-8 string: '{"page":1,"sort":"comments","direction":"desc"}' @@ -3479,7 +3479,7 @@ http_interactions: recorded_at: Thu, 07 Apr 2016 17:27:43 GMT - request: method: get - uri: https://api.github.com/repos/refinery/refinerycms/issues?direction=desc&page=1&sort=comments&state=open + uri: https://api.github.com/repos/refinery/refinerycms/issues?direction=desc&page=1&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"state":"open","page":1,"sort":"comments","direction":"desc"}' diff --git a/test/vcr_cassettes/update_repo_info_schneems/sextant.yml b/test/vcr_cassettes/update_repo_info_schneems/sextant.yml index 00d3707f0..23134aeac 100644 --- a/test/vcr_cassettes/update_repo_info_schneems/sextant.yml +++ b/test/vcr_cassettes/update_repo_info_schneems/sextant.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://api.github.com/repos/schneems/sextant/issues?direction=desc&page=1&sort=comments + uri: https://api.github.com/repos/schneems/sextant/issues?direction=desc&page=1&per_page=100&sort=comments body: encoding: UTF-8 string: '{"page":1,"sort":"comments","direction":"desc"}' @@ -79,7 +79,7 @@ http_interactions: recorded_at: Thu, 07 Apr 2016 17:26:05 GMT - request: method: get - uri: https://api.github.com/repos/schneems/sextant/issues?direction=desc&page=1&sort=comments&state=open + uri: https://api.github.com/repos/schneems/sextant/issues?direction=desc&page=1&per_page=100&sort=comments&state=open body: encoding: UTF-8 string: '{"state":"open","page":1,"sort":"comments","direction":"desc"}'