diff --git a/.rubocop.yml b/.rubocop.yml index 5b43d7a548b..c0ae3d84751 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -142,7 +142,7 @@ Naming/ClassAndModuleCamelCase: Naming/FileName: Enabled: false -Naming/PredicateName: +Naming/PredicatePrefix: ForbiddenPrefixes: - is_ diff --git a/Gemfile b/Gemfile index fc11aebd50b..68f68299073 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", "~> 2.1.0" +gem "activerecord-import", "~> 2.2.0" gem "activerecord-session_store", "~> 2.2.0" gem "ox" gem "rails", "~> 8.0.1" @@ -62,7 +62,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.8.0" -gem "closure_tree", "~> 7.4.0" +gem "closure_tree", "~> 8.0.0" gem "rubytree", "~> 2.1.0" # Only used in down migrations now. # Is to be removed once the referencing migrations have been squashed. @@ -225,7 +225,7 @@ gem "appsignal", "~> 3.10.0", require: false # Yabeda integration gem "yabeda-activerecord" -gem "yabeda-prometheus-mmap" +gem "yabeda-prometheus-mmap", require: false gem "yabeda-puma-plugin" gem "yabeda-rails" @@ -400,7 +400,7 @@ platforms :mri, :mingw, :x64_mingw do # 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. - gem "with_advisory_lock", "~> 5.1.0" + gem "with_advisory_lock", "~> 5.3.0" end # Load Gemfile.modules explicitly to allow dependabot to work diff --git a/Gemfile.lock b/Gemfile.lock index 9a97dafb24a..fc992f36fa5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -208,7 +208,7 @@ PATH remote: modules/two_factor_authentication specs: openproject-two_factor_authentication (1.0.0) - aws-sdk-sns (~> 1.99.0) + aws-sdk-sns (~> 1.100.0) messagebird-rest (~> 1.4.2) rotp (~> 6.1) webauthn (~> 3.0) @@ -289,7 +289,7 @@ GEM activemodel (= 8.0.2) activesupport (= 8.0.2) timeout (>= 0.4.0) - activerecord-import (2.1.0) + activerecord-import (2.2.0) activerecord (>= 4.2) activerecord-nulldb-adapter (1.1.1) activerecord (>= 6.0, < 8.1) @@ -342,26 +342,26 @@ GEM activerecord (>= 4.0) awesome_nested_set (3.8.0) activerecord (>= 4.0.0, < 8.1) - aws-eventstream (1.3.2) - aws-partitions (1.1105.0) - aws-sdk-core (3.224.0) + aws-eventstream (1.4.0) + aws-partitions (1.1115.0) + aws-sdk-core (3.225.2) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) aws-sigv4 (~> 1.9) base64 jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.101.0) - aws-sdk-core (~> 3, >= 3.216.0) + aws-sdk-kms (1.104.0) + aws-sdk-core (~> 3, >= 3.225.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.186.1) - aws-sdk-core (~> 3, >= 3.216.0) + aws-sdk-s3 (1.189.1) + aws-sdk-core (~> 3, >= 3.225.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) - aws-sdk-sns (1.99.0) - aws-sdk-core (~> 3, >= 3.216.0) + aws-sdk-sns (1.100.0) + aws-sdk-core (~> 3, >= 3.225.0) aws-sigv4 (~> 1.5) - aws-sigv4 (1.11.0) + aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) axe-core-api (4.10.3) dumb_delegator @@ -378,7 +378,7 @@ GEM thread_safe (~> 0.3, >= 0.3.1) base64 (0.2.0) bcrypt (3.1.20) - benchmark (0.4.0) + benchmark (0.4.1) better_html (2.1.1) actionview (>= 6.0) activesupport (>= 6.0) @@ -386,7 +386,7 @@ GEM erubi (~> 1.4) parser (>= 2.4) smart_properties - bigdecimal (3.2.0) + bigdecimal (3.2.1) bindata (2.5.1) bootsnap (1.18.6) msgpack (~> 1.2) @@ -416,13 +416,13 @@ GEM carrierwave (>= 1.0.0) fog-aws cbor (0.5.9.8) - cgi (0.4.2) + cgi (0.5.0) childprocess (5.1.0) logger (~> 1.5) climate_control (1.2.0) - closure_tree (7.4.0) - activerecord (>= 4.2.10) - with_advisory_lock (>= 4.0.0) + closure_tree (8.0.0) + activerecord (>= 7.1.0) + with_advisory_lock (>= 5.0.0, < 6.0.0) coderay (1.1.3) coercible (1.0.0) descendants_tracker (~> 0.0.1) @@ -450,7 +450,7 @@ GEM crass (1.0.6) css_parser (1.21.1) addressable - csv (3.3.4) + csv (3.3.5) cuprite (0.17) capybara (~> 3.0) ferrum (~> 0.17.0) @@ -626,7 +626,7 @@ GEM fugit (>= 1.1) railties (>= 6.0.0) thor (>= 0.14.1) - google-apis-core (0.17.0) + google-apis-core (0.18.0) addressable (~> 2.5, >= 2.5.1) googleauth (~> 1.9) httpclient (>= 2.8.3, < 3.a) @@ -636,7 +636,7 @@ GEM retriable (>= 2.0, < 4.a) google-apis-gmail_v1 (0.43.0) google-apis-core (>= 0.15.0, < 2.a) - google-cloud-env (2.3.0) + google-cloud-env (2.3.1) base64 (~> 0.2) faraday (>= 1.0, < 3.a) google-logging-utils (0.2.0) @@ -659,7 +659,7 @@ GEM rack gravatar_image_tag (1.2.0) hana (1.3.7) - hashdiff (1.1.2) + hashdiff (1.2.0) hashery (2.1.2) hashie (5.0.0) highline (3.1.2) @@ -784,7 +784,7 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2025.0527) + mime-types-data (3.2025.0603) mini_magick (5.2.0) benchmark logger @@ -953,7 +953,7 @@ GEM date stringio public_suffix (6.0.2) - puffing-billy (4.0.1) + puffing-billy (4.0.2) addressable (~> 2.5) em-http-request (~> 1.1, >= 1.1.0) em-synchrony @@ -967,12 +967,12 @@ GEM puma (>= 5.0, < 7) raabro (1.4.0) racc (1.8.1) - rack (2.2.15) + rack (2.2.17) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-cors (2.0.2) rack (>= 2.0.0) - rack-mini-profiler (3.3.1) + rack-mini-profiler (4.0.0) rack (>= 1.2.0) rack-oauth2 (2.2.1) activesupport @@ -1013,7 +1013,7 @@ GEM actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.2.0) + rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) @@ -1032,14 +1032,14 @@ GEM thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.2.1) + rake (13.3.0) rake-compiler-dock (1.9.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rb_sys (0.9.115) + rb_sys (0.9.116) rake-compiler-dock (= 1.9.1) - rbtrace (0.5.1) + rbtrace (0.5.2) ffi (>= 1.0.6) msgpack (>= 0.4.3) optimist (>= 3.0.0) @@ -1097,7 +1097,7 @@ GEM rspec-support (3.13.4) rspec-wait (1.0.1) rspec (>= 3.4) - rubocop (1.75.8) + rubocop (1.76.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -1105,10 +1105,10 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.44.0, < 2.0) + rubocop-ast (>= 1.45.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.44.1) + rubocop-ast (1.45.1) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-capybara (2.22.1) @@ -1226,7 +1226,7 @@ GEM openssl-signature_algorithm (~> 1.0) trailblazer-option (0.1.2) ttfunk (1.7.0) - turbo-rails (2.0.13) + turbo-rails (2.0.16) actionpack (>= 7.1.0) railties (>= 7.1.0) turbo_power (0.7.0) @@ -1251,7 +1251,7 @@ GEM public_suffix vcr (6.3.1) base64 - vernier (1.7.1) + vernier (1.8.0) view_component (3.23.2) activesupport (>= 5.2.0, < 8.1) concurrent-ruby (~> 1) @@ -1282,12 +1282,12 @@ GEM hashdiff (>= 0.4.0, < 2.0.0) webrick (1.9.1) websocket (1.2.11) - websocket-driver (0.7.7) + websocket-driver (0.8.0) base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) will_paginate (4.0.1) - with_advisory_lock (5.1.0) + with_advisory_lock (5.3.0) activerecord (>= 6.1) zeitwerk (>= 2.6) xpath (3.2.0) @@ -1333,7 +1333,7 @@ DEPENDENCIES actionpack-xml_parser (~> 2.0.0) active_record_doctor (~> 1.15.0) activemodel-serializers-xml (~> 1.0.1) - activerecord-import (~> 2.1.0) + activerecord-import (~> 2.2.0) activerecord-nulldb-adapter (~> 1.1.1) activerecord-session_store (~> 2.2.0) acts_as_list (~> 1.2.0) @@ -1358,7 +1358,7 @@ DEPENDENCIES carrierwave (~> 1.3.4) carrierwave_direct (~> 2.1.0) climate_control - closure_tree (~> 7.4.0) + closure_tree (~> 8.0.0) colored2 commonmarker (~> 2.3.0) compare-xml (~> 0.66) @@ -1538,7 +1538,7 @@ DEPENDENCIES warden-basic_auth (~> 0.2.1) webmock (~> 3.12) will_paginate (~> 4.0.0) - with_advisory_lock (~> 5.1.0) + with_advisory_lock (~> 5.3.0) yabeda-activerecord yabeda-prometheus-mmap yabeda-puma-plugin @@ -1558,7 +1558,7 @@ CHECKSUMS activemodel (8.0.2) sha256=0ae1fb7fa1fae0699ba041a9e97702df42ea3b13f2d39f2d0fde51fca5f0656c activemodel-serializers-xml (1.0.3) sha256=fa1b16305e7254cc58a59c68833e3c0a593a59c8ab95d3be5aaea7cd9416c397 activerecord (8.0.2) sha256=793470b92c44e4198d0262ac60086b7822f0ea585079ad67e32a6e4c86f2d90a - activerecord-import (2.1.0) sha256=7b1bc586c4b636e125c18f533c9ac4421dd2dbac8f4865dbf576382a338c2c94 + activerecord-import (2.2.0) sha256=f8ca99b196e50775723d1f1d192c379f656378dc9f5628240992a0d78807fa4b activerecord-nulldb-adapter (1.1.1) sha256=034c91106183b954b072fba14c2786adf1a2b9e852ce04f85f823afaf03e9820 activerecord-session_store (2.2.0) sha256=65918054573683bf4f87af89e765e1fece14c9d71cfac1f11abe4687c96e2743 activestorage (8.0.2) sha256=f83d221e0f06ae38f2200e55490bd155c76d0add330f6e300e8646048d672977 @@ -1577,21 +1577,21 @@ CHECKSUMS attr_required (1.0.2) sha256=f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99 auto_strip_attributes (2.6.0) sha256=a7e2e0cf744de2bcd947fd68014220702bcc88c81274c1cd9ce6f7316aae39b0 awesome_nested_set (3.8.0) sha256=469daff411d80291dbb80d1973133e498048a7afc2519c545f62d2cdebc60eda - aws-eventstream (1.3.2) sha256=7e2c3a55ca70d7861d5d3c98e47daa463ed539c349caba22b48305b8919572d7 - aws-partitions (1.1105.0) sha256=a68010773df339ffe8074eac22de4d08e5235a1b25a895c3b2cea73ec54a6ea1 - aws-sdk-core (3.224.0) sha256=c617aae3f43ba2bfe9f819c1a31c7c9dbda3e1d1a33c746c23c4dc15638817ac - aws-sdk-kms (1.101.0) sha256=44d8b5b69ce7394cc02f30f9a35bea04ea12c947b5ffe1471535eea5119368d7 - aws-sdk-s3 (1.186.1) sha256=5b703b8aafc26ca4f3f927368412da5f4b8f74909bb0e4651eb8afe8aa105803 - aws-sdk-sns (1.99.0) sha256=a84cf111e375783f8d92093cb790757f6856acfe15cbf46a7f1a9868ded6ec0a - aws-sigv4 (1.11.0) sha256=50a8796991a862324442036ad7a395920572b84bb6cd29b945e5e1800e8da1db + aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b + aws-partitions (1.1115.0) sha256=787dc8a8d8db2aeb1474b4ee9a71e9f8b315d8109063fea0021030a6522f9e6c + aws-sdk-core (3.225.2) sha256=3ebed026b4bb527740cdf9f2a0c1b4a542d070ee015f8dd6bfc4c265d75dd4f8 + aws-sdk-kms (1.104.0) sha256=d65f13254452a9648fc3557018214e4c1809224c8538de576dd079772f0390f4 + aws-sdk-s3 (1.189.1) sha256=dd46336000eb3d78ff3ba4b648dd520c83c171ac29a04f13ddb08249fd1b7de4 + aws-sdk-sns (1.100.0) sha256=706bb13c5da789a5b9c6219b397980645536dd7f7301c09fbb8d3e8613f65a96 + aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 axe-core-api (4.10.3) sha256=6e10f3ed1c031804f16e8154d9d5dc658564d10850cee860e125fe665c3f0148 axe-core-rspec (4.10.3) sha256=ca21d0111e2d0fcd0f1da922c9071337336732aa6a3a8dc21bed94c9a701527e axiom-types (0.1.1) sha256=c1ff113f3de516fa195b2db7e0a9a95fd1b08475a502ff660d04507a09980383 base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 - benchmark (0.4.0) sha256=0f12f8c495545e3710c3e4f0480f63f06b4c842cc94cec7f33a956f5180e874a + benchmark (0.4.1) sha256=d4ef40037bba27f03b28013e219b950b82bace296549ec15a78016552f8d2cce better_html (2.1.1) sha256=046c3551d1488a3f2939a7cac6fabf2bde08c32e135c91fcd683380118e5af55 - bigdecimal (3.2.0) sha256=f220c34e07d98b04e02eb23193bee436fab9afcd551f43dce8837a1b4aa80762 + bigdecimal (3.2.1) sha256=1f68631e876c6aba8fe9b84b36983c55ad3293ff2d1ad4c6f115bde1e9d802e3 bindata (2.5.1) sha256=53186a1ec2da943d4cb413583d680644eb810aacbf8902497aac8f191fad9e58 bootsnap (1.18.6) sha256=0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00 brakeman (7.0.2) sha256=b602d91bcec6c5ce4d4bc9e081e01f621c304b7a69f227d1e58784135f333786 @@ -1605,10 +1605,10 @@ CHECKSUMS carrierwave (1.3.4) sha256=81772dabd1830edbd7f4526d2ae2c79f974f1d48900c3f03f7ecb7c657463a21 carrierwave_direct (2.1.0) sha256=b0d5c19c1d17a05940e488cff644a3b2946422d6d494b2174b48f6a90c5dddbf cbor (0.5.9.8) sha256=9ee097fc58d9bc5e406d112cd2d4e112c7354ec16f8b6ff34e4732c1e44b4eb7 - cgi (0.4.2) sha256=a3cb190d46a820ca01a3e28bd5b64e67003ff99d7884b70512448566f35347e6 + cgi (0.5.0) sha256=fe99f65bb2c146e294372ebb27602adbc3b4c008e9ea7038c6bd48c1ec9759da childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec climate_control (1.2.0) sha256=36b21896193fa8c8536fa1cd843a07cf8ddbd03aaba43665e26c53ec1bd70aa5 - closure_tree (7.4.0) sha256=e39171bbe35de3c06898c3caf2d2949bba2d379aa78fc1a03c1c63f165c1d2b5 + closure_tree (8.0.0) sha256=9f33c8b2511db80d3cb418cba34da85176585bb9f1077c5d58e873e2495dbbcb coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b coercible (1.0.0) sha256=5081ad24352cc8435ce5472bc2faa30260c7ea7f2102cc6a9f167c4d9bffaadc color_conversion (0.1.2) sha256=99bea5fa412e1527a11389975aa6ad445ff8528ebae202c11d08c45ea2b94c96 @@ -1629,7 +1629,7 @@ CHECKSUMS crack (1.0.0) sha256=c83aefdb428cdc7b66c7f287e488c796f055c0839e6e545fec2c7047743c4a49 crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d css_parser (1.21.1) sha256=6cfd3ffc0a97333b39d2b1b49c95397b05e0e3b684d68f77ec471ba4ec2ef7c7 - csv (3.3.4) sha256=e96ecd5a8c3494aa5b596282249daba5c6033203c199248e6146e36d2a78d8cd + csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f cuprite (0.17) sha256=b140d5dc70d08b97ad54bcf45cd95d0bd430e291e9dffe76fff851fddd57c12b daemons (1.4.1) sha256=8fc76d76faec669feb5e455d72f35bd4c46dc6735e28c420afb822fac1fa9a1d dalli (3.2.8) sha256=2e63595084d91fae2655514a02c5d4fc0f16c0799893794abe23bf628bebaaa5 @@ -1703,9 +1703,9 @@ CHECKSUMS globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9 gon (6.4.0) sha256=e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0 good_job (3.99.1) sha256=7d3869d8a8ee8ef7048fee5d746f41c21987b7822c20038a2f773036bef0830a - google-apis-core (0.17.0) sha256=3d4408b26b3f4b517b869be3c5aba40db0e172b4481c20ff882ef47579dd08f8 + google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee google-apis-gmail_v1 (0.43.0) sha256=037a218338b2d40b31d6f4c9206e58a6e34062fceac8a310f8dbc92b1a2b947b - google-cloud-env (2.3.0) sha256=db80b120fc163d1b83ffe8c0bc82e9ad0025ef0d51d987068c7278677ee5caf7 + google-cloud-env (2.3.1) sha256=0faac01eb27be78c2591d64433663b1a114f8f7af55a4f819755426cac9178e7 google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b googleauth (1.14.0) sha256=62e7de11791890c3d3dc70582dfd9ab5516530e4e4f56d96451fd62c76475149 grape (2.3.0) sha256=99484ae2907b06a9e109edf2911c383809bf7f7c00d65554e4d01f0388728bda @@ -1713,7 +1713,7 @@ CHECKSUMS gravatar_image_tag (1.2.0) sha256=eb5630fea846b711e713b934a0178fb9785f02f4eb9ced8d6faa4d537c40fdcf grids (1.0.0) hana (1.3.7) sha256=5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d - hashdiff (1.1.2) sha256=2c30eeded6ed3dce8401d2b5b99e6963fe5f14ed85e60dd9e33c545a44b71a77 + hashdiff (1.2.0) sha256=c984f13e115bfc9953332e8e83bd9d769cfde9944e2d54e07eb9df7b76e140b5 hashery (2.1.2) sha256=d239cc2310401903f6b79d458c2bbef5bf74c46f3f974ae9c1061fb74a404862 hashie (5.0.0) sha256=9d6c4e51f2a36d4616cbc8a322d619a162d8f42815a792596039fc95595603da highline (3.1.2) sha256=67cbd34d19f6ef11a7ee1d82ffab5d36dfd5b3be861f450fc1716c7125f4bb4a @@ -1764,7 +1764,7 @@ CHECKSUMS meta-tags (2.22.1) sha256=e5ae1febbd320d396c7226d7edb868e5d63466c14b9c8b06622a1a74e6dce354 method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5 mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56 - mime-types-data (3.2025.0527) sha256=11808d780cdb27ea5db0143dbfc261b91ed63ec5e595f424a35f9822cb497a02 + mime-types-data (3.2025.0603) sha256=00a122cf046ef3867c428ed5e6d97e759027b0caa375da7fba33a9799c8a3037 mini_magick (5.2.0) sha256=2757ffbfdb1d38242d1da9ff1505360ab75d59dc02eb7ab79ff6d5acb1243f4a mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289 @@ -1860,15 +1860,15 @@ CHECKSUMS pry-rescue (1.6.0) sha256=985bfd506d9866b587fd86790cf8445266a41b7f92c627fc5b21ec7d92aba6db psych (5.2.6) sha256=814328aa5dcb6d604d32126a20bc1cbcf05521a5b49dbb1a8b30a07e580f316e public_suffix (6.0.2) sha256=bfa7cd5108066f8c9602e0d6d4114999a5df5839a63149d3e8b0f9c1d3558394 - puffing-billy (4.0.1) sha256=d23ea4b2fd54d6d213a85c4a1f603459d9b1634d93ac012dab59d36f380e778a + puffing-billy (4.0.2) sha256=c983610f737e3847301f64cf41dccd9dcedcac9219af54d6eaf8178cbe48b3cc puma (6.6.0) sha256=f25c06873eb3d5de5f0a4ebc783acc81a4ccfe580c760cfe323497798018ad87 puma-plugin-statsd (2.6.0) sha256=c13ffe6a2fa2d3c245af673316e6d2556f5d0c151455d1934dffc96ce814ea03 raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f - rack (2.2.15) sha256=cfd082f748540702228e9d83f16e033be9c3a3e58f428f60f6bce26df38c1067 + rack (2.2.17) sha256=5fe02a1ca80d6fb2271dba00985ee2962d6f5620b6f46dfed89f5301ac4699dd rack-attack (6.7.0) sha256=3ca47e8f66cd33b2c96af53ea4754525cd928ed3fa8da10ee6dad0277791d77c rack-cors (2.0.2) sha256=415d4e1599891760c5dc9ef0349c7fecdf94f7c6a03e75b2e7c2b54b82adda1b - rack-mini-profiler (3.3.1) sha256=2bf0de7d5795f54581e453b248e42cc50e8d0529efac73828653a9ad2407a801 + rack-mini-profiler (4.0.0) sha256=c37bedcb7d01e33ad4addd8c4e742986e75db7cd8908cba3432c60b4e812e00f rack-oauth2 (2.2.1) sha256=c73aa87c508043e2258f02b4fb110cacba9b37d2ccf884e22487d014a120d1a5 rack-protection (3.2.0) sha256=3c74ba7fc59066453d61af9bcba5b6fe7a9b3dab6f445418d3b391d5ea8efbff rack-session (1.0.2) sha256=a02115e5420b4de036839b9811e3f7967d73446a554b42aa45106af335851d76 @@ -1878,17 +1878,17 @@ CHECKSUMS rackup (1.0.1) sha256=ba86604a28989fe1043bff20d819b360944ca08156406812dca6742b24b3c249 rails (8.0.2) sha256=fdfaa5a83ec0388e02864e88d515959caedc88053b5f701c4deb1652d8f164c6 rails-controller-testing (1.0.5) sha256=741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94 - rails-dom-testing (2.2.0) sha256=e515712e48df1f687a1d7c380fd7b07b8558faa26464474da64183a7426fa93b + rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d rails-html-sanitizer (1.6.2) sha256=35fce2ca8242da8775c83b6ba9c1bcaad6751d9eb73c1abaa8403475ab89a560 rails-i18n (8.0.1) sha256=15303195450bdac9a80636cf14c7e5ada2f43907cc60fcd19bbb3f81ab45be0d railties (8.0.2) sha256=0d7c3f40c49ba74980f1bac1d4bb153a9331c5ee8a9631d89c7bf79db82e5cf9 rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a - rake (13.2.1) sha256=46cb38dae65d7d74b6020a4ac9d48afed8eb8149c040eccf0523bec91907059d + rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 rb-fsevent (0.11.2) sha256=43900b972e7301d6570f64b850a5aa67833ee7d87b458ee92805d56b7318aefe rb-inotify (0.11.1) sha256=a0a700441239b0ff18eb65e3866236cd78613d6b9f78fea1f9ac47a85e47be6e - rb_sys (0.9.115) sha256=4696a623b89131f4a12bd9e38710add1c64b478dc3d2e9275f61a65f42f743ec - rbtrace (0.5.1) sha256=e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc + rb_sys (0.9.116) sha256=c879891018535d4362455197065ea580541b10ffdfa940bec512ec1dd9a7def4 + rbtrace (0.5.2) sha256=a2d7d222ab81363aaa0e91337ddbf70df834885d401a80ea0339d86c71f31895 rbtree3 (0.7.1) sha256=ab60ead728a5491b70df4f4065e180b18dbab5319f817ce1dbf5dd906f26d8ba rdoc (6.14.0) sha256=2c46de58d7129b8743fcf6d76e3db971bdc914150e15ac06b386549bd82ed7db recaptcha (5.19.0) sha256=b69c021a5b788da06901d2c95ab86f10a8634e6496343096cc5167f0318b2a39 @@ -1914,8 +1914,8 @@ CHECKSUMS rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858 rspec-support (3.13.4) sha256=184b1814f6a968102b57df631892c7f1990a91c9a3b9e80ef892a0fc2a71a3f7 rspec-wait (1.0.1) sha256=50ce9cc7cd20b8bb67b95a4ed56b59a16d4af7e86ae91b28dbf20eeaa2481d1f - rubocop (1.75.8) sha256=c80ab4286c5dcfc49d7ad1787cdba5569b63b58c96ee7afde4ec47a9c8a85be9 - rubocop-ast (1.44.1) sha256=e3cc04203b2ef04f6d6cf5f85fe6d643f442b18cc3b23e3ada0ce5b6521b8e92 + rubocop (1.76.1) sha256=e15a2d750794cf2157d2de8b1b403dfa71b8dc3957a22ae6043b1bdf21e7e0e7 + rubocop-ast (1.45.1) sha256=94042e49adc17f187ba037b33f941ba7398fede77cdf4bffafba95190a473a3e rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c rubocop-factory_bot (2.27.1) sha256=9d744b5916778c1848e5fe6777cc69855bd96548853554ec239ba9961b8573fe rubocop-openproject (0.2.0) sha256=2300ed6b638a34a9368b458dc5fb618ae396da6a27670b50519ad1e3cea65ccb @@ -1971,7 +1971,7 @@ CHECKSUMS tpm-key_attestation (0.14.0) sha256=d05cc52b397f89c36a7307407e0e84d3ea1c7afce50e0a70b146f8ab17d2bf4b trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 ttfunk (1.7.0) sha256=2370ba484b1891c70bdcafd3448cfd82a32dd794802d81d720a64c15d3ef2a96 - turbo-rails (2.0.13) sha256=c40ac0a3ccd57c129925c8ac524a5dfd1e17fad080906e2d32135721a8bba22f + turbo-rails (2.0.16) sha256=d24e1b60f0c575b3549ecda967e5391027143f8220d837ed792c8d48ea0ea38d turbo_power (0.7.0) sha256=ad95d147e0fa761d0023ad9ca00528c7b7ddf6bba8ca2e23755d5b21b290d967 turbo_tests (2.2.0) typed_dag (2.0.2) sha256=b8dc65bf8cb5461715aa64650b7c96fc2075ef29fdd94554a93896c43453828c @@ -1985,7 +1985,7 @@ CHECKSUMS validate_email (0.1.6) sha256=9dfe9016d527b17a8d3a6e95e4dc50a125400eef899d13d4cc2a254393f82ee4 validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451 vcr (6.3.1) sha256=37b56e157e720446a3f4d2d39919cabef8cb7b6c45936acffd2ef8229fec03ed - vernier (1.7.1) sha256=3cb17d41a43f32a317d76bd8973eb94d2610e65c5442ebdec6accbfb0ed734a4 + vernier (1.8.0) sha256=c6cf9555c2a58affc25ebc45de0b4d45a993eb078f88f14f5b45d7bacb25acdd view_component (3.23.2) sha256=3c2fed4b9e38bf074fa3d42ca55eedbb2cc070e0f3c31d7c13a50b0db530892b virtus (2.0.0) sha256=8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2 warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0 @@ -1995,10 +1995,10 @@ CHECKSUMS webmock (3.25.1) sha256=ab9d5d9353bcbe6322c83e1c60a7103988efc7b67cd72ffb9012629c3d396323 webrick (1.9.1) sha256=b42d3c94f166f3fb73d87e9b359def9b5836c426fc8beacf38f2184a21b2a989 websocket (1.2.11) sha256=b7e7a74e2410b5e85c25858b26b3322f29161e300935f70a0e0d3c35e0462737 - websocket-driver (0.7.7) sha256=056d99f2cd545712cfb1291650fde7478e4f2661dc1db6a0fa3b966231a146b4 + websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 will_paginate (4.0.1) sha256=107b226ebe1d393d274575956a7c472e1eefdd97d8828e01b72d425d15a875b9 - with_advisory_lock (5.1.0) sha256=0692cd82013b271c59aa5e1f603b741ab94926dbce098990fbace5dd5412fed7 + with_advisory_lock (5.3.0) sha256=bdb1864430853fe9081a7765f2d5b7cc42725c9ccf1901d20e9989828901b94e xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e yabeda (0.13.1) sha256=3213025f22b7746602c8a4c41e2ed82d73a90bdc5489f6ef472142b06c1cf954 yabeda-activerecord (0.1.1) sha256=ae338213c264f20d8642e8bf47ac6058c49a6f7c8c00c892cd5765332914f45f diff --git a/app/components/_index.sass b/app/components/_index.sass index b85bb451e78..c0c27dc3275 100644 --- a/app/components/_index.sass +++ b/app/components/_index.sass @@ -4,6 +4,8 @@ @import "op_primer/border_box_table_component" @import "op_primer/form_helpers" @import "open_project/common/attribute_component" +@import "open_project/common/attribute_help_text_component" +@import "open_project/common/attribute_label_component" @import "open_project/common/submenu_component" @import "projects/row_component" @import "projects/phases/hover_card_component" diff --git a/app/components/admin/custom_fields/hierarchy/item_component.rb b/app/components/admin/custom_fields/hierarchy/item_component.rb index 2e25f04aa65..ce06a4f79d6 100644 --- a/app/components/admin/custom_fields/hierarchy/item_component.rb +++ b/app/components/admin/custom_fields/hierarchy/item_component.rb @@ -98,7 +98,7 @@ module Admin tag: :a, content_arguments: { data: { turbo_frame: ItemsComponent.wrapper_key } }, href: new_child_custom_field_item_path(@root.custom_field_id, model.parent, position: model.sort_order) - ) { _1.with_leading_visual_icon(icon: "fold-up") } + ) { it.with_leading_visual_icon(icon: "fold-up") } end def add_below_action_item(menu) @@ -107,16 +107,18 @@ module Admin tag: :a, content_arguments: { data: { turbo_frame: ItemsComponent.wrapper_key } }, href: new_child_custom_field_item_path(@root.custom_field_id, model.parent, position: model.sort_order + 1) - ) { _1.with_leading_visual_icon(icon: "fold-down") } + ) { it.with_leading_visual_icon(icon: "fold-down") } end def add_sub_item_action_item(menu) + position = model.children.any? ? model.children.maximum(:sort_order) + 1 : 0 + menu.with_item( label: I18n.t(:button_add_sub_item), tag: :a, content_arguments: { data: { turbo_frame: ItemsComponent.wrapper_key } }, - href: new_child_custom_field_item_path(@root.custom_field_id, model) - ) { _1.with_leading_visual_icon(icon: "op-arrow-in") } + href: new_child_custom_field_item_path(@root.custom_field_id, model, position:) + ) { it.with_leading_visual_icon(icon: "op-arrow-in") } end def move_to_top_action_item(menu) diff --git a/app/components/admin/custom_fields/hierarchy/items_component.rb b/app/components/admin/custom_fields/hierarchy/items_component.rb index 89253771c21..bcfca71c8bb 100644 --- a/app/components/admin/custom_fields/hierarchy/items_component.rb +++ b/app/components/admin/custom_fields/hierarchy/items_component.rb @@ -60,7 +60,7 @@ module Admin end def item_header - render(Primer::Beta::Breadcrumbs.new) do |loaf| + render(Primer::Beta::Breadcrumbs.new(data: { test_selector: "hierarchy-breadcrumbs" })) do |loaf| slices.each do |slice| loaf.with_item(href: slice[:href], target: nil) { slice[:label] } end diff --git a/app/components/admin/custom_fields/hierarchy/tree_view_component.html.erb b/app/components/admin/custom_fields/hierarchy/tree_view_component.html.erb new file mode 100644 index 00000000000..ed3a1fff18c --- /dev/null +++ b/app/components/admin/custom_fields/hierarchy/tree_view_component.html.erb @@ -0,0 +1,35 @@ +<%#-- 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. + +++#%> +<%= render(Primer::Beta::Heading.new(tag: :h3, font_size: 4, mb: 2)) { @custom_field.name } %> + +<%= + render(Primer::OpenProject::TreeView.new(node_variant: :anchor)) do |tree_view| + add_sub_tree(tree_view, hierarchy_items) + end +%> diff --git a/app/components/admin/custom_fields/hierarchy/tree_view_component.rb b/app/components/admin/custom_fields/hierarchy/tree_view_component.rb new file mode 100644 index 00000000000..e99ab023524 --- /dev/null +++ b/app/components/admin/custom_fields/hierarchy/tree_view_component.rb @@ -0,0 +1,76 @@ +# 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 Admin + module CustomFields + module Hierarchy + class TreeViewComponent < ApplicationComponent + def initialize(custom_field:, active_item:) + super + + @custom_field = custom_field + @active_item = active_item + @hierarchy_service = ::CustomFields::Hierarchy::HierarchicalItemService.new + end + + def hierarchy_items + hashed_hierarchy = @custom_field.hierarchy_root.hash_tree + hashed_hierarchy.nil? ? {} : hashed_hierarchy.first[1] + end + + def add_sub_tree(tree, hierarchy_hash) + hierarchy_hash.each do |item, child_hash| + if child_hash.empty? + tree.with_leaf(**item_options(item)) + else + expanded = child_hash.any? { |child, _| current?(child) } + + tree.with_sub_tree(expanded: expanded, **item_options(item)) do |sub_tree| + add_sub_tree(sub_tree, child_hash) + end + end + end + end + + def item_options(item) + { + label: item.label, + current: current?(item), + href: custom_field_item_path(@custom_field, item) + } + end + + def current?(item) + item.id == @active_item.id + end + end + end + end +end diff --git a/app/components/application_component.rb b/app/components/application_component.rb index 53dc6caf507..077111d26f2 100644 --- a/app/components/application_component.rb +++ b/app/components/application_component.rb @@ -42,6 +42,21 @@ class ApplicationComponent < ViewComponent::Base end class << self + ## + # Generates a unique identifier suitable for use as an HTML `id` attribute + # value. If passed a non-nil model instance, it will use the model's id to + # generate the identifier (see {ActiveModel::Conversion#to_param}). + # Otherwise, it will generate an identifier with random numbers. + # + # @see ActiveModel::Conversion#to_param + # + # @param [ActiveModel::Model] model an optional model instance. + # @param [String] base_name a prefix for the unique identifier. + # @return [String] a unique identifier. + def generate_id(model = nil, base_name: name.demodulize.underscore.dasherize) + "#{base_name}-#{model&.to_param || SecureRandom.uuid}" + end + ## # Defines options for this cell which can be used within the cell's template. # Options are passed to the cell during the render call. diff --git a/app/components/attribute_help_texts/show_dialog_component.html.erb b/app/components/attribute_help_texts/show_dialog_component.html.erb new file mode 100644 index 00000000000..f5ad22ce1c1 --- /dev/null +++ b/app/components/attribute_help_texts/show_dialog_component.html.erb @@ -0,0 +1,33 @@ +<%= + render(Primer::Alpha::Dialog.new(id: dialog_id, title:, size: :large)) do |dialog| + dialog.with_header(variant: :large) + + dialog.with_body do + concat( + render(Primer::Box.new(mt: 1, classes: "op-uc-container op-uc-container__no-permalinks")) do + helpers.format_text(@attribute_help_text, :help_text) + end + ) + + if has_attachments? + concat render(Primer::Beta::Heading.new(tag: :h4)) { I18n.t(:label_attachment_plural) } + concat helpers.list_attachments(resource_representer, inputs: { allowUploading: false }) + end + end + + dialog.with_footer(show_divider: true) do + if allowed_to_edit? + concat( + render(Primer::Beta::Button.new(tag: :a, href: edit_button_href)) do |button| + button.with_leading_visual_icon(icon: :pencil) + I18n.t(:button_edit) + end + ) + end + + concat( + render(Primer::Beta::Button.new(data: { close_dialog_id: dialog_id })) { I18n.t(:button_close) } + ) + end + end +%> diff --git a/app/components/attribute_help_texts/show_dialog_component.rb b/app/components/attribute_help_texts/show_dialog_component.rb new file mode 100644 index 00000000000..93ac44f425f --- /dev/null +++ b/app/components/attribute_help_texts/show_dialog_component.rb @@ -0,0 +1,57 @@ +# 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 AttributeHelpTexts::ShowDialogComponent < ApplicationComponent + include OpTurbo::Streamable + + DIALOG_ID = "attribute-help-text-show-modal" + + def initialize(attribute_help_text:, current_user: User.current) + super + @attribute_help_text = attribute_help_text + @current_user = current_user + end + + private + + def dialog_id = DIALOG_ID + + def title = @attribute_help_text.attribute_caption + + def has_attachments? = @attribute_help_text.attachments.any? + + def allowed_to_edit? = @current_user.allowed_globally?(:edit_attribute_help_texts) + + def edit_button_href = url_helpers.edit_attribute_help_text_path(@attribute_help_text) + + def resource_representer + ::API::V3::HelpTexts::HelpTextRepresenter.new(@attribute_help_text, current_user: @current_user, embed_links: false) + end +end diff --git a/app/components/enterprise_edition/banner_component.rb b/app/components/enterprise_edition/banner_component.rb index 9f778e107d2..80be876e196 100644 --- a/app/components/enterprise_edition/banner_component.rb +++ b/app/components/enterprise_edition/banner_component.rb @@ -44,7 +44,7 @@ module EnterpriseEdition VARIANT_OPTIONS = %i[inline medium large].freeze # @param feature_key [Symbol, NilClass] The key of the feature to show the banner for. - # @param variant [Symbol, NilClass] The variant of the banner comopnent. + # @param variant [Symbol, NilClass] The variant of the banner component. # @param image [String, NilClass] Path to the image to show on the banner, or nil. # Only applicable and required when variant is :medium. # @param video [String, NilClass] Path to the video to show on the banner, or nil. @@ -55,7 +55,7 @@ module EnterpriseEdition # @param show_always [boolean] Always show the banner, regardless of the dismissed or feature state. # @param dismiss_key [String] Provide a string to identify this banner when being dismissed. Defaults to feature_key # @param system_arguments [Hash] <%= link_to_system_arguments_docs %> - def initialize(feature_key, # rubocop:disable Metrics/AbcSize + def initialize(feature_key, variant: DEFAULT_VARIANT, image: nil, video: nil, @@ -68,12 +68,15 @@ module EnterpriseEdition @image = image @video = video @dismissable = dismissable - @dismiss_key = dismiss_key + @dismiss_key = dismiss_key.to_s + @show_always = show_always self.feature_key = feature_key self.i18n_scope = i18n_scope + trial_overrides! if trial_feature? + if @variant == :medium && @image.nil? raise ArgumentError, "The 'image' parameter is required when the variant is :medium." end @@ -82,17 +85,7 @@ module EnterpriseEdition raise ArgumentError, "The 'video' parameter is required when the variant is :large." end - @system_arguments = system_arguments - @system_arguments[:tag] = :div - @system_arguments[:mb] ||= 2 - @system_arguments[:id] = "op-enterprise-banner-#{feature_key.to_s.tr('_', '-')}" - @system_arguments[:test_selector] = "op-enterprise-banner" - @system_arguments[:classes] = class_names( - @system_arguments[:classes], - "op-enterprise-banner", - @variant == :medium ? "op-enterprise-banner_medium" : nil, - @variant == :large ? "op-enterprise-banner_large" : nil - ) + set_system_arguments(system_arguments, feature_key) super end @@ -115,15 +108,39 @@ module EnterpriseEdition end def wrapper_key - "enterprise_banner_#{feature_key}" + "enterprise_banner_#{@dismiss_key}" end private + def set_system_arguments(system_arguments, feature_key) + @system_arguments = system_arguments + @system_arguments[:tag] = :div + @system_arguments[:mb] ||= 2 + @system_arguments[:id] = "op-enterprise-banner-#{feature_key.to_s.tr('_', '-')}" + @system_arguments[:test_selector] = "op-enterprise-banner" + @system_arguments[:classes] = class_names( + @system_arguments[:classes], + "op-enterprise-banner", + "op-enterprise-banner_medium" => @variant == :medium, + "op-enterprise-banner_large" => @variant == :large, + "op-enterprise-banner_trial" => trial_feature? + ) + end + + def trial_overrides! + @dismissable = true + @dismiss_key += "_trial" unless @dismiss_key.end_with?("_trial") + @variant = :inline + end + def render? return true if @show_always + return false if dismissed? + return true if feature_available? && trial_feature? + return false if EnterpriseToken.hide_banners? - !(EnterpriseToken.hide_banners? || feature_available? || dismissed?) + !feature_available? end def feature_available? @@ -135,5 +152,9 @@ module EnterpriseEdition User.current.pref.dismissed_banner?(@dismiss_key) end + + def trial_feature? + EnterpriseToken.trialling?(feature_key) + end end end diff --git a/app/components/enterprise_edition/upsell_buttons_component.rb b/app/components/enterprise_edition/upsell_buttons_component.rb index 2eaf28d164b..82cce0f45cf 100644 --- a/app/components/enterprise_edition/upsell_buttons_component.rb +++ b/app/components/enterprise_edition/upsell_buttons_component.rb @@ -58,6 +58,7 @@ module EnterpriseEdition def buttons [ + buy_now_button, free_trial_button, upgrade_now_button, more_info_button @@ -74,6 +75,20 @@ module EnterpriseEdition ) end + def buy_now_button + return unless EnterpriseToken.active? + return unless User.current.admin? + + render(Primer::Beta::Button.new( + classes: "upsell-colored-background", + tag: :a, + href: OpenProject::Enterprise.upgrade_path, + align_self: :center + )) do + I18n.t("ee.upsell.buy_now_button") + end + end + # Allow providing a custom upgrade now button def upgrade_now_button nil diff --git a/app/components/open_project/common/attribute_help_text_component.html.erb b/app/components/open_project/common/attribute_help_text_component.html.erb new file mode 100644 index 00000000000..f8d11778925 --- /dev/null +++ b/app/components/open_project/common/attribute_help_text_component.html.erb @@ -0,0 +1,6 @@ +<%= + render Primer::Beta::Link.new(**@system_arguments) do + render Primer::Beta::Octicon.new(:question, size: :xsmall) + end +%> +<%= render @tooltip %> diff --git a/app/components/open_project/common/attribute_help_text_component.rb b/app/components/open_project/common/attribute_help_text_component.rb new file mode 100644 index 00000000000..62a99304481 --- /dev/null +++ b/app/components/open_project/common/attribute_help_text_component.rb @@ -0,0 +1,71 @@ +# 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 OpenProject + module Common + class AttributeHelpTextComponent < ApplicationComponent + def initialize(help_text:, **system_arguments) # rubocop:disable Metrics/AbcSize + super() + + @help_text = help_text + + @system_arguments = system_arguments + @system_arguments[:id] ||= self.class.generate_id(help_text) + @system_arguments[:classes] = class_names( + @system_arguments[:classes], + "op-attribute-help-text" + ) + @system_arguments[:data] ||= {} + @system_arguments[:data][:controller] = "async-dialog" + + @tooltip = Primer::Alpha::Tooltip.new( + for_id: @system_arguments[:id], + type: :label, + text: I18n.t("js.help_texts.show_modal"), + direction: :sw + ) + @system_arguments[:aria] ||= {} + @system_arguments[:aria][:labelledby] = @tooltip.id + end + + private + + def render? + @help_text.present? + end + + def before_render + return unless @help_text + + @system_arguments[:href] = show_dialog_attribute_help_text_path(@help_text) + end + end + end +end diff --git a/app/components/open_project/common/attribute_help_text_component.sass b/app/components/open_project/common/attribute_help_text_component.sass new file mode 100644 index 00000000000..bc659993cb4 --- /dev/null +++ b/app/components/open_project/common/attribute_help_text_component.sass @@ -0,0 +1,30 @@ +//-- 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. +//++ + +.op-attribute-help-text + line-height: 1rem diff --git a/app/components/open_project/common/attribute_label_component.html.erb b/app/components/open_project/common/attribute_label_component.html.erb new file mode 100644 index 00000000000..c4d4bf4fd6a --- /dev/null +++ b/app/components/open_project/common/attribute_label_component.html.erb @@ -0,0 +1,4 @@ +<%= render Primer::ConditionalWrapper.new(condition: !@tag.nil?, trim: true, **@system_arguments) do %> + <%= content %> + <%= render OpenProject::Common::AttributeHelpTextComponent.new(help_text: @help_text) if @help_text.present? %> +<% end %> diff --git a/app/components/open_project/common/attribute_label_component.rb b/app/components/open_project/common/attribute_label_component.rb new file mode 100644 index 00000000000..dbc84503cc0 --- /dev/null +++ b/app/components/open_project/common/attribute_label_component.rb @@ -0,0 +1,57 @@ +# 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 OpenProject + module Common + class AttributeLabelComponent < ApplicationComponent + def initialize( + attribute:, + model:, + tag: :span, + current_user: User.current, + **system_arguments + ) + super() + + @tag = tag + @system_arguments = system_arguments + @system_arguments[:tag] = @tag + @system_arguments[:classes] = class_names( + @system_arguments[:classes], + "op-attribute-label" + ) + + @help_text = ::AttributeHelpText.for(model) + &.cached(current_user) + &.[](attribute.to_s) + end + end + end +end diff --git a/app/components/open_project/common/attribute_label_component.sass b/app/components/open_project/common/attribute_label_component.sass new file mode 100644 index 00000000000..06b81126d13 --- /dev/null +++ b/app/components/open_project/common/attribute_label_component.sass @@ -0,0 +1,34 @@ +//-- 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. +//++ + +.op-attribute-label + display: inline-flex + + > .op-attribute-help-text + align-self: baseline + margin-left: var(--base-size-6) diff --git a/app/components/projects/settings/life_cycle_steps/index_component.html.erb b/app/components/projects/settings/life_cycle/index_component.html.erb similarity index 97% rename from app/components/projects/settings/life_cycle_steps/index_component.html.erb rename to app/components/projects/settings/life_cycle/index_component.html.erb index 4f9ce4fa6f7..c3b867a620e 100644 --- a/app/components/projects/settings/life_cycle_steps/index_component.html.erb +++ b/app/components/projects/settings/life_cycle/index_component.html.erb @@ -78,7 +78,7 @@ 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)) + render(Projects::Settings::LifeCycle::PhaseComponent.new(definition:, active?: active)) end end end diff --git a/app/components/projects/settings/life_cycle_steps/index_component.rb b/app/components/projects/settings/life_cycle/index_component.rb similarity index 98% rename from app/components/projects/settings/life_cycle_steps/index_component.rb rename to app/components/projects/settings/life_cycle/index_component.rb index d747a35cfc4..a90c57804ee 100644 --- a/app/components/projects/settings/life_cycle_steps/index_component.rb +++ b/app/components/projects/settings/life_cycle/index_component.rb @@ -28,7 +28,7 @@ module Projects module Settings - module LifeCycleSteps + module LifeCycle class IndexComponent < ApplicationComponent include ApplicationHelper include OpPrimer::ComponentHelpers diff --git a/app/components/projects/settings/life_cycle_steps/index_page_header_component.html.erb b/app/components/projects/settings/life_cycle/index_page_header_component.html.erb similarity index 100% rename from app/components/projects/settings/life_cycle_steps/index_page_header_component.html.erb rename to app/components/projects/settings/life_cycle/index_page_header_component.html.erb diff --git a/app/components/projects/settings/life_cycle_steps/index_page_header_component.rb b/app/components/projects/settings/life_cycle/index_page_header_component.rb similarity index 97% rename from app/components/projects/settings/life_cycle_steps/index_page_header_component.rb rename to app/components/projects/settings/life_cycle/index_page_header_component.rb index 8be423936db..3a42453ac37 100644 --- a/app/components/projects/settings/life_cycle_steps/index_page_header_component.rb +++ b/app/components/projects/settings/life_cycle/index_page_header_component.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. # ++ -module Projects::Settings::LifeCycleSteps +module Projects::Settings::LifeCycle class IndexPageHeaderComponent < ApplicationComponent include ApplicationHelper diff --git a/app/components/projects/settings/life_cycle_steps/step_component.html.erb b/app/components/projects/settings/life_cycle/phase_component.html.erb similarity index 97% rename from app/components/projects/settings/life_cycle_steps/step_component.html.erb rename to app/components/projects/settings/life_cycle/phase_component.html.erb index c198e9f52bf..9532a793670 100644 --- a/app/components/projects/settings/life_cycle_steps/step_component.html.erb +++ b/app/components/projects/settings/life_cycle/phase_component.html.erb @@ -7,7 +7,7 @@ render( Projects::PhaseDefinitionComponent.new( definition, - edit_link: User.current.admin?, + edit_link: definition_editable?, phase_text_options: { classes: "filter-target-visible-text" } ) ) diff --git a/app/components/projects/settings/life_cycle_steps/step_component.rb b/app/components/projects/settings/life_cycle/phase_component.rb similarity index 87% rename from app/components/projects/settings/life_cycle_steps/step_component.rb rename to app/components/projects/settings/life_cycle/phase_component.rb index 2dd0f59b647..3d00283072b 100644 --- a/app/components/projects/settings/life_cycle_steps/step_component.rb +++ b/app/components/projects/settings/life_cycle/phase_component.rb @@ -28,11 +28,12 @@ module Projects module Settings - module LifeCycleSteps - class StepComponent < ApplicationComponent + module LifeCycle + class PhaseComponent < ApplicationComponent include ApplicationHelper include OpPrimer::ComponentHelpers include OpTurbo::Streamable + include Projects::LifeCycleDefinitionHelper options :definition, :active? @@ -40,6 +41,10 @@ module Projects def toggle_aria_label I18n.t("projects.settings.life_cycle.step.use_in_project", step: definition.name) end + + def definition_editable? + User.current.admin? && allowed_to_customize_life_cycle? + end end end end diff --git a/app/components/settings/project_life_cycle_step_definitions/index_component.html.erb b/app/components/settings/project_life_cycle_step_definitions/index_component.html.erb index e6a8145822c..93bfb3498fb 100644 --- a/app/components/settings/project_life_cycle_step_definitions/index_component.html.erb +++ b/app/components/settings/project_life_cycle_step_definitions/index_component.html.erb @@ -31,7 +31,14 @@ See COPYRIGHT and LICENSE files for more details. component_wrapper do flex_layout(data: wrapper_data_attributes) do |flex| flex.with_row do - if allowed_to_customize_life_cycle? + render EnterpriseEdition::BannerComponent.new(:customize_life_cycle, + variant: :medium, + image: "enterprise/project-lifecycle.png", + mb: 3) + end + + if allowed_to_customize_life_cycle? + flex.with_row do render(Primer::OpenProject::SubHeader.new) do |subheader| subheader.with_filter_input( name: "border-box-filter", @@ -57,11 +64,6 @@ See COPYRIGHT and LICENSE files for more details. I18n.t("settings.project_phase_definitions.label_add") end end - else - render EnterpriseEdition::BannerComponent.new(:customize_life_cycle, - variant: :medium, - image: "enterprise/project-lifecycle.png", - mb: 3) end end diff --git a/app/components/shares/modal_body_component.html.erb b/app/components/shares/modal_body_component.html.erb index 3bc42afca95..7612091818c 100644 --- a/app/components/shares/modal_body_component.html.erb +++ b/app/components/shares/modal_body_component.html.erb @@ -7,7 +7,8 @@ end end - render(strategy.manage_shares_component(modal_content:, errors:)) + render(strategy.upsell_banner(modal_content:)) + render(strategy.manage_shares_component(modal_content:, errors:)) if strategy.allow_feature? end end %> diff --git a/app/components/shares/work_packages/modal_upsell_component.html.erb b/app/components/shares/work_packages/modal_upsell_component.html.erb index b283aba4e06..f10285902d1 100644 --- a/app/components/shares/work_packages/modal_upsell_component.html.erb +++ b/app/components/shares/work_packages/modal_upsell_component.html.erb @@ -1,5 +1,7 @@ <%= component_wrapper(tag: "turbo-frame") do - render(EnterpriseEdition::BannerComponent.new(:work_package_sharing)) + modal_content.with_row do + render(EnterpriseEdition::BannerComponent.new(:work_package_sharing)) + end end %> diff --git a/app/components/shares/work_packages/modal_upsell_component.rb b/app/components/shares/work_packages/modal_upsell_component.rb index 3b9efbb5a81..a26e98f422c 100644 --- a/app/components/shares/work_packages/modal_upsell_component.rb +++ b/app/components/shares/work_packages/modal_upsell_component.rb @@ -33,6 +33,14 @@ module Shares include OpTurbo::Streamable include OpPrimer::ComponentHelpers + def initialize(modal_content:) + super + + @modal_content = modal_content + end + + attr_reader :modal_content + def self.wrapper_key "share_modal_body" end diff --git a/app/components/work_packages/reminder/modal_body_component.rb b/app/components/work_packages/reminder/modal_body_component.rb index b723e462197..1b06316f337 100644 --- a/app/components/work_packages/reminder/modal_body_component.rb +++ b/app/components/work_packages/reminder/modal_body_component.rb @@ -36,15 +36,17 @@ module WorkPackages include OpPrimer::ComponentHelpers FORM_ID = "reminder-form" + DEFAULT_TIME = "09:00" - attr_reader :remindable, :reminder, :errors + attr_reader :remindable, :reminder, :errors, :preset - def initialize(remindable:, reminder:, errors: nil) + def initialize(remindable:, reminder:, errors: nil, preset: nil) super @remindable = remindable @reminder = reminder @errors = errors + @preset = preset end class << self @@ -55,9 +57,9 @@ module WorkPackages def submit_path if @reminder.persisted? - work_package_reminder_path(@remindable, @reminder) + url_helpers.work_package_reminder_path(@remindable, @reminder) else - work_package_reminders_path(@remindable) + url_helpers.work_package_reminders_path(@remindable) end end @@ -82,11 +84,32 @@ module WorkPackages end def remind_at_date_initial_value - format_time_as_date(@reminder.remind_at, format: "%Y-%m-%d") + return time_as_date(@reminder.remind_at) if @reminder.remind_at + return calculate_preset_date if @preset + + nil end def remind_at_time_initial_value - format_time(@reminder.remind_at, include_date: false, format: "%H:%M") + return format_time(@reminder.remind_at, include_date: false, format: "%H:%M") if @reminder.remind_at + + DEFAULT_TIME + end + + private + + def calculate_preset_date + case @preset + when "tomorrow" then time_as_date(1.day.from_now) + when "three_days" then time_as_date(3.days.from_now) + when "week" then time_as_date(7.days.from_now) + when "month" then time_as_date(1.month.from_now) + when "custom" then nil + end + end + + def time_as_date(time) + format_time_as_date(time, format: "%Y-%m-%d") end end end diff --git a/app/contracts/project_life_cycle_steps/activation_contract.rb b/app/contracts/project_phases/activation_contract.rb similarity index 98% rename from app/contracts/project_life_cycle_steps/activation_contract.rb rename to app/contracts/project_phases/activation_contract.rb index b120150633a..c236b966b43 100644 --- a/app/contracts/project_life_cycle_steps/activation_contract.rb +++ b/app/contracts/project_phases/activation_contract.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module ProjectLifeCycleSteps +module ProjectPhases class ActivationContract < ::ModelContract alias_method :project, :model diff --git a/app/contracts/project_life_cycle_steps/base_contract.rb b/app/contracts/project_phases/base_contract.rb similarity index 98% rename from app/contracts/project_life_cycle_steps/base_contract.rb rename to app/contracts/project_phases/base_contract.rb index f2011f634f8..7edc9853497 100644 --- a/app/contracts/project_life_cycle_steps/base_contract.rb +++ b/app/contracts/project_phases/base_contract.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module ProjectLifeCycleSteps +module ProjectPhases class BaseContract < ::ModelContract validate :validate_edit_project_phases_permission diff --git a/app/contracts/project_phases/reschedule_contract.rb b/app/contracts/project_phases/reschedule_contract.rb new file mode 100644 index 00000000000..7e647eac38f --- /dev/null +++ b/app/contracts/project_phases/reschedule_contract.rb @@ -0,0 +1,41 @@ +# 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 ProjectPhases + class RescheduleContract < BaseContract + alias_method :project, :model + + protected + + def validate_model? + false + end + end +end diff --git a/app/contracts/project_life_cycle_steps/update_contract.rb b/app/contracts/project_phases/update_contract.rb similarity index 99% rename from app/contracts/project_life_cycle_steps/update_contract.rb rename to app/contracts/project_phases/update_contract.rb index f68d18552c0..cf98a6adb28 100644 --- a/app/contracts/project_life_cycle_steps/update_contract.rb +++ b/app/contracts/project_phases/update_contract.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module ProjectLifeCycleSteps +module ProjectPhases class UpdateContract < BaseContract validate :validate_start_after_preceeding_phases validate :validate_start_date_is_a_working_day diff --git a/app/contracts/queries/base_contract.rb b/app/contracts/queries/base_contract.rb index f1b79d81909..380077e95f1 100644 --- a/app/contracts/queries/base_contract.rb +++ b/app/contracts/queries/base_contract.rb @@ -69,7 +69,7 @@ module Queries end def project_visible? - Project.visible(user).exists?(id: project_id) + ::Project.visible(user).exists?(id: project_id) end def may_not_manage_queries? diff --git a/app/contracts/settings/working_days_and_hours_params_contract.rb b/app/contracts/settings/working_days_and_hours_params_contract.rb index e2abe706095..ad98dc35aae 100644 --- a/app/contracts/settings/working_days_and_hours_params_contract.rb +++ b/app/contracts/settings/working_days_and_hours_params_contract.rb @@ -66,8 +66,15 @@ module Settings end def unique_job - WorkPackages::ApplyWorkingDaysChangeJob.new.check_concurrency do - errors.add :base, :previous_working_day_changes_unprocessed + [ + Projects::Phases::ApplyWorkingDaysChangeJob, + WorkPackages::ApplyWorkingDaysChangeJob + ].each do |job_class| + job_class.new.check_concurrency do + errors.add :base, :previous_working_day_changes_unprocessed + end + + break if errors.added? :base, :previous_working_day_changes_unprocessed end end diff --git a/app/contracts/work_packages/copy_contract.rb b/app/contracts/work_packages/copy_contract.rb index 8b41516579d..da6de498dd7 100644 --- a/app/contracts/work_packages/copy_contract.rb +++ b/app/contracts/work_packages/copy_contract.rb @@ -37,10 +37,19 @@ module WorkPackages attribute :done_ratio, writable: true + # Use the default permission for the create contract, which is :add_work_packages. + attribute_permission :project_phase_definition_id, :add_work_packages + # Do not validate predecessors or children presence when copying: when # copying, it's possible to create a work package in automatic scheduling # mode even if it has no predecessors or children yet. They will be added # later in the process. def validate_has_predecessors_or_children; end + + # No validation happening on whether the phase is active in the project. + # When copying a work package, e.g. from a project template, the phase + # might not be active in the project yet. But when it is activated later, + # the value should then be present. + def validate_phase_active_in_project; end end end diff --git a/app/controllers/attribute_help_texts_controller.rb b/app/controllers/attribute_help_texts_controller.rb index cf5fb015493..d9f970788f3 100644 --- a/app/controllers/attribute_help_texts_controller.rb +++ b/app/controllers/attribute_help_texts_controller.rb @@ -27,17 +27,29 @@ #++ class AttributeHelpTextsController < ApplicationController + include OpTurbo::ComponentStream + include OpTurbo::DialogStreamHelper + layout "admin" menu_item :attribute_help_texts - before_action :authorize_global + before_action :authorize_global, except: :show_dialog before_action :find_entry, only: %i(edit update destroy) before_action :find_type_scope + authorization_checked! :show_dialog + def index @texts_by_type = AttributeHelpText.all_by_scope end + def show_dialog + @attribute_help_text = AttributeHelpText.visible(current_user).find(params[:id]) + respond_with_dialog( + AttributeHelpTexts::ShowDialogComponent.new(attribute_help_text: @attribute_help_text) + ) + end + def new @attribute_help_text = AttributeHelpText.new type: @attribute_scope end diff --git a/app/controllers/my/enterprise_banners_controller.rb b/app/controllers/my/enterprise_banners_controller.rb index e627b18ae77..c9cca555a87 100644 --- a/app/controllers/my/enterprise_banners_controller.rb +++ b/app/controllers/my/enterprise_banners_controller.rb @@ -47,9 +47,13 @@ class My::EnterpriseBannersController < ApplicationController def dismiss pref = User.current.pref - pref.dismiss_banner(@feature_key) + pref.dismiss_banner(@dismiss_key) if pref.save - remove_via_turbo_stream(component: EnterpriseEdition::BannerComponent.new(@feature_key)) + remove_via_turbo_stream(component: EnterpriseEdition::BannerComponent.new( + @feature_key, + dismiss_key: @dismiss_key, + show_always: true + )) respond_with_turbo_streams else respond_with_flash_error(message: call.message) @@ -59,7 +63,11 @@ class My::EnterpriseBannersController < ApplicationController private def get_feature_key - @feature_key = params[:feature_key].to_sym + raw_key = params[:feature_key] + + @dismiss_key = raw_key + @feature_key = raw_key.gsub(/_trial$/, "").to_sym + render_400 unless OpenProject::Token.lowest_plan_for(@feature_key) end end diff --git a/app/controllers/projects/settings/life_cycle_steps_controller.rb b/app/controllers/projects/settings/life_cycle_steps_controller.rb index 263866e74c6..55d2543f977 100644 --- a/app/controllers/projects/settings/life_cycle_steps_controller.rb +++ b/app/controllers/projects/settings/life_cycle_steps_controller.rb @@ -66,7 +66,7 @@ class Projects::Settings::LifeCycleStepsController < Projects::SettingsControlle end def set_steps_active_status(definitions, active:) - ::ProjectLifeCycleSteps::ActivationService + ::ProjectPhases::ActivationService .new(user: current_user, project: @project, definitions:) .call(active:) end diff --git a/app/controllers/work_packages/reminders_controller.rb b/app/controllers/work_packages/reminders_controller.rb index 43fe55baa37..2caec8c52fb 100644 --- a/app/controllers/work_packages/reminders_controller.rb +++ b/app/controllers/work_packages/reminders_controller.rb @@ -40,7 +40,8 @@ class WorkPackages::RemindersController < ApplicationController def modal_body render modal_component_class.new( remindable: @work_package, - reminder: @reminder + reminder: @reminder, + preset: params[:preset] ) end diff --git a/app/forms/application_form.rb b/app/forms/application_form.rb index 87a6882aa5e..a45ed39c78d 100644 --- a/app/forms/application_form.rb +++ b/app/forms/application_form.rb @@ -29,6 +29,8 @@ #++ class ApplicationForm < Primer::Forms::Base + include AttributeHelpTexts::FormHelper + def self.settings_form form do |f| f = Settings::FormDecorator.new(f) @@ -41,9 +43,7 @@ class ApplicationForm < Primer::Forms::Base end # @return [ActionView::Base] the view helper instance - def helpers - @view_context.helpers - end + delegate :helpers, to: :@view_context # @return [ActiveRecord::Base] the model instance given to the form builder def model diff --git a/app/forms/attribute_help_texts/form_helper.rb b/app/forms/attribute_help_texts/form_helper.rb new file mode 100644 index 00000000000..ced8acacdb7 --- /dev/null +++ b/app/forms/attribute_help_texts/form_helper.rb @@ -0,0 +1,42 @@ +# 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 AttributeHelpTexts + module FormHelper + def wrap_attribute_label_with_help_text(label, attribute) + render OpenProject::Common::AttributeLabelComponent.new( + model:, + attribute:, + tag: nil, + current_user: User.current + ).with_content(label) + end + end +end diff --git a/app/forms/projects/life_cycles/form.rb b/app/forms/projects/life_cycles/form.rb index 6487aada363..3c2f90c4a33 100644 --- a/app/forms/projects/life_cycles/form.rb +++ b/app/forms/projects/life_cycles/form.rb @@ -92,12 +92,17 @@ module Projects::LifeCycles end def autofocus?(field_name) - start_date_blank = model.start_date.blank? && model.default_start_date.blank? - case field_name - when :start_date - start_date_blank - when :finish_date - !start_date_blank && model.finish_date.blank? + # let javascipt handle focusing when rendering for preview + return false if model.changed? + + field_name == autofocus_field_name + end + + def autofocus_field_name + if start_date_disabled? || (model.start_date? && !model.finish_date?) + :finish_date + else + :start_date end end diff --git a/app/forms/statuses/form.rb b/app/forms/statuses/form.rb index 2948f1356aa..cac0631dde9 100644 --- a/app/forms/statuses/form.rb +++ b/app/forms/statuses/form.rb @@ -86,10 +86,8 @@ module Statuses } ) - if readonly_work_packages_restricted? - statuses_form.html_content do - render(EnterpriseEdition::BannerComponent.new(:readonly_work_packages)) - end + statuses_form.html_content do + render(EnterpriseEdition::BannerComponent.new(:readonly_work_packages)) end statuses_form.check_box( diff --git a/app/helpers/repositories_helper.rb b/app/helpers/repositories_helper.rb index 7a58fe9fe44..154e0588970 100644 --- a/app/helpers/repositories_helper.rb +++ b/app/helpers/repositories_helper.rb @@ -116,9 +116,9 @@ module RepositoriesHelper def render_changes_tree(tree) return "" if tree.nil? - output = "