diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7b2938fd879..44311095392 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -45,7 +45,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4 + uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -58,7 +58,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4 + uses: github/codeql-action/autobuild@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -71,6 +71,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4 + uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 77d45a51c4d..168058dfbb3 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ea9e4e37992a54ee68a9622e985e60c8e8f12d9f # v3.27.4 + uses: github/codeql-action/upload-sarif@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: sarif_file: results.sarif diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29f6aae442a..66bf812f8df 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,6 +72,6 @@ jobs: - name: Upload coverage to Codecov if: matrix.rubygems.name == 'locked' && (success() || failure()) - uses: codecov/codecov-action@3b1354a6c45db9f1008891f4eafc1a7e94ce1d18 # v5.0.1 + uses: codecov/codecov-action@015f24e6818733317a2da2edd6290ab26238649a # v5.0.7 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/Gemfile b/Gemfile index 1ecd30dbcc9..7cb3848187d 100644 --- a/Gemfile +++ b/Gemfile @@ -5,8 +5,8 @@ ruby file: ".ruby-version" gem "rails", "~> 7.2.1" gem "rails-i18n", "~> 7.0" -gem "aws-sdk-s3", "~> 1.171" -gem "aws-sdk-sqs", "~> 1.88" +gem "aws-sdk-s3", "~> 1.174" +gem "aws-sdk-sqs", "~> 1.89" gem "bootsnap", "~> 1.18" gem "clearance", "~> 2.9" gem "dalli", "~> 3.2" @@ -29,12 +29,12 @@ gem "omniauth", "~> 2.1" gem "omniauth-rails_csrf_protection", "~> 1.0" gem "openid_connect", "~> 2.3" gem "pg", "~> 1.5" -gem "puma", "~> 6.4" +gem "puma", "~> 6.5" gem "rack", "~> 3.1" gem "rackup", "~> 2.2" gem "rack-sanitizer", "~> 2.0" gem "rbtrace", "~> 0.5.1" -gem "rdoc", "~> 6.7" +gem "rdoc", "~> 6.8" gem "roadie-rails", "~> 3.3" gem "ruby-magic", "~> 0.6" gem "shoryuken", "~> 6.2", require: false @@ -49,22 +49,23 @@ gem "rack-attack", "~> 6.6" gem "rqrcode", "~> 2.1" gem "rotp", "~> 6.2" gem "unpwn", "~> 1.0" -gem "webauthn", "~> 3.1" +gem "webauthn", "~> 3.2" gem "browser", "~> 6.1" gem "bcrypt", "~> 3.1" -gem "maintenance_tasks", "~> 2.8" +gem "maintenance_tasks", "~> 2.9" gem "strong_migrations", "~> 2.1" gem "phlex-rails", "~> 1.2" gem "discard", "~> 1.4" gem "user_agent_parser", "~> 2.18" gem "pghero", "~> 3.6" -gem "timescaledb", "~> 0.3.0" gem "faraday-multipart", "~> 1.0" +gem "timescaledb", "~> 0.3" +gem "sigstore", "~> 0.2.1" # Admin dashboard gem "avo", "~> 3.13" gem "pagy", "~> 8.4" -gem "view_component", "~> 3.14.0" +gem "view_component", "~> 3.20.0" gem "pundit", "~> 2.4" gem "chartkick", "~> 5.1" gem "groupdate", "~> 6.5" @@ -128,17 +129,17 @@ group :development do end group :test do - gem "datadog-ci", "~> 1.8" + gem "datadog-ci", "~> 1.9" gem "minitest", "~> 5.25", require: false gem "minitest-retry", "~> 0.2.3" gem "capybara", "~> 3.40" gem "launchy", "~> 3.0" gem "rack-test", "~> 2.1", require: "rack/test" gem "rails-controller-testing", "~> 1.0" - gem "mocha", "~> 2.5", require: false + gem "mocha", "~> 2.6", require: false gem "shoulda-context", "~> 3.0.0.rc1" gem "shoulda-matchers", "~> 6.4" - gem "selenium-webdriver", "~> 4.26" + gem "selenium-webdriver", "~> 4.27" gem "webmock", "~> 3.24" gem "simplecov", "~> 0.22", require: false gem "simplecov-cobertura", "~> 2.1", require: false diff --git a/Gemfile.lock b/Gemfile.lock index 6baade10b9f..21b6b846e38 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,29 @@ GEM remote: https://packager.dev/avo-hq/ specs: - avo-advanced (3.14.0) - avo (= 3.14.0) - avo-dynamic_filters (= 3.14.0) - avo-pro (= 3.14.0) + avo-advanced (3.14.3) + avo (= 3.14.3) + avo-dynamic_filters (= 3.14.3) + avo-pro (= 3.14.3) zeitwerk (>= 2.6.12) - avo-dashboards (3.14.0) - avo (= 3.14.0) + avo-dashboards (3.14.3) + avo (= 3.14.3) turbo-rails view_component (>= 3.7.0) zeitwerk (>= 2.6.12) - avo-dynamic_filters (3.14.0) - avo (= 3.14.0) + avo-dynamic_filters (3.14.3) + avo (= 3.14.3) ransack (>= 4.2.0) view_component (>= 3.7.0) zeitwerk (>= 2.6.12) - avo-menu (3.14.0) - avo (= 3.14.0) + avo-menu (3.14.3) + avo (= 3.14.3) docile zeitwerk (>= 2.6.12) - avo-pro (3.14.0) - avo (= 3.14.0) - avo-dashboards (= 3.14.0) - avo-menu (= 3.14.0) + avo-pro (3.14.3) + avo (= 3.14.3) + avo-dashboards (= 3.14.3) + avo-menu (= 3.14.3) zeitwerk (>= 2.6.12) GEM @@ -115,7 +115,7 @@ GEM ffi-compiler (~> 1.0) ast (2.4.2) attr_required (1.0.2) - avo (3.14.0) + avo (3.14.3) actionview (>= 6.1) active_link_to activerecord (>= 6.1) @@ -133,22 +133,21 @@ GEM avo_upgrade (0.1.1) rails (>= 6.0.0) zeitwerk - awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.1008.0) - aws-sdk-core (3.213.0) + aws-partitions (1.1013.0) + aws-sdk-core (3.214.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.95.0) + aws-sdk-kms (1.96.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.171.0) + aws-sdk-s3 (1.174.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sdk-sqs (1.88.0) + aws-sdk-sqs (1.89.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) aws-sigv4 (1.10.1) @@ -202,7 +201,7 @@ GEM compact_index (0.15.0) concurrent-ruby (1.3.4) connection_pool (2.4.1) - cose (1.3.0) + cose (1.3.1) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) crack (1.0.0) @@ -218,7 +217,7 @@ GEM libdatadog (~> 14.1.0.1.0) libddwaf (~> 1.15.0.0.0) msgpack - datadog-ci (1.8.1) + datadog-ci (1.9.0) datadog (~> 2.4) msgpack datadog-ruby_core_source (3.3.6) @@ -354,7 +353,7 @@ GEM rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) - job-iteration (1.5.1) + job-iteration (1.7.0) activejob (>= 5.2) json (2.8.2) json-jwt (1.16.7) @@ -364,7 +363,8 @@ GEM bindata faraday (~> 2.0) faraday-follow_redirects - jwt (2.7.1) + jwt (2.9.3) + base64 kaminari (1.2.2) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.2) @@ -440,13 +440,13 @@ GEM net-imap net-pop net-smtp - maintenance_tasks (2.8.0) - actionpack (>= 6.0) - activejob (>= 6.0) - activerecord (>= 6.0) + maintenance_tasks (2.9.0) + actionpack (>= 6.1) + activejob (>= 6.1) + activerecord (>= 6.1) csv job-iteration (>= 1.3.6) - railties (>= 6.0) + railties (>= 6.1) zeitwerk (>= 2.6.2) marcel (1.0.4) matrix (0.4.2) @@ -457,7 +457,7 @@ GEM mini_histogram (0.3.1) mini_mime (1.1.5) mini_portile2 (2.8.8) - minitest (5.25.1) + minitest (5.25.2) minitest-gcstats (1.3.1) minitest (~> 5.0) minitest-reporters (1.7.1) @@ -467,7 +467,7 @@ GEM ruby-progressbar minitest-retry (0.2.3) minitest (>= 5.0) - mocha (2.5.0) + mocha (2.6.0) ruby2_keywords (>= 0.0.5) msgpack (1.7.5) multi_json (1.15.0) @@ -486,7 +486,7 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.3) + nio4r (2.7.4) nokogiri (1.16.7) mini_portile2 (~> 2.8.2) racc (~> 1.4) @@ -587,7 +587,7 @@ GEM psych (5.2.0) stringio public_suffix (6.0.1) - puma (6.4.3) + puma (6.5.0) nio4r (~> 2.0) pundit (2.4.0) activesupport (>= 3.0.0) @@ -673,7 +673,7 @@ GEM ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) - rdoc (6.7.0) + rdoc (6.8.1) psych (>= 4.0.0) redcarpet (3.6.0) regexp_parser (2.9.2) @@ -749,7 +749,7 @@ GEM activemodel (>= 6.1) hashie securerandom (0.3.2) - selenium-webdriver (4.26.0) + selenium-webdriver (4.27.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -765,7 +765,7 @@ GEM shoulda-context (3.0.0.rc1) shoulda-matchers (6.4.0) activesupport (>= 5.2.0) - sigstore (0.1.1) + sigstore (0.2.1) net-http protobug_sigstore_protos (~> 0.1.0) uri @@ -811,7 +811,7 @@ GEM activesupport pg (~> 1.2) toxiproxy (2.0.2) - tpm-key_attestation (0.12.0) + tpm-key_attestation (0.12.1) bindata (~> 2.4) openssl (> 2.0) openssl-signature_algorithm (~> 1.0) @@ -835,13 +835,12 @@ GEM validates_formatting_of (0.9.0) activemodel version_gem (1.1.1) - view_component (3.14.0) - activesupport (>= 5.2.0, < 8.0) + view_component (3.20.0) + activesupport (>= 5.2.0, < 8.1) concurrent-ruby (~> 1.0) method_source (~> 1.0) - webauthn (3.1.0) + webauthn (3.2.2) android_key_attestation (~> 0.3.0) - awrence (~> 1.1) bindata (~> 2.4) cbor (~> 0.5.9) cose (~> 1.1) @@ -883,8 +882,8 @@ DEPENDENCIES avo (~> 3.13) avo-advanced (~> 3.14)! avo_upgrade (~> 0.1.1) - aws-sdk-s3 (~> 1.171) - aws-sdk-sqs (~> 1.88) + aws-sdk-s3 (~> 1.174) + aws-sdk-sqs (~> 1.89) bcrypt (~> 3.1) better_html (~> 2.1) bootsnap (~> 1.18) @@ -897,7 +896,7 @@ DEPENDENCIES csv (~> 3.3) dalli (~> 3.2) datadog (~> 2.7) - datadog-ci (~> 1.8) + datadog-ci (~> 1.9) derailed_benchmarks (~> 2.2) discard (~> 1.4) dogstatsd-ruby (~> 5.6) @@ -925,13 +924,13 @@ DEPENDENCIES local_time (~> 3.0) lookbook (~> 2.3) mail (~> 2.8) - maintenance_tasks (~> 2.8) + maintenance_tasks (~> 2.9) memory_profiler (~> 1.1) minitest (~> 5.25) minitest-gcstats (~> 1.3) minitest-reporters (~> 1.7) minitest-retry (~> 0.2.3) - mocha (~> 2.5) + mocha (~> 2.6) observer (~> 0.1.2) octokit (~> 9.2) omniauth (~> 2.1) @@ -949,7 +948,7 @@ DEPENDENCIES propshaft (~> 1.1.0) prosopite (~> 1.4) pry-byebug (~> 3.10) - puma (~> 6.4) + puma (~> 6.5) pundit (~> 2.4) rack (~> 3.1) rack-attack (~> 6.6) @@ -962,7 +961,7 @@ DEPENDENCIES rails-i18n (~> 7.0) rails_semantic_logger (~> 4.17) rbtrace (~> 0.5.1) - rdoc (~> 6.7) + rdoc (~> 6.8) roadie-rails (~> 3.3) rotp (~> 6.2) rqrcode (~> 2.1) @@ -974,11 +973,11 @@ DEPENDENCIES rubocop-rails (~> 2.25) ruby-magic (~> 0.6) searchkick (~> 5.4) - selenium-webdriver (~> 4.26) + selenium-webdriver (~> 4.27) shoryuken (~> 6.2) shoulda-context (~> 3.0.0.rc1) shoulda-matchers (~> 6.4) - sigstore (~> 0.1.1) + sigstore (~> 0.2.1) simplecov (~> 0.22) simplecov-cobertura (~> 2.1) statsd-instrument (~> 3.9) @@ -990,8 +989,8 @@ DEPENDENCIES unpwn (~> 1.0) user_agent_parser (~> 2.18) validates_formatting_of (~> 0.9) - view_component (~> 3.14.0) - webauthn (~> 3.1) + view_component (~> 3.20.0) + webauthn (~> 3.2) webmock (~> 3.24) xml-simple (~> 1.1) @@ -1017,20 +1016,19 @@ CHECKSUMS argon2 (2.3.0) sha256=980ef65172bf512ad37b6cbb0d61eef40b6dccab6a7db4e70557527e1dce9557 ast (2.4.2) sha256=1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12 attr_required (1.0.2) sha256=f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99 - avo (3.14.0) sha256=ae8744b3bde7c9b3d41869e58214abf288d7ef6f230420746f012df083f1c0be - avo-advanced (3.14.0) sha256=9b4a450819e7ea4aa2b25ff07d4e6f0bd36bcb6c99e86469a2fd1be733b9fe17 - avo-dashboards (3.14.0) sha256=5b2c30fee710fdfec6d47d940d5018cb1afcd2020720e5ef436001dc2ee6387b - avo-dynamic_filters (3.14.0) sha256=05fd9e5846ad247310fbffed61b40be310d9334646e8d59afe6c724faff5b28e - avo-menu (3.14.0) sha256=f07243d2a52921d28718d7d9bdddb11eaebdc0dd5b07deed7a7fb767550be554 - avo-pro (3.14.0) sha256=0a20743f5c5e685a9088c9b753bc8419a68aac57f7fa2966485e9ceb61a82c5f + avo (3.14.3) sha256=14cc7e5ab58f77b78cfdfefb7f267a5728633c465439ac6baf963fe6ab959b09 + avo-advanced (3.14.3) sha256=39f5c88b8a135c54839b9d1572b68b42a38b19f76cf0b12f43e25bf844d56cab + avo-dashboards (3.14.3) sha256=7caec15df4511d8ed6a4a605172d7ab929cfa68d8efb202773beee8fa419f94e + avo-dynamic_filters (3.14.3) sha256=aa61534e51c5c8ee75775fcb6b3cacdbb04e12bdc9b425dd642ca9d325b6e1ef + avo-menu (3.14.3) sha256=0f86612687bead97b382fc21eba82af5590ec890cb4d340fc150cf82166e4bf5 + avo-pro (3.14.3) sha256=3c2bb725e9396c44ed8934cb035d19f267a7d76967666d27a78d885be2d97e6e avo_upgrade (0.1.1) sha256=8d841083b9956392f5c8fe195f25bec0d139e3646d276f8a59e66b7d2e9ebf30 - awrence (1.2.1) sha256=dd1d214c12a91f449d1ef81d7ee3babc2816944e450752e7522c65521872483e aws-eventstream (1.3.0) sha256=f1434cc03ab2248756eb02cfa45e900e59a061d7fbdc4a9fd82a5dd23d796d3f - aws-partitions (1.1008.0) sha256=6fb5e6b843ea1169480c804fc861a5de7407762097de75cf4734fbcd35466227 - aws-sdk-core (3.213.0) sha256=6ca685be1d72d61776fdaaddf3c293e45a472ff0dd0b624880e7813d0c82db19 - aws-sdk-kms (1.95.0) sha256=2ae508c642ddc59baa1296229108e9601a2fa00e57cf7a2153c9488f0587fd5e - aws-sdk-s3 (1.171.0) sha256=94a2210c20f6102d8867937b021ef40683aa351e28912ac9cc6ef20509f85f4f - aws-sdk-sqs (1.88.0) sha256=3e4e022b9af1796eb87bb368a8bb2001ebcad3b5025d76aa9ba731acea01a2eb + aws-partitions (1.1013.0) sha256=60e72ff88ae49a114bea693f4479ef7d3c0cdc4795009c1d9761db79ffc1d1fe + aws-sdk-core (3.214.0) sha256=24f2a0f29dc3b5d9ee38d6ff8341a66fba48a4ebca2424688f7bac9952d8488b + aws-sdk-kms (1.96.0) sha256=b1818e140b4d1b3cbe154e6b2df1d157f8c65aa297d488f69b5745995a6ba375 + aws-sdk-s3 (1.174.0) sha256=9a80d43a7816abd49dc8becc6bf6675d8dbc8bc01ad49dcc252899fc4c021f3f + aws-sdk-sqs (1.89.0) sha256=1db1e8a1dcf1a83a6328fe12a034fe89e1c7a73c6305b8cad089656e3a5389ba aws-sigv4 (1.10.1) sha256=8a140753f34de18125686b11e7adaed4ca3db06dfb50a478993bd437f7a203bb base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 @@ -1056,14 +1054,14 @@ CHECKSUMS compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b concurrent-ruby (1.3.4) sha256=d4aa926339b0a86b5b5054a0a8c580163e6f5dcbdfd0f4bb916b1a2570731c32 connection_pool (2.4.1) sha256=0f40cf997091f1f04ff66da67eabd61a9fe0d4928b9a3645228532512fab62f4 - cose (1.3.0) sha256=63247c66a5bc76e53926756574fe3724cc0a88707e358c90532ae2a320e98601 + cose (1.3.1) sha256=d5d4dbcd6b035d513edc4e1ab9bc10e9ce13b4011c96e3d1b8fe5e6413fd6de5 crack (1.0.0) sha256=c83aefdb428cdc7b66c7f287e488c796f055c0839e6e545fec2c7047743c4a49 crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d css_parser (1.19.1) sha256=1940dce01e3b9be18d6880e6d65162d984cc04ff28998cf4759beb999275209e csv (3.3.0) sha256=0bbd1defdc31134abefed027a639b3723c2753862150f4c3ee61cab71b20d67d dalli (3.2.8) sha256=2e63595084d91fae2655514a02c5d4fc0f16c0799893794abe23bf628bebaaa5 datadog (2.7.0) sha256=cea0c125acff6630966a2ad0bc01863ba1e1ff2886b4d38dc29f254f89ad02a2 - datadog-ci (1.8.1) sha256=c461acd83d36b5894716ea7b1c207fd4b7fa103994c0773e3936a68da4dfa594 + datadog-ci (1.9.0) sha256=f48a2ec91961b65ada1e13b964a42ed1e3ae6ee586736e3ee5690d0ceb302069 datadog-ruby_core_source (3.3.6) sha256=007c72450d3f5838c6d0ae4a6a77e5008bb29dd97d10ea3bf367f978d7c02f36 date (3.4.0) sha256=2e7fadaded625c9b3e35e254e42068d4bd8b8646ceab0744cbcbcfdafaa0a711 derailed_benchmarks (2.2.1) sha256=654280664fded41c9cd8fc27fc0fcfaf096023afab90eb4ac1185ba70c5d4439 @@ -1122,10 +1120,10 @@ CHECKSUMS io-console (0.7.2) sha256=f0dccff252f877a4f60d04a4dc6b442b185ebffb4b320ab69212a92b48a7a221 irb (1.14.1) sha256=5975003b58d36efaf492380baa982ceedf5aed36967a4d5b40996bc5c66e80f8 jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 - job-iteration (1.5.1) sha256=1428ad5b308adbaae8776c16b7792a846eb1ad7f4ab3c6e0f9668dd2ab1179e5 + job-iteration (1.7.0) sha256=7e9db935ce021280a030414995047f370b458597405b316a0bf59ecc9c9cad5d json (2.8.2) sha256=dd4fa6c9c81daecf72b86ea36e56ed8955fdbb4d4dc379c93d313a59344486cf json-jwt (1.16.7) sha256=ccabff4c6d1a14276b23178e8bebe513ef236399b72a0b886d7ed94800d172a5 - jwt (2.7.1) sha256=07357cd2f180739b2f8184eda969e252d850ac996ed0a23f616e8ff0a90ae19b + jwt (2.9.3) sha256=55fd07ccdd64c622d36859748f2290fb9c119ce30b482867504e9f12654d6a65 kaminari (1.2.2) sha256=c4076ff9adccc6109408333f87b5c4abbda5e39dc464bd4c66d06d9f73442a3e kaminari-actionview (1.2.2) sha256=1330f6fc8b59a4a4ef6a549ff8a224797289ebf7a3a503e8c1652535287cc909 kaminari-activerecord (1.2.2) sha256=0dd3a67bab356a356f36b3b7236bcb81cef313095365befe8e98057dd2472430 @@ -1151,7 +1149,7 @@ CHECKSUMS loofah (2.23.1) sha256=d0a07422cb3b69272e124afa914ef6d517e30d5496b7f1c1fc5b95481f13f75e lookbook (2.3.4) sha256=16484c9eb514ac0c23c4b59cfd5a52697141d35056e3a9c2a22b314c1b887605 mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad - maintenance_tasks (2.8.0) sha256=7ee8aa37ab39c6c3a5f4637878c1a343cc296596742248112458b922968d4a16 + maintenance_tasks (2.9.0) sha256=7146aa49f66c17b83c8a8d097ed503260c857fa698f3038a6cffbb36df1257c5 marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4 matrix (0.4.2) sha256=71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0 memory_profiler (1.1.0) sha256=79a17df7980a140c83c469785905409d3027ca614c42c086089d128b805aa8f8 @@ -1160,11 +1158,11 @@ CHECKSUMS mini_histogram (0.3.1) sha256=6a114b504e4618b0e076cc672996036870f7cc6f16b8e5c25c0c637726d2dd94 mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef mini_portile2 (2.8.8) sha256=8e47136cdac04ce81750bb6c09733b37895bf06962554e4b4056d78168d70a75 - minitest (5.25.1) sha256=3db6795a80634def1cf86fda79d2d83b59b25ce5e186fa675f73c565589d2ad8 + minitest (5.25.2) sha256=59b379d63e0058159127b545c4725d3106624c9be2b3e030ddaee825d59e83eb minitest-gcstats (1.3.1) sha256=cb25490f93aac02e3a5ff307e560d41afcdcafa7952c1c32efdeb9886b1f4711 minitest-reporters (1.7.1) sha256=5060413a0c95b8c32fe73e0606f3631c173a884d7900e50013e15094eb50562c minitest-retry (0.2.3) sha256=7b7f4896efb9b931a1acb442a40e5273c441f44946cf4c6a8eb8895838e7bf29 - mocha (2.5.0) sha256=7852595064e8ef4c6a3f6d8a5a5ab8c705168f913bb17929ab1c35f4dd4c7717 + mocha (2.6.0) sha256=76ac4f4702d7c86be602e61a957f719e7e9df48ac3799b1a892655ca416a5638 msgpack (1.7.5) sha256=ffb04979f51e6406823c03abe50e1da2c825c55a37dee138518cdd09d9d3aea8 multi_json (1.15.0) sha256=1fd04138b6e4a90017e8d1b804c039031399866ff3fbabb7822aea367c78615d multi_xml (0.7.1) sha256=4fce100c68af588ff91b8ba90a0bb3f0466f06c909f21a32f4962059140ba61b @@ -1175,7 +1173,7 @@ CHECKSUMS net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 net-smtp (0.5.0) sha256=5fc0415e6ea1cc0b3dfea7270438ec22b278ca8d524986a3ae4e5ae8d087b42a - nio4r (2.7.3) sha256=54b94cdd4b8f9dc39aaad5f699e97afae13efb44f2b180a6e724df76105ff604 + nio4r (2.7.4) sha256=d95dee68e0bb251b8ff90ac3423a511e3b784124e5db7ff5f4813a220ae73ca9 nokogiri (1.16.7) sha256=f819cbfdfb0a7b19c9c52c6f2ca63df0e58a6125f4f139707b586b9511d7fe95 nokogiri (1.16.7-aarch64-linux) sha256=78778d35f165b59513be31c0fe232c63a82cf97626ffba695b5f822e5da1d74b nokogiri (1.16.7-arm64-darwin) sha256=276dcea1b988a5b22b5acc1ba901d24b8e908c40b71dccd5d54a2ae279480dad @@ -1215,7 +1213,7 @@ CHECKSUMS pry-byebug (3.10.1) sha256=c8f975c32255bfdb29e151f5532130be64ff3d0042dc858d0907e849125581f8 psych (5.2.0) sha256=6603fe756bcaf14daa25bc17625f36c90931dcf70452ac1e8da19760dc310573 public_suffix (6.0.1) sha256=61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f - puma (6.4.3) sha256=24a4645c006811d83f2480057d1f54a96e7627b6b90e1c99b260b9dc630eb43e + puma (6.5.0) sha256=94d1b75cab7f356d52e4f1b17b9040a090889b341dbeee6ee3703f441dc189f2 pundit (2.4.0) sha256=43e6d27a9df082c04f0020999ce4dcf6742ecc5775d102ef2bfe9df041417572 pwned (2.3.0) sha256=63f5a9576f109203684e9dd053f815649fd5bc0a0348b7190568272641b22353 raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 @@ -1242,7 +1240,7 @@ CHECKSUMS rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe rb-inotify (0.10.1) sha256=050062d4f31d307cca52c3f6a7f4b946df8de25fc4bd373e1a5142e41034a7ca rbtrace (0.5.1) sha256=e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc - rdoc (6.7.0) sha256=b17d5f0f57b0853d7b880d4360a32c7caf8dbb81f8503a36426df809e617f379 + rdoc (6.8.1) sha256=0128002d1bfc4892bdd780940841e4ca41275f63781fd832d11bc8ba4461462c redcarpet (3.6.0) sha256=8ad1889c0355ff4c47174af14edd06d62f45a326da1da6e8a121d59bdcd2e9e9 regexp_parser (2.9.2) sha256=5a27e767ad634f8a4b544520d5cd28a0db7aa1198a5d7c9d7e11d7b3d9066446 reline (0.5.11) sha256=868d5f4dbfd9caafa70182f7f6fa258b70baee4e565d7cd9e70b4d5b11a7cb65 @@ -1275,13 +1273,13 @@ CHECKSUMS sawyer (0.9.2) sha256=fa3a72d62a4525517b18857ddb78926aab3424de0129be6772a8e2ba240e7aca searchkick (5.4.0) sha256=75d7256d3ec2af2dc11c2ba8160c86d80451f3f86447aae2ace1f79553de0bf3 securerandom (0.3.2) sha256=e8b2ffa651dfbbb26eb4bfb8ddcfff94221a93e3f118f39e0f7f94c14fea9dc0 - selenium-webdriver (4.26.0) sha256=bb0426ffe50e5940a6a5ed2978b4dfb1cb29e0e1c4d0a420d6aabf0f6c8e0690 + selenium-webdriver (4.27.0) sha256=8821f4ad60b935cfcdc5954c0a6642d894e936250aece8bf37a6fcbebe5eb6e0 semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163 semantic_logger (4.16.0) sha256=ffba0bd0e008ceaf6be26da588f610a61208b9a9f55676b32729e962904023d9 shoryuken (6.2.1) sha256=95ddc0a717624a54e799d25a0a05100cb5a0c3728a96211935c214faaf16b3b6 shoulda-context (3.0.0.rc1) sha256=6e0d9d52ab798c13bc2b490c8537d4bf30cfd318a1ea839c39a66d1d293c6a1a shoulda-matchers (6.4.0) sha256=9055bb7f4bb342125fb860809798855c630e05ef5e75837b3168b8e6ee1608b0 - sigstore (0.1.1) sha256=0c2c3c5d175b204252eeb1507bfb79e330009188d160525d2871b5272f958897 + sigstore (0.2.1) sha256=58031c34c7899dd6aac43c54d0ab1a5282a551804013d4b7cb6930a32cbc8775 simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5 simplecov-cobertura (2.1.0) sha256=2c6532e34df2e38a379d72cef9a05c3b16c64ce90566beebc6887801c4ad3f02 simplecov-html (0.12.3) sha256=4b1aad33259ffba8b29c6876c12db70e5750cb9df829486e4c6e5da4fa0aa07b @@ -1303,7 +1301,7 @@ CHECKSUMS timeout (0.4.2) sha256=8aca2d5ff98eb2f7a501c03f8c3622065932cc58bc58f725cd50a09e63b4cc19 timescaledb (0.3.0) sha256=9ce2b39417d30544054cb609fbd84e18e304c7b7952a793846b8f4489551a28f toxiproxy (2.0.2) sha256=2e3b53604fb921d40da3db8f78a52b3133fcae33e93d440725335b15974e440a - tpm-key_attestation (0.12.0) sha256=e133d80cf24fef0e7a7dfad00fd6aeff01fc79875fbfc66cd8537bbd622b1e6d + tpm-key_attestation (0.12.1) sha256=3c1315bed06ba3563aee98ff69c270d9b45b586a43ac2da250b23cad3c3caca3 turbo-rails (2.0.11) sha256=fc47674736372780abd2a4dc0d84bef242f5ca156a457cd7fa6308291e397fcf turbo_power (0.6.2) sha256=c9080d0d1bb79deed67bee2a7654dd38f9c903b57ad52b98d19d000958fde2cc tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b @@ -1315,8 +1313,8 @@ CHECKSUMS validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451 validates_formatting_of (0.9.0) sha256=139590a4b87596dbfb04d93e897bd2e6d30fb849d04fab0343e71ed2ca856e7e version_gem (1.1.1) sha256=3c2da6ded29045ddcc0387e152dc634e1f0c490b7128dce0697ccc1cf0915b6c - view_component (3.14.0) sha256=96816de1c40d276d9fac49316ee4d196de90b1ce6eb39373b887c639749e630c - webauthn (3.1.0) sha256=e545fcf17d8a6b821161a37c1c4bc8c3d2ead0ff6ff3b098f57f417e731790b7 + view_component (3.20.0) sha256=ac3192b80c2936521e5e60e585960942f40f745cf0a78d037bf6d36e703e228b + webauthn (3.2.2) sha256=46e70b234963c85bbf8ea8febc9a3cbf04569e34a73a570d86f68556f3f36a38 webfinger (2.1.3) sha256=567a52bde77fb38ca6b67e55db755f988766ec4651c1d24916a65aa70540695c webmock (3.24.0) sha256=be01357f6fc773606337ca79f3ba332b7d52cbe5c27587671abc0572dbec7122 websocket (1.2.11) sha256=b7e7a74e2410b5e85c25858b26b3322f29161e300935f70a0e0d3c35e0462737 diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg index f261c13645c..981a3294875 100644 --- a/app/assets/images/icons.svg +++ b/app/assets/images/icons.svg @@ -6,7 +6,7 @@ - + diff --git a/app/assets/stylesheets/modules/footer.css b/app/assets/stylesheets/modules/footer.css index b544f3fc0cc..077499ad70b 100644 --- a/app/assets/stylesheets/modules/footer.css +++ b/app/assets/stylesheets/modules/footer.css @@ -96,8 +96,8 @@ background-position: 0 -717px; } .footer__sponsor__domainr { background-position: 0 -804px; } - .footer__sponsor__whitesource { - background-position: 0 -885px; } + .footer__sponsor__mend { + background-position: 0 -895px; } .footer__sponsor__logo { margin-top: 5px; diff --git a/app/avo/resources/api_key.rb b/app/avo/resources/api_key.rb index c3c2251565e..4e47a01baf2 100644 --- a/app/avo/resources/api_key.rb +++ b/app/avo/resources/api_key.rb @@ -3,9 +3,11 @@ class Avo::Resources::ApiKey < Avo::BaseResource self.includes = [] class ExpiredFilter < Avo::Filters::ScopeBooleanFilter; end + class TrustedPublisherFilter < Avo::Filters::ScopeBooleanFilter; end def filters filter ExpiredFilter, arguments: { default: { expired: false, unexpired: true } } + filter TrustedPublisherFilter, arguments: { default: { trusted_publisher: true, not_trusted_publisher: true } } end def fields diff --git a/app/avo/resources/version.rb b/app/avo/resources/version.rb index 4e3e14de176..80c46c18658 100644 --- a/app/avo/resources/version.rb +++ b/app/avo/resources/version.rb @@ -13,9 +13,13 @@ def actions end class IndexedFilter < Avo::Filters::ScopeBooleanFilter; end + class TrustedPublisherFilter < Avo::Filters::ScopeBooleanFilter; end + class AttestationFilter < Avo::Filters::ScopeBooleanFilter; end def filters filter IndexedFilter, arguments: { default: { indexed: true, yanked: true } } + filter TrustedPublisherFilter, arguments: { default: { pushed_with_trusted_publishing: true, pushed_without_trusted_publishing: true } } + filter AttestationFilter, arguments: { default: { with_attestations: true, without_attestations: true } } end def fields # rubocop:disable Metrics @@ -74,6 +78,7 @@ def fields # rubocop:disable Metrics field :dependencies, as: :has_many field :gem_download, as: :has_one, name: "Downloads" field :deletion, as: :has_one + field :attestations, as: :has_many end end end diff --git a/app/controllers/organizations/gems_controller.rb b/app/controllers/organizations/gems_controller.rb new file mode 100644 index 00000000000..463f0c77adb --- /dev/null +++ b/app/controllers/organizations/gems_controller.rb @@ -0,0 +1,22 @@ +class Organizations::GemsController < ApplicationController + before_action :redirect_to_signin, unless: :signed_in? + before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled? + before_action :redirect_to_settings_strong_mfa_required, if: :mfa_required_weak_level_enabled? + + before_action :find_organization, only: %i[index] + + layout "subject" + + # GET /organizations/organization_id/gems + + def index + @gems = @organization.rubygems.with_versions.by_downloads.preload(:most_recent_version, :gem_download).load_async + @gems_count = @organization.rubygems.with_versions.count + end + + private + + def find_organization + @organization = Organization.find_by_handle!(params[:organization_id]) + end +end diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index 8ea9cb0e87b..e573f9ca907 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -1,10 +1,55 @@ class OrganizationsController < ApplicationController + before_action :redirect_to_signin, only: :index, unless: :signed_in? + before_action :redirect_to_new_mfa, only: :index, if: :mfa_required_not_yet_enabled? + before_action :redirect_to_settings_strong_mfa_required, only: :index, if: :mfa_required_weak_level_enabled? + + before_action :find_organization, only: %i[show edit update] + + layout "subject" + + # GET /organizations + def index + @memberships = current_user.memberships.includes(:organization) + end + + # GET /organizations/1 def show - render plain: flash[:notice] # HACK: for tests until this view is ready + @latest_events = [] # @organization.latest_events + @gems = @organization + .rubygems + .with_versions + .by_downloads + .preload(:most_recent_version, :gem_download) + .load_async + @gems_count = @organization.rubygems.with_versions.count + @memberships = @organization.memberships + @memberships_count = @organization.memberships.count + end + + def edit + add_breadcrumb t("breadcrumbs.org_name", name: @organization.handle), organization_path(@organization) + add_breadcrumb t("breadcrumbs.settings") + + authorize @organization + end + + def update + authorize @organization + + if @organization.update(organization_params) + redirect_to organization_path(@organization) + else + render :edit + end end private + def find_organization + @organization = Organization.find_by_handle!(params.permit(:id).require(:id)) + end + def organization_params + params.permit(organization: [:name]).require(:organization) end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 1adbf233817..e2f466fba3d 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -8,7 +8,8 @@ class ProfilesController < ApplicationController before_action :disable_cache, only: :edit def show - @user = User.find_by_slug!(params[:id]) + @user = User.confirmed.find_by_slug!(params[:id]) + return render_not_found unless @user @rubygems = @user.rubygems_downloaded.includes(%i[latest_version gem_download]).strict_loading end diff --git a/app/helpers/rubygems_helper.rb b/app/helpers/rubygems_helper.rb index a6f2db4279c..d652cb01316 100644 --- a/app/helpers/rubygems_helper.rb +++ b/app/helpers/rubygems_helper.rb @@ -56,7 +56,7 @@ def unsubscribe_link(rubygem) link_to t("rubygems.aside.links.unsubscribe"), rubygem_subscription_path(rubygem.slug), class: [:toggler, "gem__link", "t-list__item", style], id: "unsubscribe", - method: :delete, remote: true + method: :delete end def change_diff_link(rubygem, latest_version) diff --git a/app/models/api_key.rb b/app/models/api_key.rb index 924bf5fc3f2..efc228ced38 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -38,6 +38,9 @@ class ScopeError < RuntimeError; end scope :oidc, -> { joins(:oidc_id_token) } scope :not_oidc, -> { where.missing(:oidc_id_token) } + scope :trusted_publisher, -> { where("owner_type like ?", "OIDC::TrustedPublisher::%") } + scope :not_trusted_publisher, -> { where("owner_type not like ?", "OIDC::TrustedPublisher::%") } + def self.expire_all! transaction do unexpired.find_each.all?(&:expire!) diff --git a/app/models/organization.rb b/app/models/organization.rb index 1e6560d26a9..a9836df0c43 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -20,4 +20,16 @@ class Organization < ApplicationRecord after_create do record_event!(Events::OrganizationEvent::CREATED, actor_gid: memberships.first&.to_gid) end + + def self.find_by_handle(handle) + find_by("lower(handle) = lower(?)", handle) + end + + def self.find_by_handle!(handle) + find_by_handle(handle) || raise(ActiveRecord::RecordNotFound) + end + + def to_param + handle + end end diff --git a/app/models/user.rb b/app/models/user.rb index 8b25cd75531..3741b4c7003 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -27,6 +27,7 @@ class User < ApplicationRecord scope :not_deleted, -> { kept } scope :deleted, -> { with_discarded.discarded } scope :with_deleted, -> { with_discarded } + scope :confirmed, -> { where(email_confirmed: true) } has_many :ownerships, -> { confirmed }, dependent: :destroy, inverse_of: :user diff --git a/app/models/version.rb b/app/models/version.rb index f3aa3e75413..7dee3eb7fe4 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -197,6 +197,22 @@ def self.created_between(start_time, end_time) where(created_at: start_time..end_time).order(:created_at, :id) end + def self.pushed_with_trusted_publishing + joins(:pusher_api_key).merge(ApiKey.trusted_publisher) + end + + def self.pushed_without_trusted_publishing + left_joins(:pusher_api_key).merge(ApiKey.not_trusted_publisher.or(where(pusher_api_key: nil).only(:where))) + end + + def self.with_attestations + where.associated(:attestations) + end + + def self.without_attestations + where.missing(:attestations) + end + def platformed? platform != "ruby" end diff --git a/app/policies/api/application_policy.rb b/app/policies/api/application_policy.rb index 3d7d9b7f873..15b57a4bb8e 100644 --- a/app/policies/api/application_policy.rb +++ b/app/policies/api/application_policy.rb @@ -7,10 +7,6 @@ def initialize(api_key, scope) @scope = scope end - def resolve - raise NotImplementedError, "You must define #resolve in #{self.class}" - end - private attr_reader :api_key, :scope diff --git a/app/policies/api/nil_class_policy.rb b/app/policies/api/nil_class_policy.rb index 820bb9d6e89..f9718480442 100644 --- a/app/policies/api/nil_class_policy.rb +++ b/app/policies/api/nil_class_policy.rb @@ -6,6 +6,6 @@ def resolve end def destroy? - false + deny t(:forbidden) end end diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index 45051f2f443..d7bf716f226 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -9,10 +9,6 @@ def initialize(user, scope) @scope = scope end - def resolve - raise NotImplementedError, "You must define #resolve in #{self.class}" - end - private attr_reader :user, :scope @@ -26,38 +22,6 @@ def initialize(user, record) @error = nil end - def index? - false - end - - def show? - false - end - - def create? - false - end - - def new? - create? - end - - def update? - false - end - - def edit? - update? - end - - def destroy? - false - end - - def search? - index? - end - private delegate :t, to: I18n diff --git a/app/policies/organization_policy.rb b/app/policies/organization_policy.rb index 2dc35c3e1c6..8e2ac10e100 100644 --- a/app/policies/organization_policy.rb +++ b/app/policies/organization_policy.rb @@ -12,6 +12,8 @@ def update? organization_member_with_role?(user, :owner) || deny(t(:forbidden)) end + alias edit? update? + def create? true end diff --git a/app/policies/rubygem_policy.rb b/app/policies/rubygem_policy.rb index 41d761d014c..8c23df1c0d0 100644 --- a/app/policies/rubygem_policy.rb +++ b/app/policies/rubygem_policy.rb @@ -8,22 +8,10 @@ class Scope < ApplicationPolicy::Scope alias rubygem record delegate :organization, to: :rubygem - def show? - true - end - def create? user.present? end - def update? - false - end - - def destroy? - false - end - def configure_oidc? rubygem_owned_by_with_role?(user, minimum_required_role: :owner, minimum_required_org_role: :admin) end diff --git a/app/views/components/card/timeline_component.rb b/app/views/components/card/timeline_component.rb index 7f7ec86885e..47dcea1bdd8 100644 --- a/app/views/components/card/timeline_component.rb +++ b/app/views/components/card/timeline_component.rb @@ -2,10 +2,11 @@ class Card::TimelineComponent < ApplicationComponent include Phlex::Rails::Helpers::LinkTo + include Phlex::Rails::Helpers::ImageTag include Phlex::Rails::Helpers::TimeAgoInWords def view_template(&) - div(class: "flex grow ml-2 md:-ml-2 border-l-2 border-neutral-300") do + div(class: "flex grow ml-2 border-l-2 border-neutral-300") do div(class: "flex flex-col grow -mt-2", &) end end @@ -19,12 +20,43 @@ def timeline_item(datetime, user_link = nil, &) # Content div(class: "flex-1 flex-col ml-5 md:ml-7 pb-4 border-b border-neutral-300 dark:border-neutral-700") do div(class: "flex items-center justify-between") do - span(class: "text-b3 text-neutral-600") { t("time_ago", duration: time_ago_in_words(datetime)) } - span(class: "text-b3 text-neutral-800") { user_link } if user_link + span(class: "text-b3 text-neutral-600") do + helpers.local_time_ago(datetime, class: "text-b3 text-neutral-600") + end + span(class: "text-b3 text-neutral-800 dark:text-white max-h-6") { user_link } if user_link end div(class: "flex flex-wrap w-full items-center justify-between", &) end end end + + def link_to_user(user) + link_to(profile_path(user.display_id), alt: user.display_handle, title: user.display_handle, class: "flex items-center") do + span(class: "w-6 h-6 inline-block mr-2 rounded") { helpers.avatar(48, "gravatar-#{user.id}", user) } + span { user.display_handle } + end + end + + def link_to_api_key(api_key_owner) + case api_key_owner + when OIDC::TrustedPublisher::GitHubAction + div(class: "flex items-center") do + span(class: "w-6 h-6 inline-block mr-2 rounded") do + image_tag "github_icon.png", width: 48, height: 48, theme: :light, alt: "GitHub", title: api_key_owner.name + end + span { "GitHub Actions" } + end + else + raise ArgumentError, "unknown api_key_owner type #{api_key_owner.class}" + end + end + + def link_to_pusher(version) + if version.pusher.present? + link_to_user(version.pusher) + elsif version.pusher_api_key&.owner.present? + link_to_api_key(version.pusher_api_key.owner) + end + end end diff --git a/app/views/components/card_component.rb b/app/views/components/card_component.rb index fec79c3f747..7858d1664bc 100644 --- a/app/views/components/card_component.rb +++ b/app/views/components/card_component.rb @@ -27,16 +27,6 @@ def title(title, icon: nil, count: nil) end end - def with_list(items, &) - list do - items.each do |item| - list_item do - yield(item) - end - end - end - end - def list(**options, &) options[:class] = "#{options[:class]} -mx-4" ul(**options, &) @@ -49,7 +39,9 @@ def divided_list(**options, &) def list_item(**options, &) options[:class] = "#{options[:class]} #{LIST_ITEM_CLASSES}" - li(**options, &) + li do + div(**options, &) + end end def list_item_to(url = nil, **options, &) @@ -65,9 +57,9 @@ def list_item_to(url = nil, **options, &) # removes padding inside the "content" area of the card so scroll bar and overflaw appear correctly # adds a border to the top of the scrollable area to explain the content being hidden on scroll def scrollable(**options, &) - options[:class] = "#{options[:class]} lg:max-h-96 lg:overflow-y-auto" - options[:class] << " -mx-4 -mb-6 md:-mx-10 md:-mb-10" - options[:class] << " border-t border-neutral-200 dark:border-neutral-800" + options[:class] = "#{options[:class]} lg:max-h-96 lg:overflow-y-auto " \ + "-mx-4 -mb-6 md:-mx-10 md:-mb-10 " \ + "border-t border-neutral-200 dark:border-neutral-800" div(**options) do div(class: "px-4 pt-6 md:px-10 md:pt-10", &) end diff --git a/app/views/dashboards/_subject.html.erb b/app/views/dashboards/_subject.html.erb index 64981449be9..b7efadc3565 100644 --- a/app/views/dashboards/_subject.html.erb +++ b/app/views/dashboards/_subject.html.erb @@ -1,42 +1,46 @@ -<% - user ||= @user || current_user - current ||= :dashboard -%> +<%# locals: (user:, current:) -%> -
- <%= avatar 328, "user_gravatar", theme: :dark, class: "h-24 w-24 lg:h-40 lg:w-40 rounded-lg object-cover mr-4" %> +
+
+ <%= avatar 328, "user_gravatar", theme: :dark, class: "h-24 w-24 lg:h-40 lg:w-40 rounded-lg object-cover mr-4" %> -
-

