diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index de098ef7b263..d730336dadf5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -186,6 +186,16 @@ jobs: type=semver,pattern={{version}},value=${{ needs.setup.outputs.version }} images: | ${{ env.REGISTRY_IMAGE }} + - name: Restore vendor/bundle + id: restore-vendor-bundle + uses: actions/cache/restore@v4 + with: + path: | + vendor/bundle + key: ${{ matrix.platform }}-vendor-bundle-${{ github.ref }} + - name: Include vendor/bundle in this build (so we can use it from the cache above) + run: | + sed -i 's/vendor\/bundle//g' .dockerignore - name: Build image id: build uses: docker/build-push-action@v6 @@ -195,12 +205,26 @@ jobs: target: ${{ matrix.target }} build-args: | BIM_SUPPORT=${{ matrix.bim_support }} + BUILDKIT_PROGRESS=plain pull: true load: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=s3,blobs_prefix=cache/${{ github.repository }}/,manifests_prefix=cache/${{ github.repository }}/,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }} cache-to: type=s3,blobs_prefix=cache/${{ github.repository }}/,manifests_prefix=cache/${{ github.repository }}/,region=${{ env.RUNS_ON_AWS_REGION }},bucket=${{ env.RUNS_ON_S3_BUCKET_CACHE }},mode=max + - name: Extract vendor/bundle from container + run: | + docker create --name bundle ${{ steps.build.outputs.imageid }} + rm -rf vendor/bundle || true + docker cp bundle:/app/vendor/bundle vendor/bundle + docker rm bundle + - name: Save vendor/bundle + id: save-vendor-bundle + uses: actions/cache/save@v4 + with: + path: | + vendor/bundle + key: ${{ steps.restore-vendor-bundle.outputs.cache-primary-key }} - name: Test # We only test the native container. If that fails the builds for the others # will be cancelled as well. diff --git a/.gitignore b/.gitignore index 0a27bf8e565e..a677a999f576 100644 --- a/.gitignore +++ b/.gitignore @@ -134,6 +134,8 @@ structure.sql lefthook-local.yml .rubocop-local.yml +/.lefthook-local/ + frontend/package-lock.json # Testing and nextcloud infrastructure diff --git a/Gemfile b/Gemfile index 366d19ea5233..b604d3d3c9f8 100644 --- a/Gemfile +++ b/Gemfile @@ -36,7 +36,7 @@ ruby File.read(File.expand_path(".ruby-version", __dir__)).strip gem "actionpack-xml_parser", "~> 2.0.0" gem "activemodel-serializers-xml", "~> 1.0.1" -gem "activerecord-import", "~> 1.8.0" +gem "activerecord-import", "~> 2.0.0" gem "activerecord-session_store", "~> 2.1.0" gem "ox" gem "rails", "~> 7.1.3" @@ -61,7 +61,7 @@ gem "friendly_id", "~> 5.5.0" gem "acts_as_list", "~> 1.2.0" gem "acts_as_tree", "~> 2.9.0" -gem "awesome_nested_set", "~> 3.7.0" +gem "awesome_nested_set", "~> 3.8.0" gem "closure_tree", "~> 7.4.0" gem "rubytree", "~> 2.1.0" # Only used in down migrations now. @@ -140,7 +140,7 @@ gem "rack-attack", "~> 6.7.0" gem "secure_headers", "~> 7.0.0" # Browser detection for incompatibility checks -gem "browser", "~> 6.0.0" +gem "browser", "~> 6.2.0" # Providing health checks gem "okcomputer", "~> 1.18.1" @@ -171,6 +171,9 @@ gem "paper_trail", "~> 15.2.0" gem "op-clamav-client", "~> 3.4", require: "clamav" +# Recurring meeting events definition +gem "ice_cube", "~> 0.17.0" + group :production do # we use dalli as standard memcache client # requires memcached 1.4+ @@ -188,7 +191,7 @@ gem "puma", "~> 6.5" gem "puma-plugin-statsd", "~> 2.0" gem "rack-timeout", "~> 0.7.0", require: "rack/timeout/base" -gem "nokogiri", "~> 1.16.0" +gem "nokogiri", "~> 1.17.0" gem "carrierwave", "~> 1.3.4" gem "carrierwave_direct", "~> 2.1.0" @@ -229,7 +232,7 @@ gem "factory_bot", "~> 6.5.0", require: false # require factory_bot_rails for convenience in core development gem "factory_bot_rails", "~> 6.4.4", require: false -gem "turbo_power", "~> 0.6.2" +gem "turbo_power", "~> 0.7.0" gem "turbo-rails", "~> 2.0.0" gem "httpx" @@ -269,7 +272,7 @@ group :test do gem "rails-controller-testing", "~> 1.0.2" gem "capybara", "~> 3.40.0" - gem "capybara_accessible_selectors", git: "https://github.com/citizensadvice/capybara_accessible_selectors", branch: "main" + gem "capybara_accessible_selectors", git: "https://github.com/citizensadvice/capybara_accessible_selectors", tag: "v0.12.0" gem "capybara-screenshot", "~> 1.0.17" gem "cuprite", "~> 0.15.0" gem "rspec-wait" @@ -379,7 +382,7 @@ platforms :mri, :mingw, :x64_mingw do end # Support application loading when no database exists yet. - gem "activerecord-nulldb-adapter", "~> 1.0.0" + gem "activerecord-nulldb-adapter", "~> 1.1.0" # Have application level locks on the database to have a mutex shared between workers/hosts. # We e.g. employ this to safeguard the creation of journals. @@ -398,6 +401,6 @@ gemfiles.each do |file| send(:eval_gemfile, file) if File.readable?(file) end -gem "openproject-octicons", "~>19.19.0" -gem "openproject-octicons_helper", "~>19.19.0" -gem "openproject-primer_view_components", "~>0.49.2" +gem "openproject-octicons", "~>19.20.0 " +gem "openproject-octicons_helper", "~>19.20.0 " +gem "openproject-primer_view_components", "~>0.52.0" diff --git a/Gemfile.lock b/Gemfile.lock index e42bda9e0386..2ff8ccffa25d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/citizensadvice/capybara_accessible_selectors - revision: aed2860e5b5df7f39284fdc35a5bf57bda7e1ad9 - branch: main + revision: 347bbe06cb420416855e80bb4e3a3016b2d5872c + tag: v0.12.0 specs: capybara_accessible_selectors (0.11.0) capybara (~> 3.36) @@ -206,7 +206,7 @@ PATH remote: modules/two_factor_authentication specs: openproject-two_factor_authentication (1.0.0) - aws-sdk-sns (~> 1.90.0) + aws-sdk-sns (~> 1.92.0) messagebird-rest (~> 1.4.2) rotp (~> 6.1) webauthn (~> 3.0) @@ -226,35 +226,35 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (2.0.1) - actioncable (7.1.5) - actionpack (= 7.1.5) - activesupport (= 7.1.5) + actioncable (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.5) - actionpack (= 7.1.5) - activejob (= 7.1.5) - activerecord (= 7.1.5) - activestorage (= 7.1.5) - activesupport (= 7.1.5) + actionmailbox (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.5) - actionpack (= 7.1.5) - actionview (= 7.1.5) - activejob (= 7.1.5) - activesupport (= 7.1.5) + actionmailer (7.1.5.1) + actionpack (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.5) - actionview (= 7.1.5) - activesupport (= 7.1.5) + actionpack (7.1.5.1) + actionview (= 7.1.5.1) + activesupport (= 7.1.5.1) nokogiri (>= 1.8.5) racc rack (>= 2.2.4) @@ -265,36 +265,36 @@ GEM actionpack-xml_parser (2.0.1) actionpack (>= 5.0) railties (>= 5.0) - actiontext (7.1.5) - actionpack (= 7.1.5) - activerecord (= 7.1.5) - activestorage (= 7.1.5) - activesupport (= 7.1.5) + actiontext (7.1.5.1) + actionpack (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.5) - activesupport (= 7.1.5) + actionview (7.1.5.1) + activesupport (= 7.1.5.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.1.5) - activesupport (= 7.1.5) + activejob (7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.3.6) - activemodel (7.1.5) - activesupport (= 7.1.5) + activemodel (7.1.5.1) + activesupport (= 7.1.5.1) activemodel-serializers-xml (1.0.3) activemodel (>= 5.0.0.a) activesupport (>= 5.0.0.a) builder (~> 3.1) - activerecord (7.1.5) - activemodel (= 7.1.5) - activesupport (= 7.1.5) + activerecord (7.1.5.1) + activemodel (= 7.1.5.1) + activesupport (= 7.1.5.1) timeout (>= 0.4.0) - activerecord-import (1.8.1) + activerecord-import (2.0.0) activerecord (>= 4.2) - activerecord-nulldb-adapter (1.0.1) - activerecord (>= 5.2.0, < 7.2) + activerecord-nulldb-adapter (1.1.1) + activerecord (>= 6.0, < 8.1) activerecord-session_store (2.1.0) actionpack (>= 6.1) activerecord (>= 6.1) @@ -302,13 +302,13 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 2.0.8, < 4) railties (>= 6.1) - activestorage (7.1.5) - actionpack (= 7.1.5) - activejob (= 7.1.5) - activerecord (= 7.1.5) - activesupport (= 7.1.5) + activestorage (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activesupport (= 7.1.5.1) marcel (~> 1.0) - activesupport (7.1.5) + activesupport (7.1.5.1) base64 benchmark (>= 0.3) bigdecimal @@ -330,7 +330,7 @@ GEM public_suffix (>= 2.0.2, < 7.0) aes_key_wrap (1.1.0) afm (0.2.2) - airbrake (13.0.4) + airbrake (13.0.5) airbrake-ruby (~> 6.0) airbrake-ruby (6.2.2) rbtree3 (~> 0.6) @@ -341,10 +341,10 @@ GEM attr_required (1.0.2) auto_strip_attributes (2.6.0) activerecord (>= 4.0) - awesome_nested_set (3.7.0) - activerecord (>= 4.0.0, < 8.0) + awesome_nested_set (3.8.0) + activerecord (>= 4.0.0, < 8.1) aws-eventstream (1.3.0) - aws-partitions (1.1013.0) + aws-partitions (1.1023.0) aws-sdk-core (3.214.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -353,11 +353,11 @@ GEM aws-sdk-kms (1.96.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.174.0) + aws-sdk-s3 (1.176.1) aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sdk-sns (1.90.0) + aws-sdk-sns (1.92.0) aws-sdk-core (~> 3, >= 3.210.0) aws-sigv4 (~> 1.5) aws-sigv4 (1.10.1) @@ -391,7 +391,7 @@ GEM msgpack (~> 1.2) brakeman (6.2.2) racc - browser (6.0.0) + browser (6.2.0) builder (3.3.0) byebug (11.1.3) capybara (3.40.0) @@ -441,15 +441,15 @@ GEM bigdecimal rexml crass (1.0.6) - css_parser (1.19.1) + css_parser (1.21.0) addressable - csv (3.3.0) + csv (3.3.1) cuprite (0.15.1) capybara (~> 3.0) ferrum (~> 0.15.0) daemons (1.4.1) dalli (3.2.8) - date (3.4.0) + date (3.4.1) date_validator (0.12.0) activemodel (>= 3) activesupport (>= 3) @@ -465,11 +465,11 @@ GEM disposable (0.6.3) declarative (>= 0.0.9, < 1.0.0) representable (>= 3.1.1, < 4) - doorkeeper (5.8.0) + doorkeeper (5.8.1) railties (>= 5) - dotenv (3.1.4) - dotenv-rails (3.1.4) - dotenv (= 3.1.4) + dotenv (3.1.7) + dotenv-rails (3.1.7) + dotenv (= 3.1.7) railties (>= 6.1) drb (2.2.1) dry-auto_inject (1.0.1) @@ -545,20 +545,20 @@ GEM tzinfo eventmachine (1.2.7) eventmachine_httpserver (0.2.1) - excon (1.2.0) + excon (1.2.2) factory_bot (6.5.0) activesupport (>= 5.0.0) factory_bot_rails (6.4.4) factory_bot (~> 6.5) railties (>= 5.0.0) - faraday (2.12.0) - faraday-net_http (>= 2.0, < 3.4) + faraday (2.12.2) + faraday-net_http (>= 2.0, < 3.5) json logger faraday-follow_redirects (0.3.0) faraday (>= 1, < 3) - faraday-net_http (3.3.0) - net-http + faraday-net_http (3.4.0) + net-http (>= 0.5.0) fastimage (2.3.1) ferrum (0.15) addressable (~> 2.5) @@ -567,7 +567,7 @@ GEM websocket-driver (~> 0.7) ffi (1.17.0) flamegraph (0.9.5) - fog-aws (3.29.0) + fog-aws (3.30.0) base64 (~> 0.2.0) fog-core (~> 2.6) fog-json (~> 1.1) @@ -620,9 +620,11 @@ GEM google-apis-core (>= 0.15.0, < 2.a) google-cloud-env (2.2.1) faraday (>= 1.0, < 3.a) - googleauth (1.11.2) + google-logging-utils (0.1.0) + googleauth (1.12.1) faraday (>= 1.0, < 3.a) - google-cloud-env (~> 2.1) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) jwt (>= 1.4, < 3.0) multi_json (~> 1.11) os (>= 0.9, < 2.0) @@ -652,7 +654,7 @@ GEM http-2 (1.0.2) http_parser.rb (0.6.0) httpclient (2.8.3) - httpx (1.3.3) + httpx (1.4.0) http-2 (>= 1.0.0) i18n (1.14.6) concurrent-ruby (~> 1.0) @@ -675,13 +677,13 @@ GEM ice_cube (0.17.0) ice_nine (0.11.2) interception (0.5) - io-console (0.7.2) - irb (1.14.1) + io-console (0.8.0) + irb (1.14.2) rdoc (>= 4.0.0) reline (>= 0.4.2) iso8601 (0.13.0) jmespath (1.6.2) - json (2.8.2) + json (2.9.1) json-jwt (1.16.7) activesupport (>= 4.2) aes_key_wrap @@ -707,7 +709,7 @@ GEM launchy (3.0.1) addressable (~> 2.8) childprocess (~> 5.0) - lefthook (1.8.4) + lefthook (1.10.0) letter_opener (1.10.0) launchy (>= 2.2, < 4) letter_opener_web (3.0.0) @@ -722,7 +724,7 @@ GEM omniauth (~> 1.1) omniauth-openid-connect (>= 0.2.1) rails (>= 3.2.21) - logger (1.6.1) + logger (1.6.4) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -758,11 +760,11 @@ GEM mime-types (3.6.0) logger mime-types-data (~> 3.2015) - mime-types-data (3.2024.1105) + mime-types-data (3.2024.1203) mini_magick (5.0.1) mini_mime (1.1.5) mini_portile2 (2.8.8) - minitest (5.25.1) + minitest (5.25.4) msgpack (1.7.5) multi_json (1.15.0) mustermann (3.0.3) @@ -770,7 +772,7 @@ GEM mustermann-grape (1.1.0) mustermann (>= 1.0.0) mutex_m (0.3.0) - net-http (0.4.1) + net-http (0.6.0) uri net-imap (0.5.1) date @@ -783,10 +785,10 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.4) - nokogiri (1.16.7) + nokogiri (1.17.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) - oj (3.16.7) + oj (3.16.8) bigdecimal (>= 3.0) ostruct (>= 0.2) okcomputer (1.18.5) @@ -807,15 +809,15 @@ GEM validate_email validate_url webfinger (~> 2.0) - openproject-octicons (19.19.0) - openproject-octicons_helper (19.19.0) + openproject-octicons (19.20.0) + openproject-octicons_helper (19.20.0) actionview - openproject-octicons (= 19.19.0) + openproject-octicons (= 19.20.0) railties - openproject-primer_view_components (0.49.2) + openproject-primer_view_components (0.52.0) actionview (>= 5.0.0) activesupport (>= 5.0.0) - openproject-octicons (>= 19.17.0) + openproject-octicons (>= 19.20.0) view_component (>= 3.1, < 4.0) openproject-token (4.0.0) activemodel @@ -868,7 +870,8 @@ GEM pry-rescue (1.6.0) interception (>= 0.5) pry (>= 0.12.0) - psych (5.2.0) + psych (5.2.2) + date stringio public_suffix (6.0.1) puffing-billy (4.0.0) @@ -913,20 +916,20 @@ GEM rackup (1.0.1) rack (< 3) webrick - rails (7.1.5) - actioncable (= 7.1.5) - actionmailbox (= 7.1.5) - actionmailer (= 7.1.5) - actionpack (= 7.1.5) - actiontext (= 7.1.5) - actionview (= 7.1.5) - activejob (= 7.1.5) - activemodel (= 7.1.5) - activerecord (= 7.1.5) - activestorage (= 7.1.5) - activesupport (= 7.1.5) + rails (7.1.5.1) + actioncable (= 7.1.5.1) + actionmailbox (= 7.1.5.1) + actionmailer (= 7.1.5.1) + actionpack (= 7.1.5.1) + actiontext (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activemodel (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) bundler (>= 1.15.0) - railties (= 7.1.5) + railties (= 7.1.5.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -935,15 +938,15 @@ GEM 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) rails-i18n (7.0.10) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.1.5) - actionpack (= 7.1.5) - activesupport (= 7.1.5) + railties (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) irb rackup (>= 1.0.0) rake (>= 12.2) @@ -954,22 +957,22 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rb_sys (0.9.102) + rb_sys (0.9.103) rbtrace (0.5.1) ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) rbtree3 (0.7.1) - rdoc (6.8.1) + rdoc (6.9.1) psych (>= 4.0.0) - recaptcha (5.17.0) + recaptcha (5.18.0) redcarpet (3.6.0) redis (5.3.0) redis-client (>= 0.22.0) - redis-client (0.22.2) + redis-client (0.23.0) connection_pool - regexp_parser (2.9.2) - reline (0.5.11) + regexp_parser (2.9.3) + reline (0.6.0) io-console (~> 0.5) representable (3.2.0) declarative (< 0.1.0) @@ -981,7 +984,7 @@ GEM actionpack (>= 5.2) railties (>= 5.2) retriable (3.1.2) - rexml (3.3.9) + rexml (3.4.0) rinku (2.0.6) roar (1.2.0) representable (~> 3.1) @@ -1009,20 +1012,20 @@ GEM rspec-support (~> 3.13) rspec-retry (0.6.2) rspec-core (> 3.3) - rspec-support (3.13.1) + rspec-support (3.13.2) rspec-wait (1.0.1) rspec (>= 3.4) - rubocop (1.68.0) + rubocop (1.69.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.4, < 3.0) - rubocop-ast (>= 1.32.2, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.36.1) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.37.0) parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) @@ -1038,7 +1041,7 @@ GEM rack (>= 1.1) rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (3.2.0) + rubocop-rspec (3.3.0) rubocop (~> 1.61) rubocop-rspec_rails (2.30.0) rubocop (~> 1.61) @@ -1055,8 +1058,8 @@ GEM nokogiri (>= 1.13.10) rexml ruby2_keywords (0.0.5) - rubytree (2.1.0) - json (~> 2.0, > 2.3.1) + rubytree (2.1.1) + json (~> 2.0, > 2.9) rubyzip (2.3.2) safety_net_attestation (0.4.0) jwt (~> 2.0) @@ -1064,10 +1067,10 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.12.0) secure_headers (7.0.0) - securerandom (0.3.2) - selenium-devtools (0.130.0) + securerandom (0.4.1) + selenium-devtools (0.131.0) selenium-webdriver (~> 4.2) - selenium-webdriver (4.26.0) + selenium-webdriver (4.27.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -1118,12 +1121,12 @@ GEM table_print (1.5.7) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) - test-prof (1.4.2) + test-prof (1.4.3) text-hyphen (1.5.0) thor (1.3.2) thread_safe (0.3.6) timecop (0.9.10) - timeout (0.4.2) + timeout (0.4.3) tpm-key_attestation (0.12.1) bindata (~> 2.4) openssl (> 2.0) @@ -1133,7 +1136,7 @@ GEM turbo-rails (2.0.11) actionpack (>= 6.0.0) railties (>= 6.0.0) - turbo_power (0.6.2) + turbo_power (0.7.0) turbo-rails (>= 1.3.0) typed_dag (2.0.2) rails (>= 5.0.4) @@ -1143,7 +1146,7 @@ GEM tzinfo (>= 1.0.0) uber (0.1.0) unicode-display_width (2.6.0) - uri (0.13.1) + uri (1.0.2) validate_email (0.1.6) activemodel (>= 3.0) mail (>= 2.2.5) @@ -1152,7 +1155,7 @@ GEM public_suffix vcr (6.3.1) base64 - vernier (1.4.0) + vernier (1.5.0) view_component (3.20.0) activesupport (>= 5.2.0, < 8.1) concurrent-ruby (~> 1.0) @@ -1181,7 +1184,7 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.9.0) + webrick (1.9.1) websocket (1.2.11) websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) @@ -1201,8 +1204,8 @@ PLATFORMS DEPENDENCIES actionpack-xml_parser (~> 2.0.0) activemodel-serializers-xml (~> 1.0.1) - activerecord-import (~> 1.8.0) - activerecord-nulldb-adapter (~> 1.0.0) + activerecord-import (~> 2.0.0) + activerecord-nulldb-adapter (~> 1.1.0) activerecord-session_store (~> 2.1.0) acts_as_list (~> 1.2.0) acts_as_tree (~> 2.9.0) @@ -1210,14 +1213,14 @@ DEPENDENCIES airbrake (~> 13.0.0) appsignal (~> 3.10.0) auto_strip_attributes (~> 2.5) - awesome_nested_set (~> 3.7.0) + awesome_nested_set (~> 3.8.0) aws-sdk-core (~> 3.107) aws-sdk-s3 (~> 1.91) axe-core-rspec bcrypt (~> 3.1.6) bootsnap (~> 1.18.0) brakeman (~> 6.2.0) - browser (~> 6.0.0) + browser (~> 6.2.0) budgets! capybara (~> 3.40.0) capybara-screenshot (~> 1.0.17) @@ -1269,6 +1272,7 @@ DEPENDENCIES httpx i18n-js (~> 4.2.3) i18n-tasks (~> 1.0.13) + ice_cube (~> 0.17.0) json_schemer (~> 2.3.0) json_spec (~> 1.1.4) ladle @@ -1287,7 +1291,7 @@ DEPENDENCIES multi_json (~> 1.15.0) my_page! net-ldap (~> 0.19.0) - nokogiri (~> 1.16.0) + nokogiri (~> 1.17.0) oj (~> 3.16.0) okcomputer (~> 1.18.1) omniauth! @@ -1309,10 +1313,10 @@ DEPENDENCIES openproject-job_status! openproject-ldap_groups! openproject-meeting! - openproject-octicons (~> 19.19.0) - openproject-octicons_helper (~> 19.19.0) + openproject-octicons (~> 19.20.0) + openproject-octicons_helper (~> 19.20.0) openproject-openid_connect! - openproject-primer_view_components (~> 0.49.2) + openproject-primer_view_components (~> 0.52.0) openproject-recaptcha! openproject-reporting! openproject-storages! @@ -1394,7 +1398,7 @@ DEPENDENCIES timecop (~> 0.9.0) ttfunk (~> 1.7.0) turbo-rails (~> 2.0.0) - turbo_power (~> 0.6.2) + turbo_power (~> 0.7.0) turbo_tests! typed_dag (~> 2.0.2) tzinfo-data (~> 1.2024.1) diff --git a/README.md b/README.md index 33d6cc10ae2d..c64ea5a710be 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ You found a bug? Please [report it](https://www.openproject.org/docs/development OpenProject is supported by its Community members, both companies and individuals. -We are always looking for new members to our Community, so if you are interested in improving OpenProject we would be glad to welcome and support you getting into the code. There are guides as well, e.g. a [Quick Start for Developers](https://www.openproject.org/development/setting-up-development-environment/), but don't hesitate to simply [contact us](https://www.openproject.org/contact) if you have questions. +We are always looking for new members to our Community, so if you are interested in improving OpenProject we would be glad to welcome and support you getting into the code. There are guides as well, e.g. a [Quick Start for Developers](https://www.openproject.org/docs/development/development-environment/), but don't hesitate to simply [contact us](https://www.openproject.org/contact) if you have questions. Working on OpenProject comes with the satisfaction of working on a widely used open source application. diff --git a/app/assets/images/lookbook/hover_card.png b/app/assets/images/lookbook/hover_card.png index 8dde8feb902b..f2389dd7ded0 100644 Binary files a/app/assets/images/lookbook/hover_card.png and b/app/assets/images/lookbook/hover_card.png differ diff --git a/app/assets/images/lookbook/user_hover_card.png b/app/assets/images/lookbook/user_hover_card.png new file mode 100644 index 000000000000..b225bcfad038 Binary files /dev/null and b/app/assets/images/lookbook/user_hover_card.png differ diff --git a/app/components/_index.sass b/app/components/_index.sass index 45a89e33bc3f..59f6c3cdfa43 100644 --- a/app/components/_index.sass +++ b/app/components/_index.sass @@ -6,6 +6,7 @@ @import "work_packages/activities_tab/journals/item_component/add_reactions" @import "work_packages/activities_tab/journals/item_component/reactions" @import "shares/modal_body_component" +@import "work_packages/reminder/modal_body_component" @import "shares/invite_user_form_component" @import "work_packages/details/tab_component" @import "work_packages/progress/modal_body_component" @@ -18,3 +19,5 @@ @import "op_primer/border_box_table_component" @import "work_packages/exports/modal_dialog_component" @import "work_package_relations_tab/index_component" +@import "users/hover_card_component" +@import "enterprise_edition/banner_component" diff --git a/app/components/concerns/op_turbo/streamable.rb b/app/components/concerns/op_turbo/streamable.rb index dfad026a7825..89180a56ddc2 100644 --- a/app/components/concerns/op_turbo/streamable.rb +++ b/app/components/concerns/op_turbo/streamable.rb @@ -33,6 +33,8 @@ class MissingComponentWrapper < StandardError; end # rubocop:enable OpenProject/AddPreviewForViewComponent INLINE_ACTIONS = %i[dialog flash].freeze + # Turbo allows the response method for these actions only: + ACTIONS_WITH_METHOD = %i[update replace].freeze extend ActiveSupport::Concern @@ -43,7 +45,7 @@ def wrapper_key end included do - def render_as_turbo_stream(view_context:, action: :update) + def render_as_turbo_stream(view_context:, action: :update, method: nil) case action when :update, *INLINE_ACTIONS @inner_html_only = true @@ -63,8 +65,13 @@ def render_as_turbo_stream(view_context:, action: :update) "Wrap your component in a `component_wrapper` block in order to use turbo-stream methods" end + if method && !action.in?(ACTIONS_WITH_METHOD) + raise ArgumentError, "The #{action} action does not supports a method" + end + OpTurbo::StreamComponent.new( action:, + method:, target: wrapper_key, template: ).render_in(view_context) diff --git a/app/components/enterprise_edition/banner_component.html.erb b/app/components/enterprise_edition/banner_component.html.erb new file mode 100644 index 000000000000..5161729b5d58 --- /dev/null +++ b/app/components/enterprise_edition/banner_component.html.erb @@ -0,0 +1,19 @@ +<%= + grid_layout("op-ee-banner", **@system_arguments) do |grid| + grid.with_area(:'icon-container') do + content_tag :div, class: "op-ee-banner--shield" do + render(Primer::Beta::Octicon.new(icon: 'op-enterprise-addons', + size: :medium, + classes: "op-ee-banner--icon")) + end + end + grid.with_area(:'title-container') { render(Primer::Beta::Text.new) { title } } + grid.with_area(:'description-container') { render(Primer::Beta::Text.new) { description } } + grid.with_area(:'link-container') do + render(Primer::Beta::Link.new(href: href)) do |link| + link.with_trailing_visual_icon(icon: 'link-external') + link_title + end + end + end +%> diff --git a/app/components/enterprise_edition/banner_component.rb b/app/components/enterprise_edition/banner_component.rb new file mode 100644 index 000000000000..f7ac7ed39bab --- /dev/null +++ b/app/components/enterprise_edition/banner_component.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module EnterpriseEdition + # Add a general description of component here + # Add additional usage considerations or best practices that may aid the user to use the component correctly. + # @accessibility Add any accessibility considerations + class BannerComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + # @param feature_key [Symbol, NilClass] The key of the feature to show the banner for. + # @param title [String] The title of the banner. + # @param description [String] The description of the banner. + # @param href [String] The URL to link to. + # @param skip_render [Boolean] Whether to skip rendering the banner. + # @param system_arguments [Hash] <%= link_to_system_arguments_docs %> + def initialize(feature_key, + title: nil, + description: nil, + link_title: nil, + href: nil, + skip_render: !EnterpriseToken.show_banners?, + **system_arguments) + @system_arguments = system_arguments + @system_arguments[:tag] = "div" + super + + @feature_key = feature_key + @title = title + @description = description + @link_title = link_title + @href = href + @skip_render = skip_render + end + + private + + attr_reader :skip_render, + :feature_key + + def title + @title || I18n.t("ee.upsale.#{feature_key}.title", default: I18n.t("ee.upsale.title")) + end + + def description + @description || begin + I18n.t("ee.upsale.#{feature_key}.description") + rescue StandardError + I18n.t("ee.upsale.#{feature_key}.description_html") + end + rescue I18n::MissingTranslationData => e + raise e.exception( + <<~TEXT.squish + The expected '#{I18n.locale}.ee.upsale.#{feature_key}.description' key does not exist. + Ideally, provide it in the locale file. + If that isn't applicable, a description parameter needs to be provided. + TEXT + ) + end + + def link_title + @link_title || I18n.t("ee.upsale.#{feature_key}.link_title", default: I18n.t("ee.upsale.link_title")) + end + + def href + href_value = @href || OpenProject::Static::Links.links.dig(:enterprise_docs, feature_key, :href) + + unless href_value + raise "Neither a custom href is provided nor is a value set " \ + "in OpenProject::Static::Links.enterprise_docs[#{feature_key}][:href]" + end + + href_value + end + + def render? + !skip_render + end + end +end diff --git a/app/components/enterprise_edition/banner_component.sass b/app/components/enterprise_edition/banner_component.sass new file mode 100644 index 000000000000..d8ca7bc2f193 --- /dev/null +++ b/app/components/enterprise_edition/banner_component.sass @@ -0,0 +1,68 @@ +/*! + / -- copyright + / OpenProject is an open source project management software. + / Copyright (C) 2024 the OpenProject GmbH + / + / This program is free software; you can redistribute it and/or + / modify it under the terms of the GNU General Public License version 3. + / + / OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: + / Copyright (C) 2006-2013 Jean-Philippe Lang + / Copyright (C) 2010-2013 the ChiliProject Team + / + / This program is free software; you can redistribute it and/or + / modify it under the terms of the GNU General Public License + / as published by the Free Software Foundation; either version 2 + / of the License, or (at your option) any later version. + / + / This program is distributed in the hope that it will be useful, + / but WITHOUT ANY WARRANTY; without even the implied warranty of + / MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + / GNU General Public License for more details. + / + / You should have received a copy of the GNU General Public License + / along with this program; if not, write to the Free Software + / Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + / + / See COPYRIGHT and LICENSE files for more details. + / ++ + / + +$op-ee-banner--shield-width: 32px + +// This is not named op-enterprise-banner because as of now, there is still a legacy angular component that uses that block name. +.op-ee-banner + display: grid + grid-template-columns: $op-ee-banner--shield-width auto auto + grid-template-areas: "icon-container title-container" "icon-container description-container" "icon-container link-container" + grid-column-gap: 0.5rem + justify-content: left + @media screen and (min-width: $breakpoint-md) + grid-template-areas: "icon-container title-container title-container" "icon-container description-container link-container" + + &--icon-container + @extend .upsale-colored + align-self: start + justify-self: center + + &--shield + @extend .upsale-border-colored + width: $op-ee-banner--shield-width + height: 42px + border-width: 10px 5px 10px 5px + border-radius: 0 0 10px 10px + border-style: solid + display: flex + align-items: center + justify-content: center + + &--icon + width: $op-ee-banner--shield-width + height: $op-ee-banner--shield-width + + &--title-container + @extend .upsale-colored + font-weight: bold + + &--link-container + align-self: end diff --git a/app/components/messages/show_page_header_component.html.erb b/app/components/messages/show_page_header_component.html.erb new file mode 100644 index 000000000000..4cec4fedc5d1 --- /dev/null +++ b/app/components/messages/show_page_header_component.html.erb @@ -0,0 +1,56 @@ +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { @topic.subject } + header.with_breadcrumbs(breadcrumb_items) + + watcher_action_button(header, @topic) + + if !@topic.locked? && authorize_for('messages', 'reply') + header.with_action_button(tag: :a, + scheme: :default, + mobile_icon: :quote, + mobile_label: t(:button_quote), + size: :medium, + href: url_for({ action: 'quote', id: @topic }), + aria: { label: I18n.t(:button_delete) }, + data: { 'action': 'forum-messages#quote', test_selector: "message-quote-button" }, + title: t(:button_quote)) do |button| + button.with_leading_visual_icon(icon: :quote) + t(:button_quote) + end + end + + if @message.editable_by?(User.current) + header.with_action_button(tag: :a, + scheme: :default, + mobile_icon: :pencil, + mobile_label: t(:button_edit), + size: :medium, + href: edit_topic_path(@topic), + aria: { label: t(:button_edit) }, + data: { test_selector: "message-edit-button" }, + title: t(:button_edit)) do |button| + button.with_leading_visual_icon(icon: :pencil) + t(:button_edit) + end + end + + if @message.destroyable_by?(User.current) + header.with_action_button(tag: :a, + scheme: :danger, + mobile_icon: :trash, + mobile_label: t(:button_delete), + size: :medium, + href: topic_path(@topic), + aria: { label: I18n.t(:button_delete) }, + data: { + confirm: I18n.t(:text_are_you_sure), + method: :delete + }, + title: I18n.t(:button_delete)) do |button| + button.with_leading_visual_icon(icon: :trash) + t(:button_delete) + end + end + end +%> diff --git a/app/components/messages/show_page_header_component.rb b/app/components/messages/show_page_header_component.rb new file mode 100644 index 000000000000..23a606034599 --- /dev/null +++ b/app/components/messages/show_page_header_component.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Messages + class ShowPageHeaderComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + include ApplicationHelper + include WatchersHelper + + def initialize(topic:, message:, forum:, project:) + super + @topic = topic + @message = message + @forum = forum + @project = project + end + + def breadcrumb_items + [ + { href: project_overview_path(@project.id), text: @project.name }, + { href: project_forums_path(@project), text: t(:label_forum_plural) }, + { href: project_forum_path(@project, @forum), text: @forum.name }, + @topic.subject + ] + end + end +end diff --git a/app/components/my/access_token/access_token_created_dialog_component.html.erb b/app/components/my/access_token/access_token_created_dialog_component.html.erb index cd773f77eda9..395e20e6b180 100644 --- a/app/components/my/access_token/access_token_created_dialog_component.html.erb +++ b/app/components/my/access_token/access_token_created_dialog_component.html.erb @@ -37,7 +37,7 @@ See COPYRIGHT and LICENSE files for more details. message.with_heading(tag: :h2) { I18n.t("my.access_token.create_dialog.header", type: "API") } end - dialog.with_additional_content do + dialog.with_additional_details do flex_layout do |flex| flex.with_row(mb: 2) do render(Primer::OpenProject::InputGroup.new) do |input_group| diff --git a/app/components/op_primer/border_box_table_component.html.erb b/app/components/op_primer/border_box_table_component.html.erb index 4c96232aa95f..a026da0f9aa1 100644 --- a/app/components/op_primer/border_box_table_component.html.erb +++ b/app/components/op_primer/border_box_table_component.html.erb @@ -58,6 +58,12 @@ See COPYRIGHT and LICENSE files for more details. end end end + + if has_footer? + component.with_footer(classes: grid_class, color: :muted) do + footer + end + end end %> diff --git a/app/components/op_primer/border_box_table_component.rb b/app/components/op_primer/border_box_table_component.rb index b005fe4be6d0..1e1160790966 100644 --- a/app/components/op_primer/border_box_table_component.rb +++ b/app/components/op_primer/border_box_table_component.rb @@ -43,7 +43,7 @@ class << self # # This results in the description columns to be hidden on mobile def mobile_columns(*names) - return @mobile_columns || columns if names.empty? + return Array(@mobile_columns || columns) if names.empty? @mobile_columns = names.map(&:to_sym) end @@ -54,7 +54,7 @@ def mobile_columns(*names) # # This results in the description columns to be hidden on mobile def mobile_labels(*names) - return @mobile_labels if names.empty? + return Array(@mobile_labels) if names.empty? @mobile_labels = names.map(&:to_sym) end @@ -106,6 +106,10 @@ def has_actions? false end + def has_footer? + false + end + def sortable? false end @@ -133,5 +137,9 @@ def blank_description def blank_icon nil end + + def footer + raise ArgumentError, "Need to provide footer content" + end end end diff --git a/app/components/op_primer/flash_component.erb b/app/components/op_primer/flash_component.html.erb similarity index 100% rename from app/components/op_primer/flash_component.erb rename to app/components/op_primer/flash_component.html.erb diff --git a/app/components/op_primer/flash_component.rb b/app/components/op_primer/flash_component.rb index fed84bf511f7..15a445c03316 100644 --- a/app/components/op_primer/flash_component.rb +++ b/app/components/op_primer/flash_component.rb @@ -38,6 +38,8 @@ def initialize(**system_arguments) system_arguments[:test_selector] ||= "op-primer-flash-message" system_arguments[:dismiss_scheme] ||= :remove system_arguments[:dismiss_label] ||= I18n.t(:button_close) + system_arguments[:data] ||= {} + system_arguments[:data]["flash-target"] = "flash" @autohide = system_arguments[:scheme] == :success && system_arguments[:dismiss_scheme] != :none diff --git a/app/components/projects/life_cycle_type_component.html.erb b/app/components/projects/life_cycle_type_component.html.erb new file mode 100644 index 000000000000..921c1cf191a2 --- /dev/null +++ b/app/components/projects/life_cycle_type_component.html.erb @@ -0,0 +1,8 @@ +<%= flex_layout(align_items: :center) do |type_container| + type_container.with_column(mr: 1, classes: icon_color_class) do + render Primer::Beta::Octicon.new(icon: icon) + end + type_container.with_column do + render(Primer::Beta::Text.new(**text_options)) { text } + end +end %> diff --git a/app/components/projects/life_cycle_type_component.rb b/app/components/projects/life_cycle_type_component.rb new file mode 100644 index 000000000000..453f5d90c5d6 --- /dev/null +++ b/app/components/projects/life_cycle_type_component.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ +module Projects + class LifeCycleTypeComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + def text + model.model_name.human + end + + def icon + case model + when Project::StageDefinition + :"git-commit" + when Project::GateDefinition + :diamond + else + raise NotImplementedError, "Unknown model #{model.class} to render a LifeCycleTypeComponent with" + end + end + + def icon_color_class + helpers.hl_inline_class("life_cycle_step_definition", model) + end + + def text_options + # The tag: :div is is a hack to fix the line height difference + # caused by font_size: :small. That line height difference + # would otherwise lead to the text being not on the same height as the icon + { color: :muted, font_size: :small, tag: :div }.merge(options) + end + end +end diff --git a/app/components/projects/row_component.rb b/app/components/projects/row_component.rb index 3f540eac0ecd..66ff579a9d0e 100644 --- a/app/components/projects/row_component.rb +++ b/app/components/projects/row_component.rb @@ -29,8 +29,9 @@ #++ module Projects class RowComponent < ::RowComponent - delegate :favored_project_ids, to: :table delegate :identifier, to: :project + delegate :favored_project_ids, to: :table + delegate :project_life_cycle_step_by_definition, to: :table def project model.first @@ -69,6 +70,8 @@ def currently_favored? def column_value(column) if custom_field_column?(column) custom_field_column(column) + elsif life_cycle_step_column?(column) + life_cycle_step_column(column) else send(column.attribute) end @@ -94,6 +97,16 @@ def custom_field_column(column) end end + def life_cycle_step_column(column) + return nil unless user_can_view_project_stages_and_gates? + + life_cycle_step = project_life_cycle_step_by_definition(column.life_cycle_step_definition, project) + + return nil if life_cycle_step.blank? + + fmt_date_or_range(life_cycle_step.start_date, life_cycle_step.end_date) + end + def created_at helpers.format_date(project.created_at) end @@ -370,12 +383,37 @@ def user_can_view_project? User.current.allowed_in_project?(:view_project_attributes, project) end + def user_can_view_project_stages_and_gates? + User.current.allowed_in_project?(:view_project_stages_and_gates, project) + end + def custom_field_column?(column) column.is_a?(::Queries::Projects::Selects::CustomField) end + def life_cycle_step_column?(column) + column.is_a?(::Queries::Projects::Selects::LifeCycleStep) + end + def current_page table.model.current_page.to_s end + + private + + # If only the `start_date` is given, will return a formatted version of that date as string. + # When `end_date` is given as well, will return a representation of the date range from start to end. + # @example + # fmt_date_or_range(Date.new(2024, 12, 4)) + # "04/12/2024" + # + # fmt_date_or_range(Date.new(2024, 12, 4), Date.new(2024, 12, 10)) + # "04/12/2024 - 10/12/2024" + def fmt_date_or_range(start_date, end_date = nil) + [start_date, end_date] + .compact + .map { |d| helpers.format_date(d) } + .join(" - ") + end end end diff --git a/app/components/projects/settings/index_page_header_component.html.erb b/app/components/projects/settings/index_page_header_component.html.erb new file mode 100644 index 000000000000..06c1f59002e5 --- /dev/null +++ b/app/components/projects/settings/index_page_header_component.html.erb @@ -0,0 +1,100 @@ +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t(:label_information_plural) } + header.with_breadcrumbs( [ + { href: project_overview_path(@project.id), text: @project.name }, + { href: project_settings_general_path(@project.id), text: I18n.t("label_project_settings") }, + t(:label_information_plural) + ]) + + if User.current.allowed_in_project?(:add_subprojects, @project) + header.with_action_button(scheme: :primary, + mobile_icon: :plus, + mobile_label: t(:label_subproject_new), + aria: { label: t(:label_subproject_new) }, + title: t(:label_subproject_new), + tag: :a, + href: new_project_path(parent_id: @project.id)) do |button| + button.with_leading_visual_icon(icon: :plus) + t(:label_subproject) + end + end + + header.with_action_button(tag: :a, + mobile_icon: :pencil, + mobile_label: t('projects.settings.change_identifier'), + size: :medium, + href: project_identifier_path(@project), + aria: { label: t('projects.settings.change_identifier') }, + title: t('projects.settings.change_identifier')) do |button| + button.with_leading_visual_icon(icon: :pencil) + t('projects.settings.change_identifier') + end + + header.with_action_menu( + menu_arguments: { + anchor_align: :end + }, + button_arguments: { + icon: "op-kebab-vertical", + "aria-label": t(:label_more), + test_selector: "project-settings-more-menu" + } + ) do |menu| + if @project.copy_allowed? + menu.with_item( + label:t(:button_copy), + href: copy_project_path(@project), + content_arguments: { + data: { turbo: false }, + test_selector: "project-settings--copy" + }, + accesskey: helpers.accesskey(:copy), + ) do |item| + item.with_leading_visual_icon(icon: :copy) + end + end + + if User.current.allowed_in_project?(:archive_project, @project) + menu.with_item( + tag: :a, + label: t(:button_archive), + href: project_archive_path(@project, status: '', name: @project.name), + content_arguments: { + data: { confirm: t('project.archive.are_you_sure', name: @project.name), method: :post, }, + test_selector: "project-settings--archive" + } + ) do |item| + item.with_leading_visual_icon(icon: 'lock') + end + end + if User.current.admin? + label = @project.templated ? 'remove_from_templates' : 'make_template' + menu.with_item( + tag: :a, + label: t("project.template.#{label}"), + href: project_templated_path(@project), + content_arguments: { + data: { method: @project.templated ? :delete : :post }, + test_selector: "project-settings--mark-template" + } + ) do |item| + item.with_leading_visual_icon(icon: @project.templated ? :"file-removed" : :"file-added") + end + + menu.with_item( + tag: :a, + scheme: :danger, + label: t(:button_delete), + href: confirm_destroy_project_path(@project), + content_arguments: { + data: { turbo: false }, + test_selector: "project-settings--delete" + } + ) do |item| + item.with_leading_visual_icon(icon: :trash) + end + end + end + end +%> diff --git a/app/components/projects/settings/index_page_header_component.rb b/app/components/projects/settings/index_page_header_component.rb new file mode 100644 index 000000000000..a57128ce4f05 --- /dev/null +++ b/app/components/projects/settings/index_page_header_component.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +class Projects::Settings::IndexPageHeaderComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + + def initialize(project:) + super + + @project = project + end +end diff --git a/app/components/projects/settings/life_cycle_steps/index_component.html.erb b/app/components/projects/settings/life_cycle_steps/index_component.html.erb new file mode 100644 index 000000000000..8ab58ef17910 --- /dev/null +++ b/app/components/projects/settings/life_cycle_steps/index_component.html.erb @@ -0,0 +1,88 @@ +<%= + flex_layout(data: wrapper_data_attributes) do |flex| + flex.with_row do + render(Primer::OpenProject::SubHeader.new) do |subheader| + subheader.with_filter_input(name: "border-box-filter", + label: t('projects.settings.life_cycle.filter.label'), + visually_hide_label: true, + placeholder: t('projects.settings.life_cycle.filter.label'), + leading_visual: { + icon: :search, + size: :small + }, + show_clear_button: true, + clear_button_id: clear_button_id, + data: { + action: "input->projects--settings--border-box-filter#filterLists", + "projects--settings--border-box-filter-target": "filter" + }) + end + end + + flex.with_row do + render(border_box_container(mb: 3, data: { test_selector: "project-life-cycle-administration" })) do |component| + component.with_header(font_weight: :bold, py: 2) do + flex_layout(justify_content: :space_between, align_items: :center) do |header_container| + header_container.with_column(py: 2) do + # adding py: 2 here to match the padding of the actions_container + # otherwise the header height changes when the actions gets hidden when filtering + render(Primer::Beta::Text.new(font_weight: :bold)) do + I18n.t('projects.settings.life_cycle.section_header') + end + end + header_container.with_column(flex_layout: true, justify_content: :flex_end) do |actions_container| + actions_container.with_column(data: { 'projects--settings--border-box-filter-target': 'bulkActionContainer' }) do + render(Primer::Beta::Button.new( + tag: :a, + href: enable_all_project_settings_life_cycle_steps_path(project_id: project), + scheme: :invisible, + font_weight: :bold, + color: :subtle, + 'aria-label': t('projects.settings.actions.label_enable_all'), + data: { 'turbo-method': :post, test_selector: "enable-all-life-cycle-steps" } + )) do |button| + button.with_leading_visual_icon(icon: 'check-circle', color: :subtle) + t('projects.settings.actions.label_enable_all') + end + end + actions_container.with_column(data: { 'projects--settings--border-box-filter-target': 'bulkActionContainer' }) do + render(Primer::Beta::Button.new( + tag: :a, + href: disable_all_project_settings_life_cycle_steps_path(project_id: project), + scheme: :invisible, + font_weight: :bold, + color: :subtle, + 'aria-label': t('projects.settings.actions.label_disable_all'), + data: { 'turbo-method': :post, test_selector: "disable-all-life-cycle-steps" } + )) do |button| + button.with_leading_visual_icon(icon: 'x-circle', color: :subtle) + t('projects.settings.actions.label_disable_all') + end + end + end + end + end + if life_cycle_definitions.empty? + component.with_row do + render(Primer::Beta::Text.new(color: :subtle)) { t("projects.settings.life_cycle.non_defined") } + end + else + life_cycle_definitions_and_step_active.each do |definition, active| + component.with_row(data: { 'projects--settings--border-box-filter-target': 'searchItem' }, + test_selector: "project-life-cycle-step-#{definition.id}") do + render(Projects::Settings::LifeCycleSteps::StepComponent.new(definition:, active?: active)) + end + end + end + end + end + flex.with_row do + render Primer::Beta::Text.new(display: :none, + data: { + "projects--settings--border-box-filter-target": "noResultsText", + }) do + I18n.t("js.autocompleter.notFoundText") + end + end + end +%> diff --git a/app/components/projects/settings/life_cycle_steps/index_component.rb b/app/components/projects/settings/life_cycle_steps/index_component.rb new file mode 100644 index 000000000000..bd1c174f1ccc --- /dev/null +++ b/app/components/projects/settings/life_cycle_steps/index_component.rb @@ -0,0 +1,64 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Projects + module Settings + module LifeCycleSteps + class IndexComponent < ApplicationComponent + include ApplicationHelper + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + options :project, + :life_cycle_definitions + + private + + def life_cycle_definitions_and_step_active + active_ids = project.life_cycle_steps.where(active: true).pluck(:definition_id).to_set + + life_cycle_definitions.all.map do |definition| + [definition, definition.id.in?(active_ids)] + end + end + + def wrapper_data_attributes + { + controller: "projects--settings--border-box-filter", + "application-target": "dynamic", + "projects--settings--border-box-filter-clear-button-id-value": clear_button_id + } + end + + def clear_button_id + "border-box-filter-clear-button" + end + end + end + end +end diff --git a/app/components/projects/settings/life_cycle_steps/index_page_header_component.html.erb b/app/components/projects/settings/life_cycle_steps/index_page_header_component.html.erb new file mode 100644 index 000000000000..8c7eb8f55bcc --- /dev/null +++ b/app/components/projects/settings/life_cycle_steps/index_page_header_component.html.erb @@ -0,0 +1,11 @@ +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t('projects.settings.life_cycle.header.title') } + header.with_description do + t('projects.settings.life_cycle.header.description_html', + overview_url: project_path(project), + admin_settings_url: admin_settings_project_life_cycle_step_definitions_path) + end + header.with_breadcrumbs(breadcrumb_items) + end +%> diff --git a/app/components/projects/settings/life_cycle_steps/index_page_header_component.rb b/app/components/projects/settings/life_cycle_steps/index_page_header_component.rb new file mode 100644 index 000000000000..8be423936dbe --- /dev/null +++ b/app/components/projects/settings/life_cycle_steps/index_page_header_component.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Projects::Settings::LifeCycleSteps + class IndexPageHeaderComponent < ApplicationComponent + include ApplicationHelper + + options :project + + def breadcrumb_items + [{ href: project_overview_path(project), text: project.name }, + { href: project_settings_general_path(project), text: I18n.t("label_project_settings") }, + t("projects.settings.life_cycle.header.title")] + end + end +end diff --git a/app/components/projects/settings/life_cycle_steps/step_component.html.erb b/app/components/projects/settings/life_cycle_steps/step_component.html.erb new file mode 100644 index 000000000000..9eda07459e3c --- /dev/null +++ b/app/components/projects/settings/life_cycle_steps/step_component.html.erb @@ -0,0 +1,30 @@ +<%= + flex_layout(align_items: :center, + justify_content: :space_between) do |step_container| + step_container.with_column(flex_layout: true) do |title_container| + title_container.with_column(pt: 1, mr: 3) do + render(Primer::Beta::Text.new(classes: 'filter-target-visible-text')) { definition.name } + end + title_container.with_column(pt: 1) do + render(Projects::LifeCycleTypeComponent.new(definition)) + end + end + # py: 1 quick fix: prevents the row from bouncing as the toggle switch currently changes height while toggling + step_container.with_column(py: 1, mr: 2) do + # buggy currently: + # small variant + status_label_position: :start leads to a small bounce while toggling + # behavior can be seen on primer's viewbook as well -> https://view-components-storybook.eastus.cloudapp.azure.com/view-components/lookbook/inspect/primer/alpha/toggle_switch/small + # quick fix: don't display loading indicator which is causing the bounce + render(Primer::Alpha::ToggleSwitch.new( + src: toggle_project_settings_life_cycle_step_path(id: definition.id), + csrf_token: form_authenticity_token, + data: { test_selector: "toggle-project-life-cycle-#{definition.id}" }, + aria: { label: toggle_aria_label }, + checked: active?, + size: :small, + status_label_position: :start, + classes: "op-primer-adjustments__toggle-switch--hidden-loading-indicator", + )) + end + end +%> diff --git a/app/components/projects/settings/life_cycle_steps/step_component.rb b/app/components/projects/settings/life_cycle_steps/step_component.rb new file mode 100644 index 000000000000..2dd0f59b6479 --- /dev/null +++ b/app/components/projects/settings/life_cycle_steps/step_component.rb @@ -0,0 +1,46 @@ +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module Projects + module Settings + module LifeCycleSteps + class StepComponent < ApplicationComponent + include ApplicationHelper + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + options :definition, + :active? + + def toggle_aria_label + I18n.t("projects.settings.life_cycle.step.use_in_project", step: definition.name) + end + end + end + end +end diff --git a/app/components/projects/settings/project_custom_field_sections/custom_field_row_component.html.erb b/app/components/projects/settings/project_custom_field_sections/custom_field_row_component.html.erb index 374bb4ed0642..fae8cdf15c38 100644 --- a/app/components/projects/settings/project_custom_field_sections/custom_field_row_component.html.erb +++ b/app/components/projects/settings/project_custom_field_sections/custom_field_row_component.html.erb @@ -5,7 +5,7 @@ }) do |custom_field_container| custom_field_container.with_column(flex_layout: true) do |title_container| title_container.with_column(pt: 1, mr: 2) do - render(Primer::Beta::Text.new) do + render(Primer::Beta::Text.new(classes: 'filter-target-visible-text')) do @project_custom_field.name end end diff --git a/app/components/projects/settings/project_custom_field_sections/index_component.html.erb b/app/components/projects/settings/project_custom_field_sections/index_component.html.erb index b4044ee99669..f54e78c9f20b 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_component.html.erb +++ b/app/components/projects/settings/project_custom_field_sections/index_component.html.erb @@ -3,7 +3,7 @@ flex_layout do |flex| flex.with_row do render(Primer::OpenProject::SubHeader.new) do |subheader| - subheader.with_filter_input(name: "project-custom-fields-mapping-filter", + subheader.with_filter_input(name: "border-box-filter", label: t('projects.settings.project_custom_fields.filter.label'), visually_hide_label: true, placeholder: t('projects.settings.project_custom_fields.filter.label'), @@ -14,8 +14,8 @@ show_clear_button: true, clear_button_id: clear_button_id, data: { - action: "input->projects--settings--project-custom-fields-mapping-filter#filterLists", - "projects--settings--project-custom-fields-mapping-filter-target": "filter" + action: "input->projects--settings--border-box-filter#filterLists", + "projects--settings--border-box-filter-target": "filter" }) end end diff --git a/app/components/projects/settings/project_custom_field_sections/index_component.rb b/app/components/projects/settings/project_custom_field_sections/index_component.rb index 25b8a86e8b71..b405aa381d38 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_component.rb +++ b/app/components/projects/settings/project_custom_field_sections/index_component.rb @@ -45,14 +45,14 @@ def initialize(project:, project_custom_field_sections:) def wrapper_data_attributes { - controller: "projects--settings--project-custom-fields-mapping-filter", + controller: "projects--settings--border-box-filter", "application-target": "dynamic", - "projects--settings--project-custom-fields-mapping-filter-clear-button-id-value": clear_button_id + "projects--settings--border-box-filter-clear-button-id-value": clear_button_id } end def clear_button_id - "project-custom-fields-mapping-filter-clear-button" + "border-box-filter-clear-button" end end end diff --git a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.html.erb b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.html.erb index 7cbbd5903e1d..b56371d9d989 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.html.erb +++ b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.html.erb @@ -1,8 +1,7 @@ <%= render Primer::OpenProject::PageHeader.new do |header| %> - <%= header.with_title(variant: :default) { t('projects.settings.project_custom_fields.header.title') } %> - <%= header.with_description { t('projects.settings.project_custom_fields.header.description', - overview_url: project_path(@project), - admin_settings_url: admin_settings_project_custom_fields_path - ).html_safe } %> + <%= header.with_title { t('projects.settings.project_custom_fields.header.title') } %> + <%= header.with_description { t('projects.settings.project_custom_fields.header.description_html', + overview_url: project_path(project), + admin_settings_url: admin_settings_project_custom_fields_path) } %> <%= header.with_breadcrumbs(breadcrumb_items) %> <% end %> diff --git a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb index 13014aae6aab..fe1b93434a1f 100644 --- a/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb +++ b/app/components/projects/settings/project_custom_field_sections/index_page_header_component.rb @@ -33,14 +33,11 @@ module Projects::Settings::ProjectCustomFieldSections class IndexPageHeaderComponent < ApplicationComponent include ApplicationHelper - def initialize(project: nil) - super - @project = project - end + options :project def breadcrumb_items - [{ href: project_overview_path(@project.id), text: @project.name }, - { href: project_settings_general_path(@project.id), text: I18n.t("label_project_settings") }, + [{ href: project_overview_path(project), text: project.name }, + { href: project_settings_general_path(project), text: I18n.t("label_project_settings") }, t("settings.project_attributes.heading")] end end diff --git a/app/components/projects/settings/project_custom_field_sections/show_component.html.erb b/app/components/projects/settings/project_custom_field_sections/show_component.html.erb index 727d8901228b..f2c5ee4e3a7f 100644 --- a/app/components/projects/settings/project_custom_field_sections/show_component.html.erb +++ b/app/components/projects/settings/project_custom_field_sections/show_component.html.erb @@ -13,7 +13,7 @@ end end section_header_container.with_column(flex_layout: true, justify_content: :flex_end) do |actions_container| - actions_container.with_column(data: { 'projects--settings--project-custom-fields-mapping-filter-target': 'bulkActionContainer' }) do + actions_container.with_column(data: { 'projects--settings--border-box-filter-target': 'bulkActionContainer' }) do render(Primer::Beta::Button.new( tag: :a, href: enable_all_of_section_project_settings_project_custom_fields_path( @@ -25,14 +25,14 @@ scheme: :invisible, font_weight: :bold, color: :subtle, - 'aria-label': t('projects.settings.project_custom_fields.actions.label_enable_all'), + 'aria-label': t('projects.settings.actions.label_enable_all'), data: { 'turbo-method': :put, 'turbo-stream': true, test_selector: "enable-all-project-custom-field-mappings-#{@project_custom_field_section.id}" } )) do |button| button.with_leading_visual_icon(icon: 'check-circle', color: :subtle) - t('projects.settings.project_custom_fields.actions.label_enable_all') + t('projects.settings.actions.label_enable_all') end end - actions_container.with_column(data: { 'projects--settings--project-custom-fields-mapping-filter-target': 'bulkActionContainer' }) do + actions_container.with_column(data: { 'projects--settings--border-box-filter-target': 'bulkActionContainer' }) do render(Primer::Beta::Button.new( tag: :a, href: disable_all_of_section_project_settings_project_custom_fields_path( @@ -44,11 +44,11 @@ scheme: :invisible, font_weight: :bold, color: :subtle, - 'aria-label': t('projects.settings.project_custom_fields.actions.label_disable_all'), + 'aria-label': t('projects.settings.actions.label_disable_all'), data: { 'turbo-method': :put, 'turbo-stream': true, test_selector: "disable-all-project-custom-field-mappings-#{@project_custom_field_section.id}" } )) do |button| button.with_leading_visual_icon(icon: 'x-circle', color: :subtle) - t('projects.settings.project_custom_fields.actions.label_disable_all') + t('projects.settings.actions.label_disable_all') end end end @@ -60,7 +60,7 @@ end else @project_custom_fields.each do |project_custom_field| - component.with_row(data: { 'projects--settings--project-custom-fields-mapping-filter-target': 'searchItem' }) do + component.with_row(data: { 'projects--settings--border-box-filter-target': 'searchItem' }) do render(Projects::Settings::ProjectCustomFieldSections::CustomFieldRowComponent.new( project: @project, project_custom_field:, diff --git a/app/components/projects/table_component.html.erb b/app/components/projects/table_component.html.erb index a114faeff841..9f33feb4aeea 100644 --- a/app/components/projects/table_component.html.erb +++ b/app/components/projects/table_component.html.erb @@ -56,7 +56,7 @@ See COPYRIGHT and LICENSE files for more details. <% else %> <% if use_quick_action_table_headers? %> - <%= quick_action_table_header column.attribute, order_options(column, turbo: true) %> + <%= quick_action_table_header column, order_options(column, turbo: true) %> <% else %>
<%= t(:text_work_package_category_destroy_question, count: @issue_count) %>
diff --git a/app/views/categories/edit.html.erb b/app/views/categories/edit.html.erb index cd0a4711ef27..ea7ef00d6d33 100644 --- a/app/views/categories/edit.html.erb +++ b/app/views/categories/edit.html.erb @@ -26,7 +26,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. ++#%> -<%= toolbar title: Category.model_name.human %> + +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { @category.name } + header.with_breadcrumbs([{ href: project_overview_path(@project.id), text: @project.name }, + { href: project_settings_general_path(@project.id), text: I18n.t("label_project_settings") }, + { href: project_settings_categories_path(@project.id), text: t(:label_work_package_category_plural) }, + @category.name]) + end +%> + <%= labelled_tabular_form_for @category, as: :category do |f| %> <%= render partial: 'categories/form', locals: { f: f } %> <%= f.button t(:button_save), class: 'button -primary -with-icon icon-checkmark' %> diff --git a/app/views/categories/new.html.erb b/app/views/categories/new.html.erb index f93042e60ecc..bcb04b11371c 100644 --- a/app/views/categories/new.html.erb +++ b/app/views/categories/new.html.erb @@ -26,7 +26,17 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. ++#%> -<%= toolbar title: t(:label_work_package_category_new) %> + +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t(:label_work_package_category_new) } + header.with_breadcrumbs([{ href: project_overview_path(@project.id), text: @project.name }, + { href: project_settings_general_path(@project.id), text: I18n.t("label_project_settings") }, + { href: project_settings_categories_path(@project.id), text: t(:label_work_package_category_plural) }, + t(:label_work_package_category_new) + ]) + end %> + <%= labelled_tabular_form_for [@project, @category], as: :category do |f| %> <%= render partial: 'categories/form', locals: { f: f } %> <%= f.button t(:button_create), class: 'button -primary -with-icon icon-checkmark' %> diff --git a/app/views/colors/confirm_destroy.html.erb b/app/views/colors/confirm_destroy.html.erb index 878cb53b1785..a8bd724e9195 100644 --- a/app/views/colors/confirm_destroy.html.erb +++ b/app/views/colors/confirm_destroy.html.erb @@ -26,7 +26,16 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. See COPYRIGHT and LICENSE files for more details. ++#%> -<%= toolbar title: @color.name %> + +<%= + render(Primer::OpenProject::PageHeader.new) do |header| + header.with_title { @color.name } + header.with_breadcrumbs([{ href: admin_index_path, text: t(:label_administration) }, + { href: colors_path, text: t(:label_color_plural) }, + @color.name]) + end +%> + <%= labelled_tabular_form_for @color, url: color_url(@color), html: {method: 'delete'}, diff --git a/app/views/enterprises/_info.html.erb b/app/views/enterprises/_info.html.erb index 14ac77975146..2d83f73f2c05 100644 --- a/app/views/enterprises/_info.html.erb +++ b/app/views/enterprises/_info.html.erb @@ -27,9 +27,8 @@ See COPYRIGHT and LICENSE files for more details. ++#%> <% content_for :header_tags do %> - + <%= nonced_javascript_include_tag OpenProject::Static::Links.links[:chargebee][:href], + "data-cb-site": OpenProject::Configuration.enterprise_chargebee_site %> <% end %>