<%= user.display_handle %>

- <% if user.full_name.present? %> -

<%= user.full_name %>

- <% end %> +
+

<%= user.display_handle %>

+ <% if user.full_name.present? %> +

<%= user.full_name %>

+ <% end %> +
-
-<% if user.public_email? || user == current_user %> -
- <%= icon_tag("mail", color: :primary, class: "h-6 w-6 text-orange mr-3") %> -

<%= - mail_to(user.email, encode: "hex") - %>

-
-<% end %> + <% if user.public_email? || user == current_user %> +
+ <%= icon_tag("mail", color: :primary, class: "h-6 w-6 text-orange mr-3") %> +

<%= + mail_to(user.email, encode: "hex") + %>

+
+ <% end %> -<% if user.twitter_username.present? %> -
- <%= icon_tag("x-twitter", color: :primary, class: "w-6 text-orange mr-3") %> -

<%= - link_to( - twitter_username(user), - twitter_url(user) - ) - %>

-
-<% end %> + <% if user.twitter_username.present? %> +
+ <%= icon_tag("x-twitter", color: :primary, class: "w-6 text-orange mr-3") %> +

<%= + link_to( + twitter_username(user), + twitter_url(user) + ) + %>

+
+ <% end %> +
+ + <%= render Subject::NavComponent.new(current:) do |nav| %> <%= nav.link t("layouts.application.header.dashboard"), dashboard_path, name: :dashboard, icon: "space-dashboard" %> <%= nav.link t("dashboards.show.my_subscriptions"), subscriptions_path, name: :subscriptions, icon: "notifications" %> + <% if current_user.memberships.any? %> + <%= nav.link t("dashboards.show.organizations"), organizations_path, name: :organizations, icon: "organizations" %> + <% end %> <%= nav.link t("layouts.application.header.settings"), edit_settings_path, name: :settings, icon: "settings" %> <% end %> diff --git a/app/views/dashboards/show.html.erb b/app/views/dashboards/show.html.erb index 5cfb8105f25..4037c8537e0 100644 --- a/app/views/dashboards/show.html.erb +++ b/app/views/dashboards/show.html.erb @@ -1,7 +1,7 @@ <% @title = t('.title') %> <% content_for :subject do %> - <% render "dashboards/subject", user: current_user %> + <%= render "dashboards/subject", user: current_user, current: :dashboard %> <% end %> @@ -27,14 +27,7 @@ <%= c.scrollable do %> <%= render Card::TimelineComponent.new do |t| %> <% @latest_updates.each do |version| %> - <% - pusher_link = if version.pusher.present? - link_to_user(version.pusher) - elsif version.pusher_api_key&.owner.present? - link_to_pusher(version.pusher_api_key.owner) - end - %> - <%= t.timeline_item(version.authored_at, pusher_link) do %> + <%= t.timeline_item(version.authored_at, t.link_to_pusher(version)) do %>
<%= link_to version.rubygem.name, rubygem_path(version.rubygem.slug) %>
<%= version_number(version) %> <% end %> @@ -101,9 +94,12 @@ <% end %> <%= c.divided_list do %> <% current_user.memberships.preload(:organization).each do |membership| %> - <%= c.list_item_to("#") do %> -
-

<%= membership.organization.name %>

+ <%= c.list_item_to(organization_path(membership.organization)) do %> +
+
+

<%= membership.organization.name %>

+

<%= membership.organization.handle %>

+

<%= membership.role %>

<% end %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 0c4f46be8b1..aa3422566f8 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -195,7 +195,7 @@ <%= t("layouts.application.footer.verified_by") %> Domainr - + <%= t("layouts.application.footer.secured_by") %> Whitesource diff --git a/app/views/layouts/hammy_component_preview.html.erb b/app/views/layouts/hammy_component_preview.html.erb new file mode 100644 index 00000000000..7fe2905389b --- /dev/null +++ b/app/views/layouts/hammy_component_preview.html.erb @@ -0,0 +1,34 @@ + + + + <%= page_title %> + + + <%= stylesheet_link_tag("hammy") %> + <%= stylesheet_link_tag("tailwind", "data-turbo-track": "reload") %> + + + <%= yield :head %> + <%= javascript_importmap_tags %> + + +
+ + <% if content_for?(:main) %> + <%= yield :main %> + <% else %> +
+
+ <% flash.each do |name, msg| %> + <%= render AlertComponent.new(style: name, closeable: true) do %> + <%= flash_message(name, msg) %> + <% end %> + <% end %> + + <%= yield %> +
+
+ <% end %> +
+ + diff --git a/app/views/organizations/_subject.html.erb b/app/views/organizations/_subject.html.erb new file mode 100644 index 00000000000..78ed96b9266 --- /dev/null +++ b/app/views/organizations/_subject.html.erb @@ -0,0 +1,25 @@ +<% current ||= :dashboard %> + +
+
+

<%= organization.name %>

+

<%= organization.handle %>

+

+ + <%= icon_tag("organizations", size: 6, class: "-mt-1 -ml-1 mr-1 inline-block") -%><%= t("organizations.show.organization") %> + +

+
+
+ + + +<%= render Subject::NavComponent.new(current:) do |nav| %> + <%= nav.link t("layouts.application.header.dashboard"), organization_path(@organization), name: :dashboard, icon: "space-dashboard" %> + <%= nav.link t("organizations.show.history"), organization_path(@organization), name: :subscriptions, icon: "notifications" %> + <%= nav.link t("organizations.show.gems"), organization_gems_path(@organization), name: :gems, icon: "gems" %> + <%= nav.link t("organizations.show.members"), organization_path(@organization), name: :organizations, icon: "organizations" %> + <% if policy(@organization).edit? %> + <%= nav.link t("layouts.application.header.settings"), edit_organization_path(@organization), name: :settings, icon: "settings" %> + <% end %> +<% end %> diff --git a/app/views/organizations/edit.html.erb b/app/views/organizations/edit.html.erb new file mode 100644 index 00000000000..e8467b4d86a --- /dev/null +++ b/app/views/organizations/edit.html.erb @@ -0,0 +1,26 @@ +<% content_for :subject do %> + <%= render "organizations/subject", organization: @organization, current: :settings %> +<% end %> + +

<%= t("layouts.application.header.settings") %>

+ +<%= render CardComponent.new do |c| %> + <%= c.head do %> + <%= c.title t("layouts.application.header.settings"), icon: "settings" %> + <% end %> + <%= form_with model: @organization, url: organization_path(@organization), method: :patch do |form| %> +
+ <%= form.label :name, "Display Name", class: "block text-b2 font-semibold mb-2" %> + <%= form.text_field( + :name, + class: "w-full text-b2 rounded-lg dark:bg-neutral-950 focus:ring-inset focus:ring-1 focus:ring-orange-500", + required: true, + autocomplete: "organization", + ) %> +
+ +
+ <%= render ButtonComponent.new("Update", type: :submit, style: :outline) %> +
+ <% end %> +<% end %> diff --git a/app/views/organizations/gems/index.html.erb b/app/views/organizations/gems/index.html.erb new file mode 100644 index 00000000000..9b08267a365 --- /dev/null +++ b/app/views/organizations/gems/index.html.erb @@ -0,0 +1,41 @@ +<% + add_breadcrumb t("breadcrumbs.org_name", name: @organization.handle), organization_path(@organization) + add_breadcrumb t("breadcrumbs.gems") +%> + +<% content_for :subject do %> + <%= render "organizations/subject", organization: @organization, current: :gems %> +<% end %> + +

<%= t("organizations.show.gems") %>

+ +<%= render CardComponent.new do |c| %> + <%= c.head do %> + <%= c.title t("organizations.show.gems"), icon: "gems", count: @gems_count %> + <% end %> + <% if @gems.empty? %> + <%= prose do %> + <%= t('organizations.show.no_gems') %> + <% end %> + <% else %> + <%= c.divided_list do %> + <% @gems.each do |rubygem| %> + <%= c.list_item_to( + rubygem_path(rubygem.slug), + title: short_info(rubygem.most_recent_version), + ) do %> +
+
+

<%= rubygem.name %>

+ <%= version_number(rubygem.most_recent_version) %> +
+
+ <%= download_count_component(rubygem, class: "flex") %> +
<%= version_date_component(rubygem.most_recent_version) %>
+
+
+ <% end %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/app/views/organizations/index.html.erb b/app/views/organizations/index.html.erb new file mode 100644 index 00000000000..8621c7d0e84 --- /dev/null +++ b/app/views/organizations/index.html.erb @@ -0,0 +1,34 @@ +<% + @title = t('.title') + add_breadcrumb "Dashboard", dashboard_path + add_breadcrumb "Organizations" +%> + +<% content_for :subject do %> + <%= render "dashboards/subject", user: current_user, current: :organizations %> +<% end %> + + +

+ <%= t("dashboards.show.organizations") %> + <% unless @memberships.size.zero? %> + <%= @memberships.size %> + <% end %> +

+ + +<%= render CardComponent.new do |c| %> + <%= c.divided_list do %> + <% @memberships.each do |membership| %> + <%= c.list_item_to(organization_path(membership.organization.handle)) do %> +
+
+

<%= membership.organization.name %>

+

<%= membership.organization.handle %>

+
+

<%= membership.role %>

+
+ <% end %> + <% end %> + <% end %> +<% end %> diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb new file mode 100644 index 00000000000..28ccb45a4e5 --- /dev/null +++ b/app/views/organizations/show.html.erb @@ -0,0 +1,90 @@ +<% add_breadcrumb t("breadcrumbs.org_name", name: @organization.handle) %> + +<% content_for :subject do %> + <%= render "organizations/subject", organization: @organization, current: :dashboard %> +<% end %> + +

<%= t("dashboards.show.title") %>

+ +<%= render CardComponent.new do |c| %> + <%= c.head(divide: true) do %> + <%= c.title t(".history"), icon: :history %> + <% end %> + + <% if @latest_events.empty? %> + <%= prose do %> + <%= t('.no_history') %> + <% end %> + <% else %> + <%= c.scrollable do %> + <%= render Card::TimelineComponent.new do |t| %> + <% @latest_events.each do |version| %> + <% + pusher_link = if version.pusher.present? + link_to_user(version.pusher) + elsif version.pusher_api_key&.owner.present? + link_to_pusher(version.pusher_api_key.owner) + end + %> + <%= t.timeline_item(version.authored_at, pusher_link) do %> +
<%= link_to version.rubygem.name, rubygem_path(version.rubygem.slug) %>
+ <%= version_number(version) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> +<% end %> + +<%= render CardComponent.new do |c| %> + <%= c.head do %> + <%= c.title t(".gems"), icon: "gems", count: @gems_count %> + <% end %> + <% if @gems.empty? %> + <%= prose do %> + <%= t('.no_gems') %> + <% end %> + <% else %> + <%= c.divided_list do %> + <% @gems.each do |rubygem| %> + <%= c.list_item_to( + rubygem_path(rubygem.slug), + title: short_info(rubygem.most_recent_version), + ) do %> +
+
+

<%= rubygem.name %>

+ <%= version_number(rubygem.most_recent_version) %> +
+
+ <%= download_count_component(rubygem, class: "flex") %> +
<%= version_date_component(rubygem.most_recent_version) %>
+
+
+ <% end %> + <% end %> + <% end %> + <% end %> +<% end %> + +<%= render CardComponent.new do |c| %> + <%= c.head do %> + <%= c.title t(".members"), icon: "organizations", count: @memberships_count %> + <% end %> + <% if @memberships.empty? %> + <%= prose do %> + <%= t('.no_members') %> + <% end %> + <% else %> + <%= c.divided_list do %> + <% @memberships.each do |membership| %> + <%= c.list_item_to(profile_path(membership.user.handle)) do %> +
+

<%= membership.user.name %>

+

<%= membership.role %>

+
+ <% end %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/app/views/profiles/edit.html.erb b/app/views/profiles/edit.html.erb index d56e7bdf80b..9328c5ba61d 100644 --- a/app/views/profiles/edit.html.erb +++ b/app/views/profiles/edit.html.erb @@ -66,7 +66,7 @@

<%= t('.enter_password') %>

- <%= form.password_field :password, autocomplete: 'current-password', class: 'form__input' %> + <%= form.password_field :password, autocomplete: 'current-password', class: 'form__input', required: true %>
diff --git a/app/views/rubygems/_aside_yanked.html.erb b/app/views/rubygems/_aside_yanked.html.erb new file mode 100644 index 00000000000..8f38d52e008 --- /dev/null +++ b/app/views/rubygems/_aside_yanked.html.erb @@ -0,0 +1,23 @@ +
+ <% if @adoption %> + <%= link_to "adoption", rubygem_adoptions_path(@rubygem.slug), class: "adoption__tag" %> + <% end %> + + <% if @rubygem.metadata_mfa_required? %> +

+ <%= t('.requires_mfa') %>: + + true + +

+ <% end %> +
+ <%= unsubscribe_link(@rubygem) %> + <%= ownership_link(@rubygem) if policy(@rubygem).show_unconfirmed_ownerships? %> + <%= rubygem_trusted_publishers_link(@rubygem) if policy(@rubygem).configure_trusted_publishers? %> + <%= oidc_api_key_role_links(@rubygem) if policy(@rubygem).configure_oidc? %> + <%= resend_owner_confirmation_link(@rubygem) if @rubygem.unconfirmed_ownership?(current_user) %> + <%= rubygem_adoptions_link(@rubygem) if policy(@rubygem).show_adoption? %> + <%= rubygem_security_events_link(@rubygem) if policy(@rubygem).show_events? %> +
+
diff --git a/app/views/rubygems/show_yanked.html.erb b/app/views/rubygems/show_yanked.html.erb index 61b2755fa1a..c48eede221f 100644 --- a/app/views/rubygems/show_yanked.html.erb +++ b/app/views/rubygems/show_yanked.html.erb @@ -19,4 +19,6 @@ <%= render partial: "rubygems/gem_members", locals: { latest_version: @latest_version, rubygem: @rubygem } %> <% end %>
+ + <%= render "rubygems/aside_yanked" %> diff --git a/app/views/subscriptions/index.html.erb b/app/views/subscriptions/index.html.erb index 77ddebf2171..409d650b171 100644 --- a/app/views/subscriptions/index.html.erb +++ b/app/views/subscriptions/index.html.erb @@ -17,22 +17,26 @@ <%= t("dashboards.show.no_subscriptions_html", :gem_link => link_to(t('dashboards.show.gem_link_text'), rubygem_path("rake"))) %> <% end %> <% else %> - <%= c.with_list(@subscribed_gems) do |gem| %> -
- <%= link_to rubygem_path(gem.slug) do %> -

<%= gem.name %>

-

<%= short_info(gem.most_recent_version) %>

+ <%= c.list do %> + <% @subscribed_gems.each do |gem| %> + <%= c.list_item do %> +
+ <%= link_to rubygem_path(gem.slug) do %> +

<%= gem.name %>

+

<%= short_info(gem.most_recent_version) %>

+ <% end %> + <%= button_to( + rubygem_subscription_path(gem.slug), + method: :delete, + title: t("rubygems.aside.links.unsubscribe"), + class: "h-8 w-8 ml-6 items-center justify-center outline-none -mr-2", + aria: { label: t("rubygems.aside.links.unsubscribe") } + ) do %> + <%= icon_tag "close", class: "w-6 h-6" %> + <% end %> +
<% end %> - <%= button_to( - rubygem_subscription_path(gem.slug), - method: :delete, - title: t("rubygems.aside.links.unsubscribe"), - class: "h-8 w-8 ml-6 items-center justify-center outline-none -mr-2", - aria: { label: t("rubygems.aside.links.unsubscribe") } - ) do %> - <%= icon_tag "close", class: "w-6 h-6" %> - <% end %> -
+ <% end %> <% end %> <% end %> <% end %> diff --git a/config/initializers/better_html.rb b/config/initializers/better_html.rb index 3eb5dc80cfe..37363b02b71 100644 --- a/config/initializers/better_html.rb +++ b/config/initializers/better_html.rb @@ -1,5 +1,7 @@ BetterHtml.configure do |config| config.template_exclusion_filter = proc { |filename| - filename.include?("avo") || filename.include?("/railties-") + filename.include?("avo") || + filename.include?("/railties-") || + filename.include?("lookbook-") } end diff --git a/config/initializers/lookbook.rb b/config/initializers/lookbook.rb index 47f5fba85cf..83e88a99f8f 100644 --- a/config/initializers/lookbook.rb +++ b/config/initializers/lookbook.rb @@ -1 +1,16 @@ Rails.application.config.lookbook.preview_layout = "component_preview" if Rails.env.local? + +if Rails.env.development? + module Lookbook::PreviewOverrides + # see https://github.com/ViewComponent/lookbook/issues/584 + def render(component = nil, **args, &block) + if block + super { component.instance_exec component, &block } + else + super + end + end + end + + Rails.application.configure { Lookbook::Preview.prepend Lookbook::PreviewOverrides } +end diff --git a/config/locales/de.yml b/config/locales/de.yml index 66e603971e6..b4f178cef70 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -296,6 +296,8 @@ de: settings: Einstellungen org_name: 'Organisation: %{name}' subscriptions: Abonnements + gems: + members: mailer: confirm_your_email: Bitte bestätigen Sie Ihre E-Mail-Adresse mit dem Link, der an Ihre E-Mail gesendet wurde. @@ -466,6 +468,16 @@ de: popular_gems: Beliebte Gems popular: title: Neue Veröffentlichungen — Beliebte Gems + organizations: + index: + title: + show: + organization: + gems: + history: + members: + no_history: + no_gems: pages: about: contributors_amount: "%{count} Rubyists" diff --git a/config/locales/en.yml b/config/locales/en.yml index d88cefe17e9..0a7860a919c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -282,6 +282,8 @@ en: settings: Settings org_name: "Org: %{name}" subscriptions: Subscriptions + gems: Gems + members: Members mailer: confirm_your_email: Please confirm your email address with the link sent to your email. confirmation_subject: Please confirm your email address with %{host} @@ -416,6 +418,16 @@ en: popular_gems: Popular Gems popular: title: New Releases — Popular Gems + organizations: + index: + title: Organizations + show: + organization: Organization + gems: Gems + history: History + members: Members + no_history: No events yet + no_gems: No gems yet pages: about: contributors_amount: "%{count} Rubyists" @@ -739,8 +751,8 @@ en: show_yanked: not_hosted_notice: This gem is not currently hosted on RubyGems.org. Yanked versions of this gem may already exist. reserved_namespace_html: - one: This gem previously existed, but has been removed by its owner. The RubyGems.org team has reserved this gem name for 1 more day. After that time is up, anyone will be able to claim this gem name using gem push.
If you are the previous owner of this gem, you can change ownership of this gem using the gem owner command. You can also create new versions of this gem using gem push. - other: This gem previously existed, but has been removed by its owner. The RubyGems.org team has reserved this gem name for %{count} more days. After that time is up, anyone will be able to claim this gem name using gem push.
If you are the previous owner of this gem, you can change ownership of this gem using the gem owner command. You can also create new versions of this gem using gem push. + one: This gem previously existed, but has been removed by its owner. The RubyGems.org team has reserved this gem name for 1 more day. After that time is up, anyone will be able to claim this gem name using gem push.

If you are the previous owner of this gem, you can change ownership of this gem using the gem owner command or create new versions of this gem using gem push. + other: This gem previously existed, but has been removed by its owner. The RubyGems.org team has reserved this gem name for %{count} more days. After that time is up, anyone will be able to claim this gem name using gem push.

If you are the previous owner of this gem, you can change ownership of this gem using the gem owner command or create new versions of this gem using gem push. security_events: title: Security Events description_html: "This page shows the security events that have occurred on %{gem}. If you see any suspicious activity, please contact support." diff --git a/config/locales/es.yml b/config/locales/es.yml index ee7e6a0d528..db31e6a96e9 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -295,6 +295,8 @@ es: settings: Configuración org_name: 'Org: %{name}' subscriptions: Suscripciones + gems: + members: mailer: confirm_your_email: Por favor confirma tu dirección de correo con el enlace enviado. confirmation_subject: Por favor confirma tu dirección de correo con %{host} @@ -457,6 +459,16 @@ es: popular_gems: Gemas más populares popular: title: Nuevos lanzamientos — Gemas más populares + organizations: + index: + title: + show: + organization: + gems: + history: + members: + no_history: + no_gems: pages: about: contributors_amount: "%{count} Rubystas" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 612d974d890..b5a87ed326f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -287,6 +287,8 @@ fr: settings: Paramètres org_name: 'Org: %{name}' subscriptions: Abonnements + gems: + members: mailer: confirm_your_email: Veuillez confirmer votre adresse email avec le lien qui vous a été envoyé par email. @@ -422,6 +424,16 @@ fr: popular_gems: Gems populaires popular: title: Nouvelles Versions - Gems populaires + organizations: + index: + title: + show: + organization: + gems: + history: + members: + no_history: + no_gems: pages: about: contributors_amount: diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 017c949cf28..cc43432f515 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -277,6 +277,8 @@ ja: settings: 設定 org_name: 組織:%{name} subscriptions: 購読 + gems: + members: mailer: confirm_your_email: Eメールに送信されたリンクからEメールアドレスを確認してください。 confirmation_subject: "%{host}に登録されたメールアドレスを確認してください" @@ -416,6 +418,16 @@ ja: popular_gems: 人気のgem popular: title: 新しいリリース - 人気のgem + organizations: + index: + title: + show: + organization: + gems: + history: + members: + no_history: + no_gems: pages: about: contributors_amount: "%{count}人以上のRubyist" diff --git a/config/locales/nl.yml b/config/locales/nl.yml index e290c8869b8..4d12bdc8de0 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -278,6 +278,8 @@ nl: settings: Instellingen org_name: 'Organisatie: %{name}' subscriptions: Abonnementen + gems: + members: mailer: confirm_your_email: confirmation_subject: @@ -406,6 +408,16 @@ nl: popular_gems: popular: title: + organizations: + index: + title: + show: + organization: + gems: + history: + members: + no_history: + no_gems: pages: about: contributors_amount: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index ce8a03c68ae..5aa7ccc65c5 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -284,6 +284,8 @@ pt-BR: settings: Configurações da Conta org_name: 'Org: %{name}' subscriptions: Observando + gems: + members: mailer: confirm_your_email: Por favor, confirme seu endereço de email através do link que enviamos para o seu endereço de email. @@ -419,6 +421,16 @@ pt-BR: popular_gems: Gems Populares popular: title: Novos Releases - Gems Populares + organizations: + index: + title: + show: + organization: + gems: + history: + members: + no_history: + no_gems: pages: about: contributors_amount: diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index 0e707dafd0c..b8de0c284b4 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -277,6 +277,8 @@ zh-CN: settings: 设置 org_name: 组织:%{name} subscriptions: 订阅 + gems: + members: mailer: confirm_your_email: 请在发送到您的邮件中点击链接,确认您的邮箱地址。 confirmation_subject: 请确认您的邮箱地址 @@ -420,6 +422,16 @@ zh-CN: popular_gems: 热门 Gem popular: title: 新的发布 — 热门 Gem + organizations: + index: + title: + show: + organization: + gems: + history: + members: + no_history: + no_gems: pages: about: contributors_amount: diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index c0962e266f1..e5153572d9b 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -276,6 +276,8 @@ zh-TW: settings: 設定 org_name: 組織:%{name} subscriptions: 訂閱 + gems: + members: mailer: confirm_your_email: 已寄送連結,請點擊連結來確認您的電子郵件地址。 confirmation_subject: "%{host} 電子郵件地址確認" @@ -412,6 +414,16 @@ zh-TW: popular_gems: 熱門 popular: title: 熱門新發佈 + organizations: + index: + title: + show: + organization: + gems: + history: + members: + no_history: + no_gems: pages: about: contributors_amount: "%{count} 位 Ruby 愛好者" diff --git a/config/routes.rb b/config/routes.rb index e99f9cd795c..7207d02133b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -288,7 +288,9 @@ patch "confirm", to: "confirm#update" end end - resources :organizations, only: %i[show], constraints: { id: Patterns::ROUTE_PATTERN } + resources :organizations, only: %i[index show edit update], constraints: { id: Patterns::ROUTE_PATTERN } do + resources :gems, only: :index, controller: 'organizations/gems' + end end ################################################################################ diff --git a/public/sponsors.png b/public/sponsors.png index 867acc4296b..2d925845ca4 100644 Binary files a/public/sponsors.png and b/public/sponsors.png differ diff --git a/test/components/previews/alert_component_preview.rb b/test/components/previews/alert_component_preview.rb new file mode 100644 index 00000000000..f95aa5bf072 --- /dev/null +++ b/test/components/previews/alert_component_preview.rb @@ -0,0 +1,12 @@ +class AlertComponentPreview < Lookbook::Preview + layout "hammy_component_preview" + + # @param content text "content" + # @param style select "style", { choices: [notice, alert, error, success, primary, neutral] } + # @param closeable toggle "closeable" + def default(content = "Example content", style: :notice, closeable: false) + render AlertComponent.new(style:, closeable:) do + content + end + end +end diff --git a/test/components/previews/button_component_preview.rb b/test/components/previews/button_component_preview.rb new file mode 100644 index 00000000000..2c48968e2e1 --- /dev/null +++ b/test/components/previews/button_component_preview.rb @@ -0,0 +1,60 @@ +class ButtonComponentPreview < Lookbook::Preview + layout "hammy_component_preview" + + # @param text text "text" + # @param url url "link" + # @param type select "type", { choices: [button, link, submit] } + # @param color select "color", { choices: [primary, secondary, red, orange, hammy, yellow, green, blue, neutral] } + # @param size select "size", { choices: [small, large] } + # @param style select "style", { choices: [fill, outline, plain] } + # @param disabled toggle "disabled" + def default(text: "Button", url: "", type: :button, color: :primary, size: :large, style: :fill, disabled: false) # rubocop:disable Metrics/ParameterLists + args = [text, url].compact_blank + render ButtonComponent.new( + *args, + type: type, + color: color, + size: size, + style: style, + disabled: disabled + ) + end + + # @param text text "text" + # @param url url "link" + # @param type select "type", { choices: [button, link, submit] } + # @param color select "color", { choices: [primary, secondary, red, orange, hammy, yellow, green, blue, neutral] } + # @param size select "size", { choices: [small, large] } + # @param style select "style", { choices: [fill, outline, plain] } + # @param disabled toggle "disabled" + def outline(text: "Button", url: "", type: :button, color: :primary, size: :large, style: :outline, disabled: false) # rubocop:disable Metrics/ParameterLists + args = [text, url].compact_blank + render ButtonComponent.new( + *args, + type: type, + color: color, + size: size, + style: style, + disabled: disabled + ) + end + + # @param text text "text" + # @param url url "link" + # @param type select "type", { choices: [button, link, submit] } + # @param color select "color", { choices: [primary, secondary, red, orange, hammy, yellow, green, blue, neutral] } + # @param size select "size", { choices: [small, large] } + # @param style select "style", { choices: [fill, outline, plain] } + # @param disabled toggle "disabled" + def plain(text: "Button", url: "", type: :button, color: :primary, size: :large, style: :plain, disabled: false) # rubocop:disable Metrics/ParameterLists + args = [text, url].compact_blank + render ButtonComponent.new( + *args, + type: type, + color: color, + size: size, + style: style, + disabled: disabled + ) + end +end diff --git a/test/components/previews/card/timeline_component_preview.rb b/test/components/previews/card/timeline_component_preview.rb new file mode 100644 index 00000000000..117fd4af9e0 --- /dev/null +++ b/test/components/previews/card/timeline_component_preview.rb @@ -0,0 +1,35 @@ +class Card::TimelineComponentPreview < Lookbook::Preview + layout "hammy_component_preview" + + # @param datetime datetime-local "datetime" + # @param user_link url "user link" + # @param content text "content" + def default(datetime: 1.day.ago, user_link: nil, content: nil) + block = proc { content } if content + + render CardComponent.new do |c| + c.head("Timeline", icon: "history") + c.scrollable do + render Card::TimelineComponent.new do |t| + t.timeline_item(datetime, user_link, &block) + end + end + end + end + + def with_content(datetime: 1.day.ago, user_link: nil) + render CardComponent.new do |c| + c.head("Timeline", icon: "history") + c.scrollable do + render Card::TimelineComponent.new do |t| + t.timeline_item(datetime, user_link) do + <<~HTML.html_safe # rubocop:disable Rails/OutputSafety +
gemname
+ 1.63.1 + HTML + end + end + end + end + end +end diff --git a/test/components/previews/card_component_preview.rb b/test/components/previews/card_component_preview.rb new file mode 100644 index 00000000000..417febf5c74 --- /dev/null +++ b/test/components/previews/card_component_preview.rb @@ -0,0 +1,56 @@ +class CardComponentPreview < Lookbook::Preview + layout "hammy_component_preview" + + # @param title text "Card title" + # @param icon text "icon name" + # @param count number "count (blank for no count)" + # @param url url "view all link (blank for no link)" + def default(title: "Gems", icon: "gems", count: nil, url: nil) + render CardComponent.new do |c| + c.head(title, icon: icon, count: count, url: url) + c.list do + c.list_item { "list > list_item" } + end + end + end + + def divided_list(title: "Gems", icon: "gems", count: nil, url: nil) + render CardComponent.new do |c| + c.head(title, icon: icon, count: count) + c.divided_list do + c.list_item { "divided_list > list_item" } + c.list_item_to("#") { "divided_list > list_item_to" } + c.list_item { "divided_list > list_item" } + end + c.list_item_to(url) { "View all" } if url + end + end + + def scrollable(title: "History") + render CardComponent.new do |c| + c.head do + c.title(title, icon: :history) + end + c.scrollable do + render Card::TimelineComponent.new do |t| + t.timeline_item(Time.current) { "timeline_item > content" } + end + render Card::TimelineComponent.new do |t| + t.timeline_item(1.day.ago) { "timeline_item > content" } + end + render Card::TimelineComponent.new do |t| + t.timeline_item(2.days.ago) { "timeline_item > content" } + end + render Card::TimelineComponent.new do |t| + t.timeline_item(1.week.ago) { "timeline_item > content" } + end + render Card::TimelineComponent.new do |t| + t.timeline_item(1.month.ago) { "timeline_item > content" } + end + render Card::TimelineComponent.new do |t| + t.timeline_item(1.year.ago) { "timeline_item > content" } + end + end + end + end +end diff --git a/test/functional/profiles_controller_test.rb b/test/functional/profiles_controller_test.rb index 1cfbb165ec8..0cb3ab5a29d 100644 --- a/test/functional/profiles_controller_test.rb +++ b/test/functional/profiles_controller_test.rb @@ -12,6 +12,19 @@ class ProfilesControllerTest < ActionController::TestCase end end + context "for a user whose email is not confirmed" do + setup do + @user = create(:user) + @user.update(email_confirmed: false) + end + + should "render not found page" do + get :show, params: { id: @user.handle } + + assert_response :not_found + end + end + context "when not logged in" do setup { @user = create(:user) } diff --git a/test/integration/i18n_test.rb b/test/integration/i18n_test.rb index 1212f677ccd..f7a1e80fe4a 100644 --- a/test/integration/i18n_test.rb +++ b/test/integration/i18n_test.rb @@ -30,10 +30,12 @@ def collect_combined_keys(hash, namespace = nil) assert_predicate reference, :present? + suggestion = "\nRun bin/fill-locales to add missing keys." + locale_keys.each do |locale, keys| missing = reference - keys - assert_predicate missing, :blank?, "#{locale} locale is missing: #{missing.join(', ')}" + assert_predicate missing, :blank?, "#{locale} locale is missing: #{missing.join(', ')}#{suggestion}" extra = keys - reference assert_predicate extra, :blank?, "#{locale} locale has extra: #{extra.join(', ')}" diff --git a/test/integration/organizations/gems_test.rb b/test/integration/organizations/gems_test.rb new file mode 100644 index 00000000000..03dbf5857cc --- /dev/null +++ b/test/integration/organizations/gems_test.rb @@ -0,0 +1,25 @@ +require "test_helper" + +class Organizations::GemsTest < ActionDispatch::IntegrationTest + setup do + @user = create(:user, remember_token_expires_at: Gemcutter::REMEMBER_FOR.from_now) + post session_path(session: { who: @user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD }) + end + + test "should get index" do + @organization = create(:organization, owners: [@user]) + @organization.rubygems << create(:rubygem, name: "arrakis", number: "1.0.0") + + get "/organizations/#{@organization.to_param}/gems" + + assert page.has_content? "arrakis" + end + + test "should get index with no gems" do + @organization = create(:organization, owners: [@user]) + + get "/organizations/#{@organization.to_param}/gems" + + assert page.has_content? "No gems yet" + end +end diff --git a/test/integration/organizations_test.rb b/test/integration/organizations_test.rb new file mode 100644 index 00000000000..b4b3e95faf6 --- /dev/null +++ b/test/integration/organizations_test.rb @@ -0,0 +1,62 @@ +require "test_helper" + +class OrganizationsTest < ActionDispatch::IntegrationTest + setup do + @user = create(:user, remember_token_expires_at: Gemcutter::REMEMBER_FOR.from_now) + post session_path(session: { who: @user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD }) + end + + test "should show an organization" do + organization = create(:organization, owners: [@user], handle: "arrakis", name: "Arrakis") + organization.rubygems << create(:rubygem, name: "arrakis", number: "1.0.0") + + get "/organizations/#{organization.to_param}" + + assert_response :success + assert page.has_content? "arrakis" + end + + test "should render not found when an organization doesn't exist" do + get "/organizations/notfound" + + assert_response :not_found + end + + test "should list no organization for a user with none" do + get "/organizations" + + assert_response :success + end + + test "should list organizations for a user" do + organization = create(:organization, owners: [@user]) + + get "/organizations" + + assert_response :success + assert page.has_content? organization.name + end + + test "should render organization edit form" do + organization = create(:organization, owners: [@user]) + + get "/organizations/#{organization.to_param}/edit" + + assert_response :success + assert_select "form[action=?]", organization_path(organization) + assert_select "input[name=?]", "organization[name]" + end + + test "should update an organization display name" do + organization = create(:organization, owners: [@user]) + + patch "/organizations/#{organization.to_param}", params: { + organization: { name: "New Name" } + } + + assert_redirected_to organization_path(organization) + follow_redirect! + + assert page.has_content? "New Name" + end +end diff --git a/test/integration/rubygems_test.rb b/test/integration/rubygems_test.rb index 1fc945eed7e..4ec6796efa7 100644 --- a/test/integration/rubygems_test.rb +++ b/test/integration/rubygems_test.rb @@ -28,4 +28,29 @@ class RubygemsTest < ActionDispatch::IntegrationTest assert page.has_content? "Provenance" end + + test "GET to show for a fully yanked gem as owner" do + user = create(:user, remember_token_expires_at: Gemcutter::REMEMBER_FOR.from_now) + rubygem = create(:rubygem, owners: [user], number: "1.0.0", created_at: 2.months.ago) + version = rubygem.versions.sole + user.deletions.create!(version:) + rubygem.reload + + assert_predicate rubygem.public_versions.to_a, :empty? + + get "/gems/#{rubygem.name}" + + assert page.has_content? "This gem previously existed, but has been removed by its owner." + refute page.has_link? "Owners" + refute page.has_link? "Trusted publishers" + refute page.has_link? "Security Events" + + post session_path(session: { who: user.handle, password: PasswordHelpers::SECURE_TEST_PASSWORD }) + + get "/gems/#{rubygem.name}" + + assert page.has_link? "Owners" + assert page.has_link? "Trusted publishers" + assert page.has_link? "Security Events" + end end diff --git a/test/policies/api/nil_class_policy_test.rb b/test/policies/api/nil_class_policy_test.rb new file mode 100644 index 00000000000..3472f8c9147 --- /dev/null +++ b/test/policies/api/nil_class_policy_test.rb @@ -0,0 +1,21 @@ +require "test_helper" + +class Api::NilClassPolicyTest < ApiPolicyTestCase + def policy!(api_key) + Pundit.policy!(api_key, [:api, nil]) + end + + context "::Scope.resolve" do + should "raise" do + assert_raises Pundit::NotDefinedError do + Api::NilClassPolicy::Scope.new(nil, nil).resolve + end + end + end + + context "#destroy?" do + should "not be authorized" do + refute_authorized policy!(nil), :destroy?, "Forbidden" + end + end +end diff --git a/test/policies/membership_policy_test.rb b/test/policies/membership_policy_test.rb index cc3e3465a3c..39a383f707e 100644 --- a/test/policies/membership_policy_test.rb +++ b/test/policies/membership_policy_test.rb @@ -96,26 +96,24 @@ def policy!(user, record = Membership) context "removing owner" do should "be authorized for org owners only" do membership = create(:membership, :owner, organization: @organization) - membership.role = :admin - assert_authorized policy!(@owner, membership), :update? + assert_authorized policy!(@owner, membership), :destroy? - refute_authorized policy!(@admin, membership), :update? - refute_authorized policy!(@maintainer, membership), :update? - refute_authorized policy!(@guest, membership), :update? + refute_authorized policy!(@admin, membership), :destroy? + refute_authorized policy!(@maintainer, membership), :destroy? + refute_authorized policy!(@guest, membership), :destroy? end end context "removing admin" do should "be authorized for org admins and owners" do membership = create(:membership, :admin, organization: @organization) - membership.role = :maintainer - assert_authorized policy!(@owner, membership), :update? - assert_authorized policy!(@admin, membership), :update? + assert_authorized policy!(@owner, membership), :destroy? + assert_authorized policy!(@admin, membership), :destroy? - refute_authorized policy!(@maintainer, membership), :update? - refute_authorized policy!(@guest, membership), :update? + refute_authorized policy!(@maintainer, membership), :destroy? + refute_authorized policy!(@guest, membership), :destroy? end end end diff --git a/test/policies/rubygem_policy_test.rb b/test/policies/rubygem_policy_test.rb index 4979fa27697..505dc93a4fd 100644 --- a/test/policies/rubygem_policy_test.rb +++ b/test/policies/rubygem_policy_test.rb @@ -33,6 +33,14 @@ def org_policy!(user) Pundit.policy!(user, @org_rubygem) end + context "#create?" do + should "allow users" do + assert_authorized policy!(@owner), :create? + assert_authorized policy!(@user), :create? + refute_authorized policy!(nil), :create? + end + end + context "#configure_oidc?" do should "only allow the owner" do assert_authorized policy!(@owner), :configure_oidc? diff --git a/test/system/profile_test.rb b/test/system/profile_test.rb new file mode 100644 index 00000000000..5afb8195384 --- /dev/null +++ b/test/system/profile_test.rb @@ -0,0 +1,38 @@ +require "application_system_test_case" +require "test_helper" + +class ProfileTest < ApplicationSystemTestCase + setup do + @user = create(:user, email: "nick@example.com", password: PasswordHelpers::SECURE_TEST_PASSWORD, handle: "nick1", mail_fails: 1) + end + + def sign_in + visit sign_in_path + fill_in "Email or Username", with: @user.reload.email + fill_in "Password", with: @user.password + click_button "Sign in" + end + + test "adding X(formerly Twitter) username without filling in your password" do + twitter_username = "nick1twitter" + + sign_in + visit profile_path("nick1") + + click_link "Edit Profile" + fill_in "user_twitter_username", with: twitter_username + + assert_equal twitter_username, page.find_by_id("user_twitter_username").value + + click_button "Update" + + # Verify that the newly added Twitter username is still on the form so that the user does not need to re-enter it + assert_equal twitter_username, page.find_by_id("user_twitter_username").value + + fill_in "Password", with: PasswordHelpers::SECURE_TEST_PASSWORD + click_button "Update" + + assert page.has_content? "Your profile was updated." + assert_equal twitter_username, page.find_by_id("user_twitter_username").value + end +end diff --git a/test/views/card/timeline_component_test.rb b/test/views/card/timeline_component_test.rb new file mode 100644 index 00000000000..8357c0be118 --- /dev/null +++ b/test/views/card/timeline_component_test.rb @@ -0,0 +1,16 @@ +require "test_helper" + +class Card::TimelineComponentTest < ComponentTest + should "render timeline item without link to user" do + datetime = 1.2.days.ago + + render Card::TimelineComponent.new do |c| + c.timeline_item(datetime) do + "additional content" + end + end + + assert_selector "time[datetime='#{datetime.iso8601}']" + assert_text "additional content" + end +end diff --git a/test/views/card_component_test.rb b/test/views/card_component_test.rb new file mode 100644 index 00000000000..a36daccf46f --- /dev/null +++ b/test/views/card_component_test.rb @@ -0,0 +1,44 @@ +require "test_helper" + +class CardComponentTest < ComponentTest + def render(...) + response = super + Capybara.string(response) + end + + should "render a card with title, icon, and list content" do + render CardComponent.new do |c| + c.head("Gems", icon: "gems", count: 3) + c.list do + c.list_item_to("rubygem_version_path(1)") { "RubyGem1 (0.0.1)" } + c.list_item_to("rubygem_version_path(2)") { "RubyGem2 (0.0.2)" } + c.list_item_to("rubygem_version_path(3)") { "RubyGem3 (0.0.3)" } + end + end + + assert_selector "article" + assert_selector "h3", text: "Gems" + assert_selector "svg.fill-orange" + assert_selector "span", text: "3" + assert_link "RubyGem1 (0.0.1)", href: "rubygem_version_path(1)" + assert_link "RubyGem2 (0.0.2)", href: "rubygem_version_path(2)" + assert_link "RubyGem3 (0.0.3)", href: "rubygem_version_path(3)" + refute_text "View all" + end + + should "render a card with custom title and scrollable content" do + render CardComponent.new do |c| + c.head do + c.title("History") + end + c.scrollable do + "content" + end + end + + assert_selector "article" + assert_selector "h3", text: "History" + assert_text "content" + refute_text "View all" + end +end