Merge remote-tracking branch 'origin/dev' into epic/54751-meeting-agenda-items-backlog-for-recurring-meetings

This commit is contained in:
Oliver Günther
2025-04-28 20:49:09 +02:00
1848 changed files with 189869 additions and 10645 deletions
+5
View File
@@ -81,6 +81,11 @@ jobs:
- name: "Fix root key in Portuguese crowdin translation files"
run: |
script/i18n/fix_crowdin_pt_language_root_key
- name: "Fix storages.health.checks.failures.zero key in Arabic and Latvian crowdin translation files"
# Only run if the file exists
if: ${{ hashFiles('script/i18n/fix_crowdin_storages_health_checks_failures_zero_key') != '' }}
run: |
script/i18n/fix_crowdin_storages_health_checks_failures_zero_key
- name: "Commit translations"
env:
BRANCH: ${{ matrix.branch }}
+1 -1
View File
@@ -1,3 +1,3 @@
#Changelog
# Changelog
Notable changes to the project are documented in the release notes at https://www.openproject.org/open-source/release-notes/.
+9 -9
View File
@@ -4,7 +4,7 @@ We are pleased that you are thinking about contributing to OpenProject! This gui
## Get in touch
Please get in touch with us using our [development forum](https://community.openproject.org/projects/openproject/boards/7) or send us an email to info@openproject.com.
Please get in touch with us using our [development forum](https://community.openproject.org/projects/openproject/boards/7) or send us an email to [info@openproject.com](mailto:info@openproject.com).
## Issue tracking and coordination
@@ -31,31 +31,31 @@ For contributing source code, please follow the git workflow below:
- **Fork** OpenProject on GitHub
- Clone your fork to your development machine:
```
```bash
git clone git@github.com/<username>/openproject
```
- Optional: Add the original OpenProject repository as a remote, so you can fetch changes:
```
```bash
git remote add upstream git@github.com:opf/openproject
```
- Make sure you're on the right branch. The main development branch is `dev`:
```
```bash
git checkout dev
```
- Create a feature branch:
```
```bash
git checkout -b feature/<short description of your feature>
```
- Make your changes, then push the branch into your ***own*** repository:
```
```bash
git push origin <your feature branch>
```
@@ -83,7 +83,7 @@ More on this topic can be found in our [blog post](https://www.openproject.org/h
Please add tests to your code to verify functionality, especially if it is a new feature.
Pull requests will be verified by GitHub Actions as well,
Pull requests will be verified by GitHub Actions,
but please run them locally as well and make sure they are green before creating your pull request.
We have a lot of pull requests coming in and it takes some time to run the complete suite for each one.
@@ -134,9 +134,9 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor
by opening an issue or contacting one or more of the project maintainers.
This code of conduct is adapted from the
[Contributor Covenant](http:contributor-covenant.org),
[Contributor Covenant](https://www.contributor-covenant.org),
version 1.0.0, available at
[http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
[http://contributor-covenant.org/version/1/0/0/](https://www.contributor-covenant.org/version/1/0/0/code-of-conduct/)
## Contributors license agreement
+8 -8
View File
@@ -151,14 +151,14 @@ gem "gon", "~> 6.4.0"
gem "lograge", "~> 0.14.0"
# Structured warnings to selectively disable them in production
gem "structured_warnings", "~> 0.4.0"
gem "structured_warnings", "~> 0.5.0"
# catch exceptions and send them to any airbrake compatible backend
# don't require by default, instead load on-demand when actually configured
gem "airbrake", "~> 13.0.0", require: false
gem "markly", "~> 0.10" # another markdown parser like commonmarker, but with AST support used in PDF export
gem "md_to_pdf", git: "https://github.com/opf/md-to-pdf", ref: "66893683b94a5fe8f1dc9a60600773307209cdd2"
gem "markly", "~> 0.13" # another markdown parser like commonmarker, but with AST support used in PDF export
gem "md_to_pdf", git: "https://github.com/opf/md-to-pdf", ref: "67d14c6c7a13f918d158bde1a51ef1067a8cf724"
gem "prawn", "~> 2.4"
gem "ttfunk", "~> 1.7.0" # remove after https://github.com/prawnpdf/prawn/issues/1346 resolved.
@@ -191,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.18.1"
gem "nokogiri", "~> 1.18.8"
gem "carrierwave", "~> 1.3.4"
gem "carrierwave_direct", "~> 2.1.0"
@@ -201,7 +201,7 @@ gem "aws-sdk-core", "~> 3.107"
# File upload via fog + screenshots on travis
gem "aws-sdk-s3", "~> 1.91"
gem "openproject-token", "~> 5.2.0"
gem "openproject-token", "~> 5.3.0"
gem "plaintext", "~> 0.3.2"
@@ -410,6 +410,6 @@ gemfiles.each do |file|
send(:eval_gemfile, file) if File.readable?(file)
end
gem "openproject-octicons", "~>19.24.2"
gem "openproject-octicons_helper", "~>19.24.2"
gem "openproject-primer_view_components", "~>0.61.1"
gem "openproject-octicons", "~>19.25.0"
gem "openproject-octicons_helper", "~>19.25.0"
gem "openproject-primer_view_components", "~>0.63.0"
+98 -98
View File
@@ -19,16 +19,16 @@ GIT
GIT
remote: https://github.com/opf/md-to-pdf
revision: 66893683b94a5fe8f1dc9a60600773307209cdd2
ref: 66893683b94a5fe8f1dc9a60600773307209cdd2
revision: 67d14c6c7a13f918d158bde1a51ef1067a8cf724
ref: 67d14c6c7a13f918d158bde1a51ef1067a8cf724
specs:
md_to_pdf (0.2.0)
md_to_pdf (0.2.1)
base64 (~> 0.2)
bigdecimal (~> 3.1)
color_conversion (~> 0.1)
front_matter_parser (~> 1.0)
json-schema (~> 4.3)
markly (~> 0.10)
markly (~> 0.13)
matrix (~> 0.4)
nokogiri (~> 1.18)
prawn (~> 2.4)
@@ -352,8 +352,8 @@ GEM
awesome_nested_set (3.8.0)
activerecord (>= 4.0.0, < 8.1)
aws-eventstream (1.3.2)
aws-partitions (1.1082.0)
aws-sdk-core (3.222.1)
aws-partitions (1.1088.0)
aws-sdk-core (3.222.2)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -372,12 +372,12 @@ GEM
aws-sigv4 (~> 1.5)
aws-sigv4 (1.11.0)
aws-eventstream (~> 1, >= 1.0.2)
axe-core-api (4.10.2)
axe-core-api (4.10.3)
dumb_delegator
ostruct
virtus
axe-core-rspec (4.10.2)
axe-core-api (= 4.10.2)
axe-core-rspec (4.10.3)
axe-core-api (= 4.10.3)
dumb_delegator
ostruct
virtus
@@ -448,7 +448,7 @@ GEM
compare-xml (0.66)
nokogiri (~> 1.8)
concurrent-ruby (1.3.4)
connection_pool (2.5.0)
connection_pool (2.5.1)
cookiejar (0.3.4)
cose (1.3.1)
cbor (~> 0.5.9)
@@ -566,7 +566,7 @@ GEM
factory_bot_rails (6.4.4)
factory_bot (~> 6.5)
railties (>= 5.0.0)
faraday (2.12.2)
faraday (2.13.0)
faraday-net_http (>= 2.0, < 3.5)
json
logger
@@ -575,19 +575,19 @@ GEM
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
fastimage (2.3.1)
ffi (1.17.1)
ffi (1.17.1-aarch64-linux-gnu)
ffi (1.17.1-aarch64-linux-musl)
ffi (1.17.1-arm-linux-gnu)
ffi (1.17.1-arm-linux-musl)
ffi (1.17.1-arm64-darwin)
ffi (1.17.1-x86-linux-gnu)
ffi (1.17.1-x86-linux-musl)
ffi (1.17.1-x86_64-darwin)
ffi (1.17.1-x86_64-linux-gnu)
ffi (1.17.1-x86_64-linux-musl)
ffi (1.17.2)
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-arm-linux-gnu)
ffi (1.17.2-arm-linux-musl)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86-linux-gnu)
ffi (1.17.2-x86-linux-musl)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.2-x86_64-linux-musl)
flamegraph (0.9.5)
fog-aws (3.30.0)
fog-aws (3.31.0)
base64 (~> 0.2.0)
fog-core (~> 2.6)
fog-json (~> 1.1)
@@ -636,7 +636,7 @@ GEM
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
google-apis-gmail_v1 (0.41.0)
google-apis-gmail_v1 (0.42.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-env (2.2.2)
base64 (~> 0.2)
@@ -734,7 +734,7 @@ GEM
addressable (~> 2.8)
childprocess (~> 5.0)
logger (~> 1.6)
lefthook (1.11.10)
lefthook (1.11.11)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (3.0.0)
@@ -759,7 +759,7 @@ GEM
loofah (2.24.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
lookbook (2.3.8)
lookbook (2.3.9)
activemodel
css_parser
htmlbeautifier (~> 1.3)
@@ -786,7 +786,7 @@ GEM
mime-types (3.6.2)
logger
mime-types-data (~> 3.2015)
mime-types-data (3.2025.0402)
mime-types-data (3.2025.0415)
mini_magick (5.2.0)
benchmark
logger
@@ -813,24 +813,24 @@ GEM
net-smtp (0.5.1)
net-protocol
nio4r (2.7.4)
nokogiri (1.18.7)
nokogiri (1.18.8)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri (1.18.7-aarch64-linux-gnu)
nokogiri (1.18.8-aarch64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.7-aarch64-linux-musl)
nokogiri (1.18.8-aarch64-linux-musl)
racc (~> 1.4)
nokogiri (1.18.7-arm-linux-gnu)
nokogiri (1.18.8-arm-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.7-arm-linux-musl)
nokogiri (1.18.8-arm-linux-musl)
racc (~> 1.4)
nokogiri (1.18.7-arm64-darwin)
nokogiri (1.18.8-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.7-x86_64-darwin)
nokogiri (1.18.8-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.18.7-x86_64-linux-gnu)
nokogiri (1.18.8-x86_64-linux-gnu)
racc (~> 1.4)
nokogiri (1.18.7-x86_64-linux-musl)
nokogiri (1.18.8-x86_64-linux-musl)
racc (~> 1.4)
oj (3.16.10)
bigdecimal (>= 3.0)
@@ -853,17 +853,17 @@ GEM
validate_email
validate_url
webfinger (~> 2.0)
openproject-octicons (19.24.2)
openproject-octicons_helper (19.24.2)
openproject-octicons (19.25.0)
openproject-octicons_helper (19.25.0)
actionview
openproject-octicons (= 19.24.2)
openproject-octicons (= 19.25.0)
railties
openproject-primer_view_components (0.61.1)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
openproject-primer_view_components (0.63.0)
actionview (>= 7.1.0)
activesupport (>= 7.1.0)
openproject-octicons (>= 19.23.0)
view_component (>= 3.1, < 4.0)
openproject-token (5.2.0)
openproject-token (5.3.0)
activemodel
openssl (3.3.0)
openssl-signature_algorithm (1.3.0)
@@ -876,10 +876,10 @@ GEM
paper_trail (16.0.0)
activerecord (>= 6.1)
request_store (~> 1.4)
parallel (1.26.3)
parallel (1.27.0)
parallel_tests (4.10.1)
parallel
parser (3.3.7.4)
parser (3.3.8.0)
ast (~> 2.4.1)
racc
pdf-core (0.9.0)
@@ -1063,7 +1063,7 @@ GEM
rspec-support (3.13.2)
rspec-wait (1.0.1)
rspec (>= 3.4)
rubocop (1.75.2)
rubocop (1.75.3)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -1074,7 +1074,7 @@ GEM
rubocop-ast (>= 1.44.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.44.0)
rubocop-ast (1.44.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
@@ -1095,7 +1095,7 @@ GEM
rack (>= 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0)
rubocop-rspec (3.5.0)
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec_rails (2.31.0)
@@ -1166,7 +1166,7 @@ GEM
activerecord (>= 6.1)
stringex (2.8.6)
stringio (3.1.6)
structured_warnings (0.4.0)
structured_warnings (0.5.0)
svg-graph (2.2.2)
swd (2.0.3)
activesupport (>= 3)
@@ -1359,7 +1359,7 @@ DEPENDENCIES
lograge (~> 0.14.0)
lookbook (~> 2.3.4)
mail (= 2.8.1)
markly (~> 0.10)
markly (~> 0.13)
matrix (~> 0.4.2)
md_to_pdf!
meta-tags (~> 2.22.0)
@@ -1367,7 +1367,7 @@ DEPENDENCIES
multi_json (~> 1.15.0)
my_page!
net-ldap (~> 0.19.0)
nokogiri (~> 1.18.1)
nokogiri (~> 1.18.8)
oj (~> 3.16.0)
okcomputer (~> 1.19.0)
omniauth!
@@ -1388,15 +1388,15 @@ DEPENDENCIES
openproject-job_status!
openproject-ldap_groups!
openproject-meeting!
openproject-octicons (~> 19.24.2)
openproject-octicons_helper (~> 19.24.2)
openproject-octicons (~> 19.25.0)
openproject-octicons_helper (~> 19.25.0)
openproject-openid_connect!
openproject-primer_view_components (~> 0.61.1)
openproject-primer_view_components (~> 0.63.0)
openproject-recaptcha!
openproject-reporting!
openproject-storages!
openproject-team_planner!
openproject-token (~> 5.2.0)
openproject-token (~> 5.3.0)
openproject-two_factor_authentication!
openproject-webhooks!
openproject-xls_export!
@@ -1464,7 +1464,7 @@ DEPENDENCIES
stackprof
store_attribute (~> 2.0)
stringex (~> 2.8.5)
structured_warnings (~> 0.4.0)
structured_warnings (~> 0.5.0)
svg-graph (~> 2.2.0)
sys-filesystem (~> 1.5.0)
table_print (~> 1.5.6)
@@ -1519,14 +1519,14 @@ CHECKSUMS
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.1082.0) sha256=b77347af71e71cd457e227997e53635078b4fc8b14961ab606685a385dd7fa8c
aws-sdk-core (3.222.1) sha256=349f39840fca4300384bd6dc85a546a0ad9def81d48d1f99607a6b3ec27bcbb2
aws-partitions (1.1088.0) sha256=8535142a11dadebed7b87e68bb607031b972171472d7a4b55bfb409d2a22998a
aws-sdk-core (3.222.2) sha256=0639070595c6d123fc371d773a2a86f9fc208466ec88e9763d7af924a757c8d1
aws-sdk-kms (1.99.0) sha256=ba292fc3ffd672532aae2601fe55ff424eee78da8e23c23ba6ce4037138275a8
aws-sdk-s3 (1.183.0) sha256=8c06b0330c76fc57b4a04a94aec25a8474160b05b2436334d9f8e57ea2799f4c
aws-sdk-sns (1.97.0) sha256=e0489eb11cc107496738c2b39628842df970f4534bdc81a2254d2b49f7d0b7f6
aws-sigv4 (1.11.0) sha256=50a8796991a862324442036ad7a395920572b84bb6cd29b945e5e1800e8da1db
axe-core-api (4.10.2) sha256=c84ea0a041380a329f38fcfbc32b3759d35b481a27a368471e93afba5aafb289
axe-core-rspec (4.10.2) sha256=e302be5807e40eaa170f45559b2067791dfc42a58cb4400d2d6217321f12f775
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
@@ -1563,7 +1563,7 @@ CHECKSUMS
commonmarker (2.3.0-x86_64-linux-musl) sha256=af895be1dfe723a1ae2671fd147d082e1be107be732b7a52e2e0d5daa1ae687a
compare-xml (0.66) sha256=e21aa5c0f69ef1177eced997c688fd4df989084e74a1b612257af32e1dd05319
concurrent-ruby (1.3.4) sha256=d4aa926339b0a86b5b5054a0a8c580163e6f5dcbdfd0f4bb916b1a2570731c32
connection_pool (2.5.0) sha256=233b92f8d38e038c1349ccea65dd3772727d669d6d2e71f9897c8bf5cd53ebfc
connection_pool (2.5.1) sha256=ae802a90a4b5a081101b39d618e69921a9a50bea9ac3420a5b8c71f1befa3e9c
cookiejar (0.3.4) sha256=11b16acfc4baf7a0f463c21a6212005e04e25f5554d4d9f24d97f3492dfda0df
cose (1.3.1) sha256=d5d4dbcd6b035d513edc4e1ab9bc10e9ce13b4011c96e3d1b8fe5e6413fd6de5
costs (1.0.0)
@@ -1613,24 +1613,24 @@ CHECKSUMS
excon (1.2.5) sha256=ca040bb61bc0059968f34a17115a00d2db8562e3c0c5c5c7432072b551c85a9d
factory_bot (6.5.1) sha256=40581ea7bec0aee05514b8f4f99ed477274bdf1884c1372de5209e60322d6ca9
factory_bot_rails (6.4.4) sha256=139e17caa2c50f098fddf5e5e1f29e8067352024e91ca1186d018b36589e5c88
faraday (2.12.2) sha256=157339c25c7b8bcb739f5cf1207cb0cefe8fa1c65027266bcbc34c90c84b9ad6
faraday (2.13.0) sha256=f2697cd61a434dc446ee035f0370de654c2ad64707c4fc2541eb2338702e9614
faraday-follow_redirects (0.3.0) sha256=d92d975635e2c7fe525dd494fcd4b9bb7f0a4a0ec0d5f4c15c729530fdb807f9
faraday-net_http (3.4.0) sha256=a1f1e4cd6a2cf21599c8221595e27582d9936819977bbd4089a601f24c64e54a
fastimage (2.3.1) sha256=23c629f1f3e7d61bcfcc06c25b3d2418bc6bf41d2e615dbf5132c0e3b63ecce9
ferrum (0.15)
ffi (1.17.1) sha256=26f6b0dbd1101e6ffc09d3ca640b2a21840cc52731ad8a7ded9fb89e5fb0fc39
ffi (1.17.1-aarch64-linux-gnu) sha256=c5d22cb545a3a691d46060f1343c461d1a8d38c3fd71b96b4cbbe6906bf1fd38
ffi (1.17.1-aarch64-linux-musl) sha256=88b9d6ae905d21142df27c94bb300042c1aae41b67291885f600eaad16326b1d
ffi (1.17.1-arm-linux-gnu) sha256=fe14f5ece94082f3b0e651a09008113281f2764e7ea95f522b64e2fe32e11504
ffi (1.17.1-arm-linux-musl) sha256=df14927ca7bd9095148a7d1938bb762bbf189d190cf25d9547395ec7acc198a0
ffi (1.17.1-arm64-darwin) sha256=a8e04f79d375742c54ee7f9fff4b4022b87200a4ec0eb082128d3b6559e67b4d
ffi (1.17.1-x86-linux-gnu) sha256=01411c78cb3cff3c88cf67b2a7b24534e9b1638253d88581fef44c2083f6a174
ffi (1.17.1-x86-linux-musl) sha256=02bcc7bbcff71e021ef05f43469f7c5074ab3422e415b287001bd890c9cbb1c6
ffi (1.17.1-x86_64-darwin) sha256=0036199c290462dd7f03bc22933644c1685b7834a21788062bd5df48c72aa7a6
ffi (1.17.1-x86_64-linux-gnu) sha256=8c0ade2a5d19f3672bccfe3b58e016ae5f159e3e2e741c856db87fcf07c903d0
ffi (1.17.1-x86_64-linux-musl) sha256=3a343086820c96d6fbea4a5ef807fb69105b2b8174678f103b3db210c3f78401
ffi (1.17.2) sha256=297235842e5947cc3036ebe64077584bff583cd7a4e94e9a02fdec399ef46da6
ffi (1.17.2-aarch64-linux-gnu) sha256=c910bd3cae70b76690418cce4572b7f6c208d271f323d692a067d59116211a1a
ffi (1.17.2-aarch64-linux-musl) sha256=69e6556b091d45df83e6c3b19d3c54177c206910965155a6ec98de5e893c7b7c
ffi (1.17.2-arm-linux-gnu) sha256=d4a438f2b40224ae42ec72f293b3ebe0ba2159f7d1bd47f8417e6af2f68dbaa5
ffi (1.17.2-arm-linux-musl) sha256=977dfb7f3a6381206dbda9bc441d9e1f9366bf189a634559c3b7c182c497aaa3
ffi (1.17.2-arm64-darwin) sha256=54dd9789be1d30157782b8de42d8f887a3c3c345293b57ffb6b45b4d1165f813
ffi (1.17.2-x86-linux-gnu) sha256=95d8f9ebea23c39888e2ab85a02c98f54acb2f4e79b829250d7267ce741dc7b0
ffi (1.17.2-x86-linux-musl) sha256=41741449bab2b9530f42a47baa5c26263925306fad0ac2d60887f51af2e3b24c
ffi (1.17.2-x86_64-darwin) sha256=981f2d4e32ea03712beb26e55e972797c2c5a7b0257955d8667ba58f2da6440e
ffi (1.17.2-x86_64-linux-gnu) sha256=05d2026fc9dbb7cfd21a5934559f16293815b7ce0314846fee2ac8efbdb823ea
ffi (1.17.2-x86_64-linux-musl) sha256=97c0eb3981414309285a64dc4d466bd149e981c279a56371ef811395d68cb95c
flamegraph (0.9.5) sha256=a683020637ffa0e14a72640fa41babf14d926bfeaed87e31907cfd06ab2de8dc
fog-aws (3.30.0) sha256=f70b811b655fbfa2e7c59da9c3c0672af43436128cbee4bbf46ee6d78d9a5004
fog-aws (3.31.0) sha256=31f7854f102a5133562828c32c8587bdbf276576cadd2cd3aec1292eaf3f0405
fog-core (2.6.0) sha256=3fe08aa83a23cddce42f4ba412040c08f890d7ff04c175c0ee59119371245be6
fog-json (1.2.0) sha256=dd4f5ab362dbc72b687240bba9d2dd841d5dfe888a285797533f85c03ea548fe
fog-xml (0.1.5) sha256=52b9fea10701461dd3eaf9d9839702169b418dbbf50426786b9b74fade373bd6
@@ -1644,7 +1644,7 @@ CHECKSUMS
gon (6.4.0) sha256=e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0
good_job (3.99.1) sha256=7d3869d8a8ee8ef7048fee5d746f41c21987b7822c20038a2f773036bef0830a
google-apis-core (0.16.0) sha256=046a2c30a5ec123b2a6bc5e64348be781ce5fcd18dd4e85982e7a6a8da9d0dcc
google-apis-gmail_v1 (0.41.0) sha256=d09102619a571213e9637efb9ca4504c54d7779b524344045e24088eb2d0f8d0
google-apis-gmail_v1 (0.42.0) sha256=74b26b486fdb5f3212f54d1c24e98043be912079572729135473500c2f478cd5
google-cloud-env (2.2.2) sha256=94bed40e05a67e9468ce1cb38389fba9a90aa8fc62fc9e173204c1dca59e21e7
google-logging-utils (0.1.0) sha256=70950b1e49314273cf2e167adb47b62af7917a4691b580da7e9be67b9205fcd5
googleauth (1.14.0) sha256=62e7de11791890c3d3dc70582dfd9ab5516530e4e4f56d96451fd62c76475149
@@ -1685,7 +1685,7 @@ CHECKSUMS
ladle (1.0.1) sha256=e8586964108c798d48bf57d2a65bd5602e8e5223a176b6602a0fb36c0bda90dc
language_server-protocol (3.17.0.4) sha256=c484626478664fd13482d8180947c50a8590484b1258b99b7aedb3b69df89669
launchy (3.1.1) sha256=72b847b5cc961589dde2c395af0108c86ff0119f42d4648d25b5440ebb10059e
lefthook (1.11.10) sha256=bd99932e48e46469049a58734d46d6d94fceeb78d92edb3f0e0e29f5b269ad58
lefthook (1.11.11) sha256=8603255f118484e049979cd1ad549c4f151108083437f03004aa538d18362f16
letter_opener (1.10.0) sha256=2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2
letter_opener_web (3.0.0) sha256=3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
@@ -1694,17 +1694,17 @@ CHECKSUMS
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
lograge (0.14.0) sha256=42371a75823775f166f727639f5ddce73dd149452a55fc94b90c303213dc9ae1
loofah (2.24.0) sha256=61e6a710883abb8210887f3dc868cf3ed66594c509d9ff6987621efa6651ee1e
lookbook (2.3.8) sha256=8fe719b5f80a1f245074d94474fa16f2c102a388d93576f43ed0ba2244f1dc41
lookbook (2.3.9) sha256=b91fd92bd20281b87fbd23d7b22ffacb83ceb086c30986ab90275d220eaf0f39
mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad
marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4
markly (0.13.0) sha256=326a232c876cd95093d110b8d184be8effeadd776c3ffcefd59bdc8de7f59e0d
matrix (0.4.2) sha256=71083ccbd67a14a43bfa78d3e4dc0f4b503b9cc18e5b4b1d686dc0f9ef7c4cc0
md_to_pdf (0.2.0)
md_to_pdf (0.2.1)
messagebird-rest (1.4.2) sha256=1501e16ad76c87558f20f3b26cb39d05f587b349b44b8bf8056b040e9b495301
meta-tags (2.22.1) sha256=e5ae1febbd320d396c7226d7edb868e5d63466c14b9c8b06622a1a74e6dce354
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
mime-types (3.6.2) sha256=6109148e6a6e656607510b74571deff8ecd9a97ab0dcec9b7431bdd0b74460af
mime-types-data (3.2025.0402) sha256=455cc502e76bf4260d9f4fc8484c6324a9d765c4917a6672cb5a13f7818719c0
mime-types-data (3.2025.0415) sha256=82267683886373a95aa8b38d5de6ad3a4c2cca70d2ffa2aed07ec176eabb69bc
mini_magick (5.2.0) sha256=2757ffbfdb1d38242d1da9ff1505360ab75d59dc02eb7ab79ff6d5acb1243f4a
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
mini_portile2 (2.8.8) sha256=8e47136cdac04ce81750bb6c09733b37895bf06962554e4b4056d78168d70a75
@@ -1722,15 +1722,15 @@ CHECKSUMS
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
nio4r (2.7.4) sha256=d95dee68e0bb251b8ff90ac3423a511e3b784124e5db7ff5f4813a220ae73ca9
nokogiri (1.18.7) sha256=6b63ff5defe48f30d1d3b3122f65255ca91df2caf5378c6e0482ce73ff46fb31
nokogiri (1.18.7-aarch64-linux-gnu) sha256=57a064ab5440814a69a0e040817bd8154adea68a30d2ff2b3aa515a6a06dbb5f
nokogiri (1.18.7-aarch64-linux-musl) sha256=3e442dc5b69376e84288295fe37cbb890a21ad816a7e571e5e9967b3c1e30cd3
nokogiri (1.18.7-arm-linux-gnu) sha256=337d9149deb5ae01022dff7c90f97bed81715fd586aacab0c5809ef933994c5e
nokogiri (1.18.7-arm-linux-musl) sha256=97a26edcc975f780a0822aaf7f7d7427c561067c1c9ee56bd3542960f0c28a6e
nokogiri (1.18.7-arm64-darwin) sha256=083abb2e9ed2646860f6b481a981485a658c6064caafaa81bf1cda1bada2e9d5
nokogiri (1.18.7-x86_64-darwin) sha256=081d1aa517454ba3415304e2ea51fe411d6a3a809490d0c4aa42799cada417b7
nokogiri (1.18.7-x86_64-linux-gnu) sha256=3a0bf946eb2defde13d760f869b61bc8b0c18875afdd3cffa96543cfa3a18005
nokogiri (1.18.7-x86_64-linux-musl) sha256=9d83f8ec1fc37a305fa835d7ee61a4f37899e6ccc6dcb05be6645fa9797605af
nokogiri (1.18.8) sha256=8c7464875d9ca7f71080c24c0db7bcaa3940e8be3c6fc4bcebccf8b9a0016365
nokogiri (1.18.8-aarch64-linux-gnu) sha256=36badd2eb281fca6214a5188e24a34399b15d89730639a068d12931e2adc210e
nokogiri (1.18.8-aarch64-linux-musl) sha256=664e0f9a77a7122a66d6c03abba7641ca610769a4728db55ee1706a0838b78a2
nokogiri (1.18.8-arm-linux-gnu) sha256=17de01ca3adf9f8e187883ed73c672344d3dbb3c260f88ffa1008e8dc255a28e
nokogiri (1.18.8-arm-linux-musl) sha256=6e6d7e71fc39572bd613a82d528cf54392c3de1ba5ce974f05c832b8187a040b
nokogiri (1.18.8-arm64-darwin) sha256=483b5b9fb33653f6f05cbe00d09ea315f268f0e707cfc809aa39b62993008212
nokogiri (1.18.8-x86_64-darwin) sha256=024cdfe7d9ae3466bba6c06f348fb2a8395d9426b66a3c82f1961b907945cc0c
nokogiri (1.18.8-x86_64-linux-gnu) sha256=4a747875db873d18a2985ee2c320a6070c4a414ad629da625fbc58d1a20e5ecc
nokogiri (1.18.8-x86_64-linux-musl) sha256=ddd735fba49475a395b9ea793bb6474e3a3125b89960339604d08a5397de1165
oj (3.16.10) sha256=7f26bed974e331e16d579b470b0865010757f6fe6ee30ea9b67df653fbe13d7c
okcomputer (1.19.0) sha256=8548935a82f725bdd8f2c329925a9f1a1bb2ce19ce26b47d7515665ee363b458
omniauth (1.9.2)
@@ -1754,15 +1754,15 @@ CHECKSUMS
openproject-job_status (1.0.0)
openproject-ldap_groups (1.0.0)
openproject-meeting (1.0.0)
openproject-octicons (19.24.2) sha256=c4d63f4c16796c51e2a43cefae29e6042aa5cd418ea1bc11015add12e25c841e
openproject-octicons_helper (19.24.2) sha256=b7c8e7f4a22f60c8ac9ae70535e92a90121f28d58391aa21053bf751efc1c576
openproject-octicons (19.25.0) sha256=16fc221375e693f0e893b1c208286f2d7719ae4dfe080c5415642b221f51f550
openproject-octicons_helper (19.25.0) sha256=9b1778a67b0015ebe84ca0471f74e31004b985a8dcaaa443f7a2ac365b0a4e2d
openproject-openid_connect (1.0.0)
openproject-primer_view_components (0.61.1) sha256=c7a7c264fd125a32f6e8d8fadaf97f4ed1eee30d809f8caf7150f67ad4ed72b2
openproject-primer_view_components (0.63.0) sha256=fa9d8b4e2ffc968d908c487c687f0eb380c7d02de50876aae92ba10ce42c0c57
openproject-recaptcha (1.0.0)
openproject-reporting (1.0.0)
openproject-storages (1.0.0)
openproject-team_planner (1.0.0)
openproject-token (5.2.0) sha256=dd30482ea94897f24b7dc19d1f1cdd5a149e76635e29bf2ca34d31cf82f9d394
openproject-token (5.3.0) sha256=5fe2f33992e6bd8926ef56c44afd098ac868bd91b07f4abff5577c171663901a
openproject-two_factor_authentication (1.0.0)
openproject-webhooks (1.0.0)
openproject-xls_export (1.0.0)
@@ -1774,9 +1774,9 @@ CHECKSUMS
overviews (1.0.0)
ox (2.14.22) sha256=0bbc4a40d109e4b76295c4927b5f2d453070eb5af221e5b05ec0ff58e291c6a9
paper_trail (16.0.0) sha256=e9b9f0fb1b8b590c8231cfa931b282ba92f90e066e393930a5e1c61ae4c5019d
parallel (1.26.3) sha256=d86babb7a2b814be9f4b81587bf0b6ce2da7d45969fab24d8ae4bf2bb4d4c7ef
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70
parser (3.3.7.4) sha256=2b26282274280e13f891080dc4ef3f65ce658d62e13255b246b28ec6754e98ab
parser (3.3.8.0) sha256=2476364142b307fa5a1b1ece44f260728be23858a9c71078e956131a75453c45
pdf-core (0.9.0) sha256=4f368b2f12b57ec979872d4bf4bd1a67e8648e0c81ab89801431d2fc89f4e0bb
pdf-inspector (1.3.0) sha256=fc107579d6f29b636e2da3d6743479b2624d9e390bf2d84beef8fd4ebe1a05bd
pdf-reader (2.14.1) sha256=b45a4521c249a394ad7ad9e691bfd46d4d00998cfc4f019e4525afb4963b411b
@@ -1847,14 +1847,14 @@ CHECKSUMS
rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858
rspec-support (3.13.2) sha256=cea3a2463fd9b84b9dcc9685efd80ea701aa8f7b3decb3b3ce795ed67737dbec
rspec-wait (1.0.1) sha256=50ce9cc7cd20b8bb67b95a4ed56b59a16d4af7e86ae91b28dbf20eeaa2481d1f
rubocop (1.75.2) sha256=8efde647e278417e8074421b007e0d7d7c591482ef99d980528b18fea015a7c8
rubocop-ast (1.44.0) sha256=77fdaf4aacf0c7d1ef887b075686124fb4ae5743d42ff7e73bb1c0d6b61b5d0a
rubocop (1.75.3) sha256=1860a1b571fe4a15dff79d6683642ebf3ea4b3f7ae7903743ab0885b4686a1d5
rubocop-ast (1.44.1) sha256=e3cc04203b2ef04f6d6cf5f85fe6d643f442b18cc3b23e3ada0ce5b6521b8e92
rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c
rubocop-factory_bot (2.27.1) sha256=9d744b5916778c1848e5fe6777cc69855bd96548853554ec239ba9961b8573fe
rubocop-openproject (0.2.0) sha256=2300ed6b638a34a9368b458dc5fb618ae396da6a27670b50519ad1e3cea65ccb
rubocop-performance (1.25.0) sha256=6f7d03568a770054117a78d0a8e191cefeffb703b382871ca7743831b1a52ec1
rubocop-rails (2.31.0) sha256=79476e1075299c3e60fc50549c7c32614f9ebaae719b899ed75785c6786c52bd
rubocop-rspec (3.5.0) sha256=710c942fe1af884ba8eea75cbb8bdbb051929a2208880a6fc2e2dce1eed5304c
rubocop-rspec (3.6.0) sha256=c0e4205871776727e54dee9cc91af5fd74578001551ba40e1fe1a1ab4b404479
rubocop-rspec_rails (2.31.0) sha256=775375e18a26a1184a812ef3054b79d218e85601b9ae897f38f8be24dddf1f45
ruby-duration (3.2.3) sha256=eb3d13b1df85067a015a8fb2ed8f1eec842a3b721e47c9b6fd74d2f356069784
ruby-ole (1.2.13.1) sha256=578d10dd2a797a2b35a1286c6fb2c9525f67c24791346fc8015d39f0ffa3cb72
@@ -1888,7 +1888,7 @@ CHECKSUMS
store_attribute (2.0.0) sha256=3399d9baa20776ec9dcb266cff407aac82bf99791678d6266ea617703e4b0e28
stringex (2.8.6) sha256=c7b382d2b2a47a1e1646f256df201c48d487d6296fbb289d76802f67f5e929c4
stringio (3.1.6) sha256=292c495d1657adfcdf0a32eecf12a60e6691317a500c3112ad3b2e31068274f5
structured_warnings (0.4.0) sha256=ff9a59278e3ad8d18f742e677b764ce374f16e52c7993c0ff7d76900e93383ad
structured_warnings (0.5.0) sha256=864aca5a4d6b63cd3a948f9dcd7f5498af1cf42bcf4fd75e2c315b7d77a30583
svg-graph (2.2.2) sha256=f928866403055e6539afdfdab5f6268d108b2abc9f002e0fc51b16511809513a
swd (2.0.3) sha256=4cdbe2a4246c19f093fce22e967ec3ebdd4657d37673672e621bf0c7eb770655
sys-filesystem (1.5.3) sha256=17b561d1be683c34bc53946461ea9d67012d8f395e7297db8c63b9018cb30ece
Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

+1 -1
View File
@@ -28,5 +28,5 @@
@import "work_package_relations_tab/relation_component"
@import "users/hover_card_component"
@import "enterprise_edition/banner_component"
@import "enterprise_edition/upsale_page_component"
@import "enterprise_edition/upsell_page_component"
@import "work_packages/types/pattern_input"
@@ -32,7 +32,7 @@ module WorkPackages
module ActivitiesTab
module StimulusControllers
def index_stimulus_controller(suffix = nil) = "work-packages--activities-tab--index#{suffix}"
def restricted_comment_stimulus_controller(suffix = nil) = "work-packages--activities-tab--restricted-comment#{suffix}"
def internal_comment_stimulus_controller(suffix = nil) = "work-packages--activities-tab--internal-comment#{suffix}"
def quote_comments_stimulus_controller(suffix = nil) = "work-packages--activities-tab--quote-comment#{suffix}"
def items_index_selector = "##{WorkPackages::ActivitiesTab::IndexComponent.wrapper_key}"
@@ -2,20 +2,36 @@
component_wrapper(tag: "turbo-frame") do
grid_layout("op-enterprise-banner",
**@system_arguments) do |grid|
grid.with_area(:visual) do
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons", classes: "op-enterprise-banner--icon"))
if inline?
grid.with_area(:visual) do
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons", classes: "op-enterprise-banner--icon"))
end
end
grid.with_area(:content) do
flex_layout do |flex|
flex.with_row do
render(Primer::Beta::Text.new(font_weight: :bold)) { title }
if medium?
flex_layout(align_items: :center, mb: 3) do |title_flex|
title_flex.with_column do
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons",
size: :medium,
classes: "upsell-colored"))
end
title_flex.with_column(ml: 2) do
render(Primer::Beta::Heading.new(tag: :h2)) { title }
end
end
else
render(Primer::Beta::Text.new(font_weight: :bold)) { title }
end
end
flex.with_row(mt: 1) do
concat render(Primer::Beta::Text.new) { description }
concat " "
concat render(Primer::Beta::Text.new) { plan_text }
flex.with_row(classes: "op-enterprise-banner--description") do
concat render(Primer::Beta::Text.new(tag: :p)) { description }
concat render(Primer::Beta::Text.new(tag: :p)) { plan_text }
end
if features.present?
@@ -24,23 +40,31 @@
end
end
flex.with_row(mt: 2) do
render EnterpriseEdition::UpsaleButtonsComponent.new(feature_key)
flex.with_row(mt: 1) do
render EnterpriseEdition::UpsellButtonsComponent.new(feature_key)
end
end
end
if medium?
grid.with_area(:graphic) do
image_tag @image,
class: "op-enterprise-banner--image"
end
end
if @dismissable
grid.with_area(:dismiss) do
classes = medium? ? "op-enterprise-banner--close-icon_hovering" : ""
render(
Primer::Beta::IconButton.new(
classes: "op-enterprise-banner--close-icon",
classes: class_names("op-enterprise-banner--close-icon", classes),
scheme: :invisible,
tag: :a,
href: dismiss_enterprise_banner_path(feature_key: @dismiss_key),
data: { turbo_stream: true, turbo_method: :post },
icon: :x,
aria: { label: t("ee.upsale.hide_banner") }
aria: { label: t("ee.upsell.hide_banner") }
)
)
end
@@ -33,32 +33,66 @@ module EnterpriseEdition
# This component uses conventional names for translation keys or URL look-ups based on the feature_key passed in.
# It will only be rendered if necessary.
class BannerComponent < ApplicationComponent
include Primer::FetchOrFallbackHelper
include Primer::ClassNameHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
include PlanForFeature
DEFAULT_VARIANT = :inline
VARIANT_OPTIONS = %i[inline medium].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 image [String, NilClass] Path to the image to show on the banner, or nil.
# Required when variant is :medium.
# @param i18n_scope [String] Provide the i18n scope to look for title, description, and features.
# Defaults to "ee.upsale.{feature_key}"
# Defaults to "ee.upsell.{feature_key}"
# @param dismissable [boolean] Allow this banner to be dismissed.
# @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,
i18n_scope: "ee.upsale.#{feature_key}",
def initialize(feature_key, # rubocop:disable Metrics/AbcSize
variant: DEFAULT_VARIANT,
image: nil,
i18n_scope: "ee.upsell.#{feature_key}",
dismissable: false,
show_always: false,
dismiss_key: feature_key,
**system_arguments)
@variant = fetch_or_fallback(VARIANT_OPTIONS, variant, DEFAULT_VARIANT)
@image = image
@dismissable = dismissable
@dismiss_key = dismiss_key
@show_always = show_always
self.feature_key = feature_key
self.i18n_scope = i18n_scope
if @variant == :medium && @image.nil?
raise ArgumentError, "The 'image' parameter is required when the variant is :medium."
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"
super
@system_arguments[:classes] = class_names(
@system_arguments[:classes],
"op-enterprise-banner",
@variant == :medium ? "op-enterprise-banner_medium" : nil
)
self.feature_key = feature_key
self.i18n_scope = i18n_scope
@dismissable = dismissable
@dismiss_key = dismiss_key
super
end
def medium?
@variant == :medium
end
def inline?
@variant == :inline
end
def wrapper_key
@@ -68,6 +102,8 @@ module EnterpriseEdition
private
def render?
return true if @show_always
!(EnterpriseToken.hide_banners? || feature_available? || dismissed?)
end
@@ -28,6 +28,7 @@
/ ++
/
$op-enterprise-banner-fixed-height: 220px
.op-enterprise-banner
display: grid
@@ -40,17 +41,47 @@
background: var(--body-background)
padding: var(--base-size-8)
&_medium
padding: 0
grid-template-areas: "content graphic"
grid-template-columns: 1fr 1fr
height: $op-enterprise-banner-fixed-height
&_medium &
padding: 0
grid-template-areas: "content graphic"
grid-template-columns: 1fr 1fr
&--content
align-self: center
padding: var(--base-size-16)
&--graphic
height: $op-enterprise-banner-fixed-height
&--image
width: 100%
height: 100%
object-fit: cover
&--visual
display: grid
align-self: start
padding: var(--base-size-6) var(--base-size-8)
&--icon
color: var(--enterprise-upsale-color)
color: var(--enterprise-upsell-color)
margin-block: var(--base-size-2)
&--close-icon .Button-visual
color: var(--enterprise-upsale-color)
&--close-icon
.Button-visual
color: var(--enterprise-upsell-color)
&_hovering
position: absolute
top: var(--base-size-24)
right: var(--base-size-24)
&--content
padding: var(--base-size-6) var(--base-size-8)
@@ -74,11 +74,11 @@ module EnterpriseEdition
end
def plan_text
plan_name = render(Primer::Beta::Text.new(font_weight: :bold, classes: "upsale-colored")) do
I18n.t("ee.upsale.plan_name", plan:)
plan_name = render(Primer::Beta::Text.new(font_weight: :bold, classes: "upsell-colored")) do
I18n.t("ee.upsell.plan_name", plan:)
end
I18n.t("ee.upsale.plan_text_html", plan_name:).html_safe
I18n.t("ee.upsell.plan_text_html", plan_name:).html_safe
end
end
end
@@ -29,7 +29,7 @@
# ++
module EnterpriseEdition
class UpsaleButtonsComponent < ApplicationComponent
class UpsellButtonsComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
# @param feature_key [Symbol, NilClass] The key of the feature to show the banner for.
@@ -66,6 +66,7 @@ module EnterpriseEdition
def free_trial_button
return if EnterpriseToken.active?
return unless User.current.admin?
helpers.angular_component_tag("opce-free-trial-button")
end
@@ -83,7 +84,7 @@ module EnterpriseEdition
end
def link_title
I18n.t("ee.upsale.#{feature_key}.link_title", default: I18n.t("ee.upsale.link_title"))
I18n.t("ee.upsell.#{feature_key}.link_title", default: I18n.t(:label_more_information))
end
def enterprise_link
@@ -3,13 +3,13 @@
<% helpers.write_trial_key_to_gon if !EnterpriseToken.current.present? %>
<% end %>
<div class="op-enterprise-upsale-page" data-test-selector="op-enterprise-upsale-page">
<div class="op-enterprise-upsell-page" data-test-selector="op-enterprise-upsell-page">
<%=
render(Primer::OpenProject::FlexLayout.new(justify_content: :center, align_items: :center)) do |flex|
flex.with_column do
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons",
size: :medium,
classes: "upsale-colored"))
classes: "upsell-colored"))
end
flex.with_column(ml: 2) do
@@ -18,7 +18,7 @@
end
%>
<p class="upsale--feature-reference">
<p class="upsell--feature-reference">
<%= render(Primer::Beta::Text.new) { description } %>
<%= render(Primer::Beta::Text.new) { plan_text } %>
</p>
@@ -42,5 +42,5 @@
<%= image_tag "enterprise-add-on.svg", class: "widget-box--teaser-image widget-box--teaser-image_default" %>
<% end %>
<%= render EnterpriseEdition::UpsaleButtonsComponent.new(feature_key, justify_content: :center) %>
<%= render EnterpriseEdition::UpsellButtonsComponent.new(feature_key, justify_content: :center) %>
</div>
@@ -30,16 +30,16 @@
module EnterpriseEdition
# A full page banner for the enterprise edition
class UpsalePageComponent < ApplicationComponent
class UpsellPageComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include PlanForFeature
# @param feature_key [Symbol, NilClass] The key of the feature to show the upsale page for.
# @param feature_key [Symbol, NilClass] The key of the feature to show the upsell page for.
# @param i18n_scope [String] Provide the i18n scope to look for title, description, and features.
# Defaults to "ee.upsale.{feature_key}"
# @param image [String, NilClass] Path to the image to show on the upsale page, or nil.
# @param video [String, NilClass] Path to the video to show on the upsale page, or nil.
def initialize(feature_key, i18n_scope: "ee.upsale.#{feature_key}", image: nil, video: nil)
# Defaults to "ee.upsell.{feature_key}"
# @param image [String, NilClass] Path to the image to show on the upsell page, or nil.
# @param video [String, NilClass] Path to the video to show on the upsell page, or nil.
def initialize(feature_key, i18n_scope: "ee.upsell.#{feature_key}", image: nil, video: nil)
super
self.feature_key = feature_key
@@ -27,7 +27,7 @@
/ See COPYRIGHT and LICENSE files for more details.
/ ++
/
.op-enterprise-upsale-page
.op-enterprise-upsell-page
max-width: 50vw
margin: auto
padding-top: 20px
@@ -30,6 +30,7 @@ See COPYRIGHT and LICENSE files for more details.
<%=
render(
border_box_container(
id: container_id,
classes: container_class,
test_selector:
)
@@ -55,10 +56,10 @@ See COPYRIGHT and LICENSE files for more details.
if has_actions?
concat render(
Primer::BaseComponent.new(
classes: heading_class,
tag: :div
classes: header_classes(:actions),
tag: :span
)
)
) { action_row_header_content }
end
end
@@ -138,6 +138,10 @@ module OpPrimer
nil
end
def action_row_header_content
nil
end
def footer
raise ArgumentError, "Need to provide footer content"
end
@@ -23,6 +23,11 @@
&:not(:last-child)
padding-right: 6px
&:last-child
justify-content: flex-end !important
display: flex !important
flex-direction: row !important
&--mobile-heading,
&--row-label
display: none
+18
View File
@@ -55,6 +55,15 @@ module OpPrimer
# @param blk [Proc] A block that defines the form structure.
def render_inline_form(form_builder, &blk)
form_class = Class.new(ApplicationForm) do
# This is a workaround to make the form class aware of the template path
# of the block that is passed to it.
#
# This avoids an annoying warning "Could not identify the template" from
# original `base_template_path` method.
define_singleton_method(:base_template_path) do
blk.source_location.first
end
form(&blk)
end
render(form_class.new(form_builder))
@@ -89,6 +98,15 @@ module OpPrimer
# @param blk [Proc] A block that defines the form structure.
def render_inline_settings_form(form_builder, &blk)
form_class = Class.new(ApplicationForm) do
# This is a workaround to make the form class aware of the template path
# of the block that is passed to it.
#
# This avoids an annoying warning "Could not identify the template" from
# original `base_template_path` method.
define_singleton_method(:base_template_path) do
blk.source_location.first
end
settings_form(&blk)
end
render(form_class.new(form_builder))
@@ -42,7 +42,7 @@
<%= menu_item.title %>
<% if menu_item.show_enterprise_icon %>
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsale-colored", ml: 2) %>
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsell-colored", ml: 2) %>
<% end %>
</span>
@@ -82,7 +82,7 @@
<%= child_item.title %>
<% if child_item.show_enterprise_icon %>
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsale-colored", ml: 2) %>
<%= render Primer::Beta::Octicon.new(icon: "op-enterprise-addons", "aria-label": I18n.t(:label_enterprise_edition), classes: "upsell-colored", ml: 2) %>
<% end %>
<% if child_item.favored %>
@@ -73,7 +73,7 @@
anchor_align: :end
},
button_arguments: {
icon: "op-kebab-vertical",
icon: "kebab-horizontal",
"aria-label": t(:label_more),
data: { "test-selector": "project-more-dropdown-menu" }
}
@@ -0,0 +1,87 @@
<%#-- 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.
++#%>
<%=
flex_layout do |container|
container.with_row(mb: 3) do
admin_settings_primer_form_with(model: project, url: project_settings_general_path(project)) do |f|
concat(
render(Primer::Beta::Subhead.new) do |component|
component.with_heading(tag: :h2, size: :medium) { I18n.t("projects.settings.header_details") }
end
)
concat(
render(
Primer::Forms::FormList.new(
Projects::Settings::NameForm.new(f),
Projects::Settings::DescriptionForm.new(f),
Projects::Settings::Submit.new(f, label: I18n.t("projects.settings.button_update_details"))
)
)
)
end
end
container.with_row(mb: 3) do
admin_settings_primer_form_with(model: project, url: project_settings_general_path(project)) do |f|
concat(
render(Primer::Beta::Subhead.new) do |component|
component.with_heading(tag: :h2, size: :medium) { I18n.t("projects.settings.header_status") }
end
)
concat(
render(
Primer::Forms::FormList.new(
Projects::Settings::StatusForm.new(f),
Projects::Settings::Submit.new(f, label: I18n.t("projects.settings.button_update_status"))
)
)
)
end
end
container.with_row(mb: 3) do
admin_settings_primer_form_with(model: project, url: project_settings_general_path(project)) do |f|
concat(
render(Primer::Beta::Subhead.new) do |component|
component.with_heading(tag: :h2, size: :medium) { I18n.t("projects.settings.header_relations") }
end
)
concat(
render(
Primer::Forms::FormList.new(
Projects::Settings::RelationsForm.new(f),
Projects::Settings::Submit.new(f, label: I18n.t("projects.settings.button_update_parent_project"))
)
)
)
end
end
end
%>
@@ -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
module Settings
module General
class ShowComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
options :project
end
end
end
end
@@ -42,7 +42,7 @@
anchor_align: :end
},
button_arguments: {
icon: "op-kebab-vertical",
icon: "kebab-horizontal",
"aria-label": t(:label_more),
test_selector: "project-settings-more-menu"
}
@@ -61,6 +61,20 @@
end
end
if User.current.allowed_in_project?(:edit_project, @project)
menu.with_item(
tag: :a,
label: public? ? t(:button_unpublish) : t(:button_publish),
href: toggle_public_dialog_project_settings_general_path(@project),
content_arguments: {
data: { turbo_stream: true },
test_selector: "project-settings--toggle-public"
}
) do |item|
item.with_leading_visual_icon(icon: public? ? :lock : :unlock)
end
end
if User.current.allowed_in_project?(:archive_project, @project)
menu.with_item(
tag: :a,
@@ -74,6 +88,7 @@
item.with_leading_visual_icon(icon: "lock")
end
end
if User.current.admin?
label = @project.templated ? "remove_from_templates" : "make_template"
menu.with_item(
@@ -36,4 +36,8 @@ class Projects::Settings::IndexPageHeaderComponent < ApplicationComponent
@project = project
end
attr_reader :project
delegate :public?, to: :project
end
@@ -27,10 +27,23 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= toolbar title: t(:label_version) %>
<% html_title t(:label_version) %>
<%= labelled_tabular_form_with model: @version, url: project_version_path(@project, @version), html: { method: :put } do |f| %>
<%= render partial: "form", locals: { f: } %>
<%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %>
<% end %>
<%=
render(
Primer::OpenProject::DangerDialog.new(
id: "projects_toggle_public_confirmation",
title: t(:title, scope: i18n_scope),
confirm_button_text: t(:'js.button_confirm'),
form_arguments: {
action: toggle_public_project_settings_general_path(@project),
method: :post,
data: { turbo: true }
}
)
) do |dialog|
dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { t(:title, scope: i18n_scope) }
message.with_description_content(t(:description, scope: i18n_scope))
end
dialog.with_confirmation_check_box_content(t(:checkbox, scope: i18n_scope))
end
%>
@@ -0,0 +1,51 @@
# 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 Projects::Settings
class TogglePublicDialogComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
def initialize(project, back_url: nil)
super
@project = project
@back_url = back_url
end
def i18n_scope
if @project.public?
"projects.settings.private_confirmation"
else
"projects.settings.public_confirmation"
end
end
end
end
@@ -47,7 +47,7 @@ See COPYRIGHT and LICENSE files for more details.
end
end
tab_nav.with_tab(selected: selected?(:activities), href: project_settings_work_packages_activities_path) do |tab|
tab.with_text { t("label_activity") }
tab.with_text { activity_title }
end
end
end
@@ -58,6 +58,14 @@ module Settings
def show_custom_fields?
User.current.allowed_in_project?(:select_custom_fields, @project)
end
def activity_title
label = t("label_activity").html_safe
unless EnterpriseToken.allows_to?(:internal_comments)
label << render(Primer::Beta::Octicon.new(icon: "op-enterprise-addons", classes: "upsell-colored", ml: 2))
end
label
end
end
end
end
@@ -1,10 +1,10 @@
<%=
modal_content.with_row(data: { "test-selector": "op-share-dialog-upsale-block" }) do
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsale-colored" }, border: true) do |component|
component.with_heading(tag: :h2, classes: "upsale-colored").with_content(I18n.t(:"ee.upsale.title"))
component.with_description { I18n.t("sharing.project_queries.upsale.message") }
modal_content.with_row(data: { "test-selector": "op-share-dialog-upsell-block" }) do
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsell-colored" }, border: true) do |component|
component.with_heading(tag: :h2, classes: "upsell-colored").with_content(I18n.t(:"ee.upsell.title"))
component.with_description { I18n.t("sharing.project_queries.upsell.message") }
href = "#{OpenProject::Static::Links.links[:upsale][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=project-list-sharing-modal"
href = "#{OpenProject::Static::Links.links[:upsell][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=project-list-sharing-modal"
component.with_secondary_action(href:) do |link|
link.with_trailing_visual_icon(icon: "link-external")
I18n.t("admin.enterprise.enterprise_link")
@@ -28,7 +28,7 @@
module Shares
module ProjectQueries
class UpsaleComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
class UpsellComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
include OpPrimer::ComponentHelpers
def initialize(modal_content:)
@@ -1,10 +1,10 @@
<%=
component_wrapper(tag: "turbo-frame") do
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsale-colored" }, border: true) do |component|
component.with_heading(tag: :h2, classes: "upsale-colored").with_content(I18n.t(:"ee.upsale.title"))
render Primer::OpenProject::FeedbackMessage.new(icon_arguments: { icon: :"op-enterprise-addons", classes: "upsell-colored" }, border: true) do |component|
component.with_heading(tag: :h2, classes: "upsell-colored").with_content(I18n.t(:"ee.upsell.title"))
component.with_description { I18n.t("mail.sharing.work_packages.enterprise_text") }
href = "#{OpenProject::Static::Links.links[:upsale][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=work-package-sharing-modal"
href = "#{OpenProject::Static::Links.links[:upsell][:href]}/?utm_source=unknown&utm_medium=community-edition&utm_campaign=work-package-sharing-modal"
component.with_secondary_action(href:) do |link|
link.with_trailing_visual_icon(icon: "link-external")
I18n.t("admin.enterprise.enterprise_link")
@@ -28,7 +28,7 @@
module Shares
module WorkPackages
class ModalUpsaleComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
class ModalUpsellComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
+1 -1
View File
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<div class="generic-table--container <%= container_class %>" data-test-selector="<%= test_selector %>">
<div class="generic-table--container <%= container_class %>" data-test-selector="<%= test_selector %>" id="<%= container_id %>">
<div class="generic-table--results-container">
<table class="generic-table" data-controller="table-highlighting">
<colgroup>
+14 -8
View File
@@ -31,8 +31,8 @@
##
# Abstract view component. Subclass this for a concrete table.
class TableComponent < ApplicationComponent
def initialize(rows: [], **options)
super(rows, **options)
def initialize(rows: [], **)
super(rows, **)
end
class << self
@@ -107,7 +107,7 @@ class TableComponent < ApplicationComponent
end
def sortable_columns_correlation
sortable_columns.to_h { [_1.to_s, _1.to_s] }
sortable_columns.to_h { [it.to_s, it.to_s] }
.with_indifferent_access
end
@@ -153,20 +153,22 @@ class TableComponent < ApplicationComponent
model
end
def row_class
self.class.row_class
end
delegate :row_class, to: :class
def container_class
nil
end
def container_id
nil
end
def columns
self.class.columns
self.class.columns.reject { skip_column?(it) }
end
def sortable_columns
self.class.sortable_columns
self.class.sortable_columns.reject { skip_column?(it) }
end
def render_collection(rows)
@@ -193,6 +195,10 @@ class TableComponent < ApplicationComponent
true
end
def skip_column?(_column)
false
end
def sortable_column?(column)
sortable? && sortable_columns.include?(column.to_sym)
end
@@ -39,7 +39,7 @@ See COPYRIGHT and LICENSE files for more details.
feature = tab[:enterprise_feature]
if feature && !EnterpriseToken.allows_to?(feature)
t.with_icon(icon: :"op-enterprise-addons", classes: "upsale-colored")
t.with_icon(icon: :"op-enterprise-addons", classes: "upsell-colored")
end
t.with_text { I18n.t(tab[:label]) }
end
@@ -0,0 +1,196 @@
<%#-- 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::OpenProject::PageHeader.new do |header|
header.with_title(test_selector: "wiki-page-header-title") { h(@page.title) }
header.with_breadcrumbs(
helpers.breadcrumb_for_page(@project, @page),
data: { "test-selector": "wiki-page-header-breadcrumbs" }
)
if @editable
if show_edit?
header.with_action_button(
tag: :a,
mobile_icon: :pencil,
mobile_label: t(:button_edit),
size: :medium,
href: url_for(controller: "wiki", action: "edit", id: @page),
aria: { label: I18n.t(:button_edit) },
data: { "test-selector": "wiki-edit-action-button" },
title: I18n.t(:button_edit)
) do |button|
button.with_leading_visual_icon(icon: :pencil)
t(:button_edit)
end
end
helpers.watcher_action_button(header, @page.object)
unless @page.current_version?
header.with_action_button(
tag: :a,
mobile_icon: :book,
mobile_label: t(:label_history),
size: :medium,
href: url_for(controller: "wiki", action: "history", id: @page),
aria: { label: t(:label_history) },
title: t(:label_history)
) do |button|
button.with_leading_visual_icon(icon: "book")
t(:label_history)
end
end
end
header.with_action_menu(
menu_arguments: { anchor_align: :end },
button_arguments: {
icon: "kebab-horizontal",
"aria-label": t(:label_more),
data: { "test-selector": "wiki-more-dropdown-menu" }
}
) do |menu|
if @editable
if User.current.allowed_in_project?(:protect_wiki_pages, @project)
menu.with_item(
label: lock_data[:label],
tag: :a,
size: :medium,
content_arguments: { data: { method: :post } },
href: url_for(controller: "wiki", action: "protect", id: @page, protected: lock_data[:protected])
) do |item|
item.with_leading_visual_icon(icon: lock_data[:icon])
end
end
if @page.current_version?
if User.current.allowed_in_project?(:rename_wiki_pages, @project)
menu.with_item(
label: t(:button_rename),
tag: :a,
size: :medium,
data: { "test-selector": "wiki-rename-action-menu-item" },
href: url_for(controller: "wiki", action: "rename", id: @page)
) do |item|
item.with_leading_visual_icon(icon: "arrow-switch")
end
end
if User.current.allowed_in_project?(:change_wiki_parent_page, @project)
menu.with_item(
label: t(:button_change_parent_page),
tag: :a,
size: :medium,
href: url_for(controller: "wiki", action: "edit_parent_page", id: @page)
) do |item|
item.with_leading_visual_icon(icon: :link)
end
end
end
if User.current.allowed_in_project?(:delete_wiki_pages, @project)
menu.with_item(
label: t(:button_delete),
scheme: :danger,
tag: :a,
size: :medium,
content_arguments: { data: { confirm: t(:text_are_you_sure), method: :delete } },
href: url_for(controller: "wiki", action: "destroy", id: @page),
data: { "test-selector": "wiki-delete-action-menu-item" }
) do |item|
item.with_leading_visual_icon(icon: :trash)
end
end
if show_rollback?
menu.with_item(
label: t(:button_rollback),
href: url_for(controller: "wiki", action: "edit", id: @page, version: @page.version)
) do |item|
item.with_leading_visual_icon(icon: :x)
end
end
if User.current.allowed_in_project?(:view_wiki_edits, @project)
menu.with_item(
label: t(:label_history),
href: url_for(controller: "wiki", action: "history", id: @page),
data: { "test-selector": "wiki-history-action-menu-item" }
) do |item|
item.with_leading_visual_icon(icon: :history)
end
end
if User.current.allowed_in_project?(:manage_wiki_menu, @project)
menu.with_item(
label: t(:button_manage_menu_entry),
href: url_for(controller: "/wiki_menu_items", action: "edit", project_id: @project.identifier, id: @page),
data: { "test-selector": "wiki-configure-menu-action-menu-item" }
) do |item|
item.with_leading_visual_icon(icon: :gear)
end
end
menu.with_item(
tag: :a,
label: t(:button_print),
content_arguments: { data: {
controller: "print",
action: "print#triggerPrint"
} },
title: t("wiki.print_hint"),
href: ""
) do |item|
item.with_leading_visual_icon(icon: "op-printer")
end
if User.current.allowed_in_project?(:export_wiki_pages, @project)
menu.with_item(
tag: :a,
label: t("js.label_export"),
size: :medium,
href: "",
title: t("js.label_export"),
classes: "modal-delivery-element--activation-link"
) do |item|
item.with_leading_visual_icon(icon: :download)
end
end
menu.with_item(
tag: :a,
label: t(:label_table_of_contents),
title: t("wiki.print_hint"),
href: url_for({ controller: "/wiki", action: "index", project_id: @project.identifier, id: @page })
) do |item|
item.with_leading_visual_icon(icon: "op-view-list")
end
end
end
end
%>
<section data-augmented-model-wrapper
data-modal-class-name="wiki-export---modal">
<%= link_to t("js.label_export"),
"",
title: t("js.label_export"),
style: "display: none;" %>
<%= render partial: "wiki/wiki_export_modal" %>
</section>
@@ -0,0 +1,59 @@
# 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 WikiPages
class ShowPageHeaderComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include ApplicationHelper
def initialize(page:, project:, editable:)
super
@page = page
@project = project
@editable = editable
end
def lock_data
if @page.protected?
{ label: t(:button_unlock), icon: :unlock, protected: 0 }
else
{ label: t(:button_lock), icon: :lock, protected: 1 }
end
end
def show_edit?
User.current.allowed_in_project?(:edit_wiki_pages, @project) && @page.current_version?
end
def show_rollback?
User.current.allowed_in_project?(:edit_wiki_pages, @project) && !@page.current_version?
end
end
end
@@ -27,8 +27,18 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= error_messages_for "version" %>
<% if @project.enabled_modules.map(&:name).include?("backlogs") %>
<%= version_settings_fields @version, @project %>
<% end %>
<%=
render(Primer::OpenProject::SubHeader.new) do |subheader|
subheader.with_action_button(
scheme: :primary,
aria: { label: t("wiki.index.no_results_content_text") },
title: t("wiki.index.no_results_content_text"),
tag: :a,
test_selector: "wiki-new-child-button",
href: url_for({ controller: "/wiki", action: "new_child", project_id: @project.identifier, id: @page })
) do |button|
button.with_leading_visual_icon(icon: :plus)
t(:create_wiki_page_button)
end
end
%>
@@ -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 WikiPages
class ShowSubHeaderComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include ApplicationHelper
def initialize(page:, project:)
super
@page = page
@project = project
end
end
end
@@ -9,8 +9,9 @@
d.with_header(variant: :large)
d.with_body(classes: body_classes) do
render(
WorkPackageRelationsTab::AddWorkPackageChildFormComponent.new(
work_package: @work_package
WorkPackageRelationsTab::AddWorkPackageHierarchyFormComponent.new(
work_package:,
relation_type:
)
)
end
@@ -28,28 +28,29 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class WorkPackageRelationsTab::AddWorkPackageChildDialogComponent < ApplicationComponent
class WorkPackageRelationsTab::AddWorkPackageHierarchyDialogComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
I18N_NAMESPACE = "work_package_relations_tab"
DIALOG_ID = "add-work-package-child-dialog"
FORM_ID = "add-work-package-child-form"
DIALOG_ID = "add-work-package-hierarchy-dialog"
FORM_ID = "add-work-package-hierarchy-form"
attr_reader :work_package
attr_reader :work_package, :relation_type
def initialize(work_package:)
def initialize(work_package:, relation_type:)
super()
@work_package = work_package
@relation_type = relation_type
end
private
def dialog_title
child_label = t("#{I18N_NAMESPACE}.relations.label_child_singular")
t("#{I18N_NAMESPACE}.label_add_x", x: child_label)
hierarchy_label = t("#{I18N_NAMESPACE}.relations.label_#{relation_type}_singular")
t("#{I18N_NAMESPACE}.label_add_x", x: hierarchy_label)
end
def body_classes
@@ -1,24 +1,27 @@
<%= component_wrapper do %>
<%= primer_form_with(
<%=
primer_form_with(
id: FORM_ID,
model: @child,
model: related,
**submit_url_options,
data: {
turbo: true,
update_work_package: true
}
) do |f| %>
) do |f|
%>
<%# Form fields section %>
<%= flex_layout(mb: 3) do |flex|
<%=
flex_layout(mb: 3) do |flex|
flex.with_row do
if @base_errors&.any?
render(Primer::Alpha::Banner.new(mb: 3, icon: :stop, scheme: :danger)) { @base_errors.join("\n") }
if base_errors.any?
render(Primer::Alpha::Banner.new(mb: 3, icon: :stop, scheme: :danger)) { base_errors.join("\n") }
end
end
flex.with_row do
# @workpackage is not available inside the render_inline_form block
# so we need to re-define them here. Figure out solution for this.
url = ::API::V3::Utilities::PathHelper::ApiV3Path.work_package_available_relation_candidates(@work_package.id, type: Relation::TYPE_CHILD)
url = ::API::V3::Utilities::PathHelper::ApiV3Path.work_package_available_relation_candidates(work_package.id, type: relation_type)
render_inline_form(f) do |my_form|
my_form.work_package_autocompleter(
name: :id,
@@ -26,18 +29,19 @@
required: true,
visually_hide_label: false,
autocomplete_options: {
resource: 'work_packages',
searchKey: 'subjectOrId',
resource: "work_packages",
searchKey: "subjectOrId",
url:,
openDirectly: false,
focusDirectly: true,
dropdownPosition: 'bottom',
dropdownPosition: "bottom",
appendTo: "##{DIALOG_ID}",
data: { test_selector: ID_FIELD_TEST_SELECTOR }
}
)
end
end
end %>
end
%>
<% end %>
<% end %>
@@ -28,26 +28,29 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class WorkPackageRelationsTab::AddWorkPackageChildFormComponent < ApplicationComponent
class WorkPackageRelationsTab::AddWorkPackageHierarchyFormComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
DIALOG_ID = "add-work-package-child-dialog"
FORM_ID = "add-work-package-child-form"
ID_FIELD_TEST_SELECTOR = "work-package-child-form-id"
DIALOG_ID = "add-work-package-hierarchy-dialog"
FORM_ID = "add-work-package-hierarchy-form"
ID_FIELD_TEST_SELECTOR = "work-package-hierarchy-form-id"
I18N_NAMESPACE = "work_package_relations_tab"
def initialize(work_package:, child: nil, base_errors: nil)
attr_reader :work_package, :relation_type, :related, :base_errors
def initialize(work_package:, relation_type:, related: nil, base_errors: nil)
super()
@work_package = work_package
@child = child.presence || WorkPackage.new
@base_errors = base_errors
@relation_type = relation_type
@related = related.presence || WorkPackage.new
@base_errors = base_errors || []
end
def submit_url_options
{ method: :post,
url: work_package_children_relations_path(@work_package) }
url: work_package_hierarchy_relations_path(work_package, relation_type:) }
end
end
@@ -12,8 +12,8 @@
action_bar.with_column(flex_shrink: 0, ml: 2) do
render(
Primer::Alpha::ActionMenu.new(
test_selector: NEW_RELATION_ACTION_MENU,
menu_id: NEW_RELATION_ACTION_MENU
test_selector: ADD_RELATION_ACTION_MENU,
menu_id: ADD_RELATION_ACTION_MENU
)
) do |menu|
menu.with_show_button do |button|
@@ -22,23 +22,7 @@
t(:label_relation)
end
render_child_menu_items(menu)
if should_render_add_relations?
Relation::TYPES.each do |relation_type, type_configuration_hash|
label_key = "#{I18N_NAMESPACE}.relations.#{type_configuration_hash[:name]}_singular"
menu.with_item(
label: t(label_key).upcase_first,
href: new_relation_path(relation_type:),
test_selector: new_button_test_selector(relation_type:),
content_arguments: {
data: { turbo_stream: true }
}
) do |item|
item.with_description.with_content(t("#{I18N_NAMESPACE}.relations.#{relation_type}_description"))
end
end
end
render_add_relations_menu_items(menu, ADD_RELATION_MENU_TYPES)
end
end
end
@@ -8,9 +8,31 @@
# - Child work packages
class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
FRAME_ID = "work-package-relations-tab-content"
NEW_RELATION_ACTION_MENU = "new-relation-action-menu"
NEW_CHILD_ACTION_MENU = "new-child-action-menu"
ADD_RELATION_ACTION_MENU = "add-relation-action-menu"
ADD_CHILD_ACTION_MENU = "add-child-action-menu"
I18N_NAMESPACE = "work_package_relations_tab"
ADD_CHILD_MENU_TYPES = [
"new_child",
Relation::TYPE_CHILD
].freeze
ADD_RELATION_MENU_TYPES = [
*ADD_CHILD_MENU_TYPES,
Relation::TYPE_RELATES,
Relation::TYPE_FOLLOWS,
Relation::TYPE_PRECEDES,
Relation::TYPE_PARENT,
Relation::TYPE_DUPLICATES,
Relation::TYPE_DUPLICATED,
Relation::TYPE_BLOCKS,
Relation::TYPE_BLOCKED,
Relation::TYPE_INCLUDES,
Relation::TYPE_PARTOF,
Relation::TYPE_REQUIRES,
Relation::TYPE_REQUIRED
].freeze
include ApplicationHelper
include OpPrimer::ComponentHelpers
include Turbo::FramesHelper
@@ -44,15 +66,23 @@ class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
def should_render_add_child?
return false if work_package.milestone?
helpers.current_user.allowed_in_project?(:manage_subtasks, work_package.project)
allowed_to?(:manage_subtasks)
end
def should_render_add_parent?
allowed_to?(:manage_subtasks)
end
def should_render_add_relations?
helpers.current_user.allowed_in_project?(:manage_work_package_relations, work_package.project)
allowed_to?(:manage_work_package_relations)
end
def allowed_to?(permission)
helpers.current_user.allowed_in_project?(permission, work_package.project)
end
def should_render_create_button?
should_render_add_child? || should_render_add_relations?
should_render_add_child? || should_render_add_parent? || should_render_add_relations?
end
def render_relation_group(title:, relation_group:, &)
@@ -85,46 +115,74 @@ class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
concat render(Primer::Beta::Counter.new(count:, round: true, scheme: :primary))
end
header.with_column do
render(Primer::Alpha::ActionMenu.new(menu_id: NEW_CHILD_ACTION_MENU)) do |menu|
render(Primer::Alpha::ActionMenu.new(menu_id: ADD_CHILD_ACTION_MENU)) do |menu|
menu.with_show_button do |button|
button.with_leading_visual_icon(icon: :plus)
button.with_trailing_action_icon(icon: :"triangle-down")
t("work_package_relations_tab.label_add_child_button")
end
render_child_menu_items(menu)
render_add_relations_menu_items(menu, ADD_CHILD_MENU_TYPES)
end
end
end
end
end
def render_child_menu_items(menu) # rubocop:disable Metrics/AbcSize
return unless should_render_add_child?
if helpers.current_user.allowed_in_project?(:add_work_packages, work_package.project)
menu.with_item(
label: t("work_package_relations_tab.relations.new_child"),
href: new_project_work_packages_dialog_path(work_package.project, parent_id: work_package.id),
content_arguments: {
data: { turbo_stream: true }
}
) do |item|
item.with_description.with_content(t("work_package_relations_tab.relations.new_child_text"))
end
end
# Renders the menu items for the add relations action menu
#
# @param menu [Primer::Alpha::ActionMenu] The action menu component to render the items in
# @param relation_types [Array<String>] The relation types to render menu items for
def render_add_relations_menu_items(menu, relation_types)
relation_types
.filter { |relation_type| can_add_relation?(relation_type) }
.each { |relation_type| render_add_relation_menu_item(menu, relation_type) }
end
def render_add_relation_menu_item(menu, relation_type)
menu.with_item(
label: t("work_package_relations_tab.relations.child"),
href: new_work_package_children_relation_path(work_package),
label: label(relation_type),
href: new_relation_path(relation_type),
test_selector: new_button_test_selector(relation_type),
content_arguments: {
data: { turbo_stream: true }
}
) do |item|
item.with_description.with_content(t("work_package_relations_tab.relations.child_description"))
item.with_description.with_content(description(relation_type))
end
end
def can_add_relation?(relation_type)
case relation_type
when "new_child"
should_render_add_child? && allowed_to?(:add_work_packages)
when Relation::TYPE_CHILD
should_render_add_child?
when Relation::TYPE_PARENT
should_render_add_parent?
when *Relation::TYPES.keys
should_render_add_relations?
else
false
end
end
def label(relation_type)
label_key =
if Relation::TYPES.key?(relation_type)
"#{Relation::TYPES[relation_type][:name]}_singular"
else
relation_type
end
label = t("#{I18N_NAMESPACE}.relations.#{label_key}")
label.upcase_first
end
def description(relation_type)
I18n.t("#{I18N_NAMESPACE}.relations.#{relation_type}_description")
end
def render_items(border_box, relation_items)
relation_items.each do |relation_item|
relation = relation_item.relation || relation_item.related
@@ -154,17 +212,20 @@ class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
&& item.instance_of?(relation_to_scroll_to.class)
end
def new_relation_path(relation_type:)
raise ArgumentError, "Invalid relation type: #{relation_type}" unless Relation::TYPES.key?(relation_type)
if relation_type == Relation::TYPE_CHILD
raise NotImplementedError, "Child relations are not supported yet"
else
def new_relation_path(relation_type)
case relation_type
when "new_child"
new_project_work_packages_dialog_path(work_package.project, parent_id: work_package.id)
when Relation::TYPE_CHILD, Relation::TYPE_PARENT
new_work_package_hierarchy_relation_path(work_package, relation_type:)
when *Relation::TYPES.keys
new_work_package_relation_path(work_package, relation_type:)
else
raise ArgumentError, "Invalid relation type: #{relation_type}"
end
end
def new_button_test_selector(relation_type:)
def new_button_test_selector(relation_type)
"op-new-relation-button-#{relation_type}"
end
@@ -1,6 +1,6 @@
// We reference an ID as one is required to be specified for the action menu list.
// It can't be nested inside the BEM model as it's placed as a #top-layer element.
#new-relation-action-menu-list,
#new-child-action-menu-list
max-height: 470px
#add-relation-action-menu-list,
#add-child-action-menu-list
max-height: 510px
max-width: 280px
@@ -51,25 +51,21 @@ class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
relation_item.related
end
def child
relation_item.related if parent_child_relationship?
end
def editable? = editable
private
def parent_child_relationship? = relation.nil?
def hierarchy_relationship? = relation.nil?
def should_render_edit_option?
# Children have nothing to edit as it's not a relation.
!parent_child_relationship? && allowed_to_manage_relations?
# Children and parent can not be edited as it's not a relation.
!hierarchy_relationship? && allowed_to_manage_relations?
end
def should_render_action_menu?
return false unless editable?
if parent_child_relationship?
if hierarchy_relationship?
allowed_to_manage_subtasks?
else
allowed_to_manage_relations?
@@ -78,7 +74,7 @@ class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
def allowed_to_manage_subtasks?
helpers.current_user.allowed_in_project?(:manage_subtasks, work_package.project) &&
helpers.current_user.allowed_in_project?(:manage_subtasks, child.project)
helpers.current_user.allowed_in_project?(:manage_subtasks, related_work_package.project)
end
def allowed_to_manage_relations?
@@ -86,7 +82,7 @@ class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
end
def should_display_description?
return false if parent_child_relationship?
return false if hierarchy_relationship?
relation.description.present?
end
@@ -96,32 +92,32 @@ class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
end
def should_display_dates_row?
parent_child_relationship? || relation.follows? || relation.precedes?
hierarchy_relationship? || relation.follows? || relation.precedes?
end
def follows?
return false if parent_child_relationship?
return false if hierarchy_relationship?
relation.relation_type_for(work_package) == Relation::TYPE_FOLLOWS
end
def precedes?
return false if parent_child_relationship?
return false if hierarchy_relationship?
relation.relation_type_for(work_package) == Relation::TYPE_PRECEDES
end
def edit_path
if parent_child_relationship?
raise NotImplementedError, "Children relationships are not editable"
if hierarchy_relationship?
raise NotImplementedError, "Children and parent relationships are not editable"
else
edit_work_package_relation_path(work_package, relation)
end
end
def destroy_path
if parent_child_relationship?
work_package_children_relation_path(work_package, child)
if hierarchy_relationship?
work_package_hierarchy_relation_path(work_package, related_work_package)
else
work_package_relation_path(work_package, relation)
end
@@ -37,17 +37,13 @@ class WorkPackageRelationsTab::RelationsMediator
end
def count
visible_relations.count + ghost_relations.count
[visible_relations, ghost_relations].sum(&:count)
end
def any?
visible_relations.any? || ghost_relations.any?
end
def all_relations
visible_relations + ghost_relations
end
def all_relation_items
(visible_relation_items + ghost_relation_items).sort_by(&:order_key)
end
@@ -86,10 +82,11 @@ class WorkPackageRelationsTab::RelationsMediator
# relation
RelationItem = Data.define(:type, :work_package, :related, :relation, :visibility, :closest) do
def initialize(type:, work_package:, relation:, visibility: :visible, closest: false)
type = ActiveSupport::StringInquirer.new(type.to_s)
if relation.is_a?(Relation)
related = relation.other_work_package(work_package)
else
related = relation # for parent-child relations, `relation` parameter holds the child work package
related = relation # for parent-child relations, `relation` parameter holds the child or parent work package
relation = nil
end
super(type:, work_package:, related:, relation:, visibility:, closest:)
@@ -114,6 +111,10 @@ class WorkPackageRelationsTab::RelationsMediator
@visible_relations ||= work_package.relations.visible.includes(:to, :from).load
end
def visible_parents
@visible_parents ||= work_package.parent_id && work_package.parent.visible? ? [work_package.parent] : []
end
def visible_children
@visible_children ||= work_package.children.visible.load
end
@@ -122,6 +123,10 @@ class WorkPackageRelationsTab::RelationsMediator
@ghost_relations ||= work_package.relations.includes(:to, :from).where.not(id: visible_relations.select(:id)).load
end
def ghost_parents
@ghost_parents ||= work_package.parent_id && !work_package.parent.visible? ? [work_package.parent] : []
end
def ghost_children
@ghost_children ||= work_package.children.where.not(id: visible_children.select(:id)).load
end
@@ -132,7 +137,15 @@ class WorkPackageRelationsTab::RelationsMediator
end
def relation_group(type)
if type == Relation::TYPE_CHILD
case type
when Relation::TYPE_PARENT
RelationGroup.new(
type:,
work_package:,
visible_relations: visible_parents,
ghost_relations: ghost_parents
)
when Relation::TYPE_CHILD
RelationGroup.new(
type:,
work_package:,
@@ -150,7 +163,11 @@ class WorkPackageRelationsTab::RelationsMediator
end
def all_relations_count
visible_relations.count + ghost_relations.count + visible_children.count + ghost_children.count
[
visible_relations, ghost_relations,
visible_parents, ghost_parents,
visible_children, ghost_children
].sum(&:count)
end
private
@@ -74,14 +74,14 @@ module WorkPackages
def add_comment_wrapper_data_attributes
{
test_selector: "op-work-package-journal--new-comment-component",
controller: restricted_comment_stimulus_controller,
controller: internal_comment_stimulus_controller,
"application-target": "dynamic",
restricted_comment_stimulus_controller("-target") => "formContainer",
action: index_stimulus_controller(":onSubmit-end@window->#{restricted_comment_stimulus_controller}#onSubmitEnd"),
restricted_comment_stimulus_controller("-highlight-class") => "work-packages-activities-tab-journals-new-component--journal-notes-body__restricted-comment", # rubocop:disable Layout/LineLength
restricted_comment_stimulus_controller("-hidden-class") => "d-none",
restricted_comment_stimulus_controller("-#{index_stimulus_controller}-outlet") => "##{wrapper_key}",
restricted_comment_stimulus_controller("-is-restricted-value") => false # Initial value
internal_comment_stimulus_controller("-target") => "formContainer",
action: index_stimulus_controller(":onSubmit-end@window->#{internal_comment_stimulus_controller}#onSubmitEnd"),
internal_comment_stimulus_controller("-highlight-class") => "work-packages-activities-tab-journals-new-component--journal-notes-body__internal-comment", # rubocop:disable Layout/LineLength
internal_comment_stimulus_controller("-hidden-class") => "d-none",
internal_comment_stimulus_controller("-#{index_stimulus_controller}-outlet") => "##{wrapper_key}",
internal_comment_stimulus_controller("-is-internal-value") => false # Initial value
}
end
@@ -95,7 +95,7 @@ module WorkPackages
end
def adding_comment_allowed?
User.current.allowed_in_work_package?(:add_work_package_notes, @work_package)
User.current.allowed_in_work_package?(:add_work_package_comments, @work_package)
end
def unsaved_changes_confirmation_message
@@ -71,7 +71,7 @@ module WorkPackages
API::V3::Activities::ActivityEagerLoadingWrapper.wrap(
work_package
.journals
.restricted_visible
.internal_visible
.includes(:user, :customizable_journals, :attachable_journals, :storable_journals, :notifications)
.reorder(version: journal_sorting)
.with_sequence_version
@@ -73,23 +73,23 @@
Primer::Beta::Octicon.new(
:"dot-fill", # color is set via CSS as requested by UI/UX Team
classes: "work-packages-activities-tab-journals-item-component--notification-dot-icon",
size: (OpenProject::FeatureDecisions.comments_with_restricted_visibility_active? ? :small : :medium),
size: (OpenProject::FeatureDecisions.internal_comments_active? ? :small : :medium),
data: { test_selector: "op-journal-unread-notification", "op-ian-center-update-immediate": true }
)
)
end
end
if journal.restricted?
if journal.internal?
header_end_container.with_column(mr: 2) do
render(
Primer::Beta::Octicon.new(
:lock,
classes: "work-packages-activities-tab-journals-item-component--restricted-icon",
classes: "work-packages-activities-tab-journals-item-component--internal-icon",
size: :small,
aria: { label: I18n.t("activities.work_packages.activity_tab.restricted_journal") },
data: { test_selector: "op-journal-restricted-icon" },
color: (journal.restricted? ? :attention : :default)
aria: { label: I18n.t("activities.work_packages.activity_tab.internal_journal") },
data: { test_selector: "op-journal-internal-icon" },
color: (journal.internal? ? :attention : :default)
)
)
end
@@ -63,24 +63,24 @@ module WorkPackages
def container_classes
[].tap do |classes|
if journal.restricted?
classes << "work-packages-activities-tab-journals-item-component--container__restricted-comment"
if journal.internal?
classes << "work-packages-activities-tab-journals-item-component--container__internal-comment"
end
end
end
def comment_header_classes
[].tap do |classes|
if journal.restricted?
classes << "work-packages-activities-tab-journals-item-component--header__restricted-comment"
if journal.internal?
classes << "work-packages-activities-tab-journals-item-component--header__internal-comment"
end
end
end
def comment_body_classes
["work-packages-activities-tab-journals-item-component--journal-notes-body"].tap do |classes|
if journal.restricted?
classes << "work-packages-activities-tab-journals-item-component--journal-notes-body__restricted-comment"
if journal.internal?
classes << "work-packages-activities-tab-journals-item-component--journal-notes-body__internal-comment"
end
end
end
@@ -112,7 +112,7 @@ module WorkPackages
end
def allowed_to_quote?
User.current.allowed_in_project?(:add_work_package_notes, journal.journable.project)
User.current.allowed_in_project?(:add_work_package_comments, journal.journable.project)
end
def copy_url_action_item(menu)
@@ -163,10 +163,10 @@ module WorkPackages
quote_comments_stimulus_controller("-content-param") => journal.notes,
quote_comments_stimulus_controller("-user-id-param") => journal.user_id,
quote_comments_stimulus_controller("-user-name-param") => journal.user.name,
quote_comments_stimulus_controller("-is-restricted-param") => journal.restricted?,
quote_comments_stimulus_controller("-is-internal-param") => journal.internal?,
quote_comments_stimulus_controller("-text-wrote-param") => I18n.t(:text_wrote),
quote_comments_stimulus_controller("-#{index_stimulus_controller}-outlet") => items_index_selector,
quote_comments_stimulus_controller("-#{restricted_comment_stimulus_controller}-outlet") => add_comment_selector,
quote_comments_stimulus_controller("-#{internal_comment_stimulus_controller}-outlet") => add_comment_selector,
test_selector: "op-wp-journal-#{journal.id}-quote"
}
end
@@ -11,11 +11,11 @@
color: var(--bgColor-accent-emphasis)
&--header-start-container
flex-grow: 1
&--container__restricted-comment
&--container__internal-comment
border-color: var(--display-brown-borderColor-emphasis)
&--header__restricted-comment
&--header__internal-comment
border-color: var(--display-brown-borderColor-emphasis)
background-color: var(--display-brown-bgColor-muted)
&--journal-notes-body__restricted-comment
&--journal-notes-body__internal-comment
border-color: var(--display-brown-borderColor-emphasis)
@@ -73,7 +73,7 @@ module WorkPackages
def wrapper_uniq_by = journal.id
def current_user_can_react?
User.current.allowed_in_work_package?(:add_work_package_notes, work_package)
User.current.allowed_in_work_package?(:add_work_package_comments, work_package)
end
def current_user_cannot_react? = !current_user_can_react?
@@ -193,7 +193,7 @@ module WorkPackages
render(Primer::Beta::Octicon.new(
:"dot-fill", # color is set via CSS as requested by UI/UX Team
classes: "work-packages-activities-tab-journals-item-component-details--notification-dot-icon",
size: (OpenProject::FeatureDecisions.comments_with_restricted_visibility_active? ? :small : :medium),
size: (OpenProject::FeatureDecisions.internal_comments_active? ? :small : :medium),
data: { test_selector: "op-journal-unread-notification", "op-ian-center-update-immediate": true }
))
end
@@ -53,7 +53,7 @@ module WorkPackages
def wrapper_data_attributes
{
restricted_comment_stimulus_controller("-is-restricted-value") => journal.restricted?
internal_comment_stimulus_controller("-is-internal-value") => journal.internal?
}
end
end
@@ -88,7 +88,7 @@ module WorkPackages
end
def current_user_can_react?
User.current.allowed_in_work_package?(:add_work_package_notes, work_package)
User.current.allowed_in_work_package?(:add_work_package_comments, work_package)
end
def current_user_cannot_react? = !current_user_can_react?
@@ -36,6 +36,7 @@
end
end
end
new_form_container.with_row(
display: form_row_display_value,
data: { "work-packages--activities-tab--index-target": "formRow" }
@@ -61,16 +62,16 @@
render(WorkPackages::ActivitiesTab::Journals::NotesForm.new(f))
end
form_container.with_row(flex_layout: true, justify_content: adding_restricted_comment_allowed? ? :space_between : :flex_end) do |form_container_row|
if adding_restricted_comment_allowed?
form_container_row.with_column(flex_layout: true, align_items: :center) do |restricted_comment_container|
restricted_comment_container.with_column(mr: 2, test_selector: "op-work-package-journal-restricted-comment-checkbox") do
render(WorkPackages::ActivitiesTab::Journals::RestrictedNoteForm.new(f))
form_container.with_row(flex_layout: true, justify_content: adding_internal_comment_allowed? ? :space_between : :flex_end) do |form_container_row|
if adding_internal_comment_allowed?
form_container_row.with_column(flex_layout: true, align_items: :center) do |internal_comment_container|
internal_comment_container.with_column(mr: 2, test_selector: "op-work-package-journal-internal-comment-checkbox") do
render(WorkPackages::ActivitiesTab::Journals::InternalNoteForm.new(f))
end
restricted_comment_container.with_column(
internal_comment_container.with_column(
display: :none,
data: { "work-packages--activities-tab--restricted-comment-target": "learnMoreLink" },
data: { "work-packages--activities-tab--internal-comment-target": "learnMoreLink" },
pb: 1
) do
render(Primer::Beta::Link.new(href: learn_more_static_link_url, target: "_blank")) do |link|
@@ -99,3 +100,21 @@
end
end
%>
<% if adding_internal_comment_allowed? %>
<%=
render(
Primer::OpenProject::DangerDialog.new(
title: I18n.t("activities.work_packages.activity_tab.internal_comment_confirmation.title"),
confirm_button_text: I18n.t("activities.work_packages.activity_tab.internal_comment_confirmation.confirm_button_text"),
data: confirm_dialog_data_attributes,
test_selector: "op-work-package-internal-comment-confirmation-dialog"
)
) do |dialog|
dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { I18n.t("activities.work_packages.activity_tab.internal_comment_confirmation.title") }
message.with_description_content(I18n.t("activities.work_packages.activity_tab.internal_comment_confirmation.description"))
end
end
%>
<% end %>
@@ -33,6 +33,7 @@ module WorkPackages
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
include WorkPackages::ActivitiesTab::StimulusControllers
def initialize(work_package:, journal: nil, form_hidden_initially: true)
super
@@ -58,15 +59,21 @@ module WorkPackages
form_hidden_initially ? :none : :block
end
def adding_restricted_comment_allowed?
OpenProject::FeatureDecisions.comments_with_restricted_visibility_active? &&
work_package.project.enabled_comments_with_restricted_visibility &&
User.current.allowed_in_project?(:add_comments_with_restricted_visibility, work_package.project)
def adding_internal_comment_allowed?
OpenProject::FeatureDecisions.internal_comments_active? &&
work_package.project.enabled_internal_comments &&
User.current.allowed_in_project?(:add_internal_comments, work_package.project)
end
def learn_more_static_link_url
::OpenProject::Static::Links.url_for(:user_guides_work_package_activity)
end
def confirm_dialog_data_attributes
{
internal_comment_stimulus_controller("-target") => "confirmationDialog"
}
end
end
end
end
@@ -27,6 +27,6 @@
max-height: 10vh
.ck-editor__preview
max-height: 30vh
&--journal-notes-body__restricted-comment
&--journal-notes-body__internal-comment
background-color: var(--display-brown-bgColor-muted) !important
border-top: var(--borderWidth-thick) solid var(--display-brown-borderColor-emphasis)
@@ -18,7 +18,7 @@
end
start_date.with_row(classes: "wp-datepicker-dialog-date-form--text-field-container") do
render(Primer::Alpha::TextField.new(**text_field_options(name: :start_date, label: start_date_label), aria: { live: :polite }))
render(Primer::Alpha::TextField.new(**text_field_options(name: :start_date, label: start_date_label)))
end
start_date.with_row(classes: "wp-datepicker-dialog-set-today-link") do
render_today_link(name: :start_date)
@@ -45,7 +45,7 @@
end
due_date.with_row(classes: "wp-datepicker-dialog-date-form--text-field-container") do
render(Primer::Alpha::TextField.new(**text_field_options(name: :due_date, label: I18n.t("attributes.due_date")), aria: { live: :polite }))
render(Primer::Alpha::TextField.new(**text_field_options(name: :due_date, label: I18n.t("attributes.due_date"))))
end
due_date.with_row(classes: "wp-datepicker-dialog-set-today-link") do
render_today_link(name: :due_date)
@@ -54,7 +54,7 @@
end
body.with_column(classes: "wp-datepicker-dialog-date-form--duration") do
render(Primer::Alpha::TextField.new(**text_field_options(name: :duration, label: WorkPackage.human_attribute_name("duration")), aria: { live: :polite }))
render(Primer::Alpha::TextField.new(**text_field_options(name: :duration, label: WorkPackage.human_attribute_name("duration"))))
end
end
end
@@ -167,7 +167,7 @@ module WorkPackages
def field_value(name)
errors = @work_package.errors.where(name)
if (user_value = errors.map { |error| error.options[:value] }.find { !_1.nil? })
if (user_value = errors.map { |error| error.options[:value] }.find { !it.nil? })
user_value
else
@work_package.public_send(name)
@@ -211,16 +211,19 @@ module WorkPackages
data[:focus] = "true"
end
{ data: }
{
data: data,
aria: { live: :polite, atomic: true }
}
end
def single_date_field_button_link(focused_field)
permitted_params = params.merge(date_mode: "range", focused_field:).permit!
if params[:action] == "new"
new_work_package_datepicker_dialog_content_path(permitted_params)
new_date_picker_path(permitted_params)
else
work_package_datepicker_dialog_content_path(permitted_params)
work_package_date_picker_path(permitted_params)
end
end
@@ -61,9 +61,11 @@ module WorkPackages
def submit_path
if work_package.new_record?
datepicker_dialog_content_path
# create: get json of selected dates
date_picker_path
else
work_package_datepicker_dialog_content_path(work_package)
# update dates of work package
work_package_date_picker_path(work_package)
end
end
@@ -72,9 +74,9 @@ module WorkPackages
.merge(schedule_manually:)
.permit!
if work_package.new_record?
new_work_package_datepicker_dialog_content_path("new", dialog_params)
new_date_picker_path(dialog_params)
else
work_package_datepicker_dialog_content_path(work_package, dialog_params)
work_package_date_picker_path(work_package, dialog_params)
end
end
@@ -44,7 +44,7 @@ module WorkPackages
render(Primer::Beta::Octicon.new(
icon: "op-enterprise-addons",
"aria-label": I18n.t(:label_enterprise_edition),
classes: "upsale-colored", ml: 2
classes: "upsell-colored", ml: 2
))
end
@@ -63,9 +63,9 @@ module EmojiReactions
def manage_emoji_reactions?
case model.reactable
when WorkPackage
user.allowed_in_work_package?(:add_work_package_notes, model.reactable)
user.allowed_in_work_package?(:add_work_package_comments, model.reactable)
when Journal
user.allowed_in_work_package?(:add_work_package_notes, model.reactable.journable)
user.allowed_in_work_package?(:add_work_package_comments, model.reactable.journable)
else
false
end
@@ -28,47 +28,15 @@
module ProjectLifeCycleSteps
class BaseContract < ::ModelContract
validate :select_custom_fields_permission
validate :consecutive_steps_have_increasing_dates
attribute :start_date
attribute :finish_date
def valid?(context = :saving_phases) = super
validate :validate_edit_project_phase_permission
def select_custom_fields_permission
return if user.allowed_in_project?(:edit_project_phases, model)
def validate_edit_project_phase_permission
return if user.allowed_in_project?(:edit_project_phases, model.project)
errors.add :base, :error_unauthorized
end
def consecutive_steps_have_increasing_dates
# Filter out steps with missing dates before proceeding with comparison
filtered_steps = model.available_phases.select(&:start_date)
# Only proceed with comparisons if there are at least 2 valid steps
return if filtered_steps.size < 2
# Compare consecutive steps in pairs
filtered_steps.each_cons(2) do |previous_step, current_step|
if has_invalid_dates?(previous_step, current_step)
error = current_step.errors.add(:date_range, :non_continuous_dates)
unless model.errors.include?(:"available_phases.date_range")
model.errors.import(error, attribute: :"available_phases.date_range")
end
end
end
end
private
def start_date_for(step)
step.start_date
end
def finish_date_for(step)
step.finish_date || step.start_date # Use the start_date as fallback for single date stages
end
def has_invalid_dates?(previous_step, current_step)
start_date_for(current_step) <= finish_date_for(previous_step)
end
end
end
@@ -0,0 +1,33 @@
# 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 WorkPackages::ActivitiesTab::CommentAttachmentsClaimsContract < ModelContract
include ::Attachments::ValidateReplacements
end
@@ -162,12 +162,6 @@ module WorkPackages
validate :validate_duration_and_dates_are_not_derivable
def initialize(work_package, user, options: {})
super
@can = WorkPackagePolicy.new(user)
end
def assignable_statuses(include_default: false)
# Do not allow skipping statuses without intermediately saving the work package.
# We therefore take the original status of the work_package, while preserving all
@@ -226,8 +220,6 @@ module WorkPackages
private
attr_reader :can
def validate_after_soonest_start(date_attribute)
return if model.schedule_manually?
return if model.children.any?
@@ -33,26 +33,33 @@ module WorkPackages
def self.model = WorkPackage
attribute :journal_notes do
errors.add(:journal_notes, :error_unauthorized) unless can?(:comment)
errors.add(:journal_notes, :error_unauthorized) unless adding_notes_allowed?
errors.add(:journal_notes, :blank) if model.journal_notes.blank?
end
attribute :journal_restricted do
if model.journal_restricted && !OpenProject::FeatureDecisions.comments_with_restricted_visibility_active?
errors.add(:journal_restricted, :feature_disabled)
attribute :journal_internal do
next unless model.journal_internal
unless OpenProject::FeatureDecisions.internal_comments_active?
errors.add(:journal_internal, :feature_disabled)
end
unless allowed_in_project?(:add_internal_comments)
errors.add(:journal_internal, :error_unauthorized)
end
end
private
def can?(permission)
policy.allowed?(model, permission)
def adding_notes_allowed?
allowed_in_work_package?(:add_work_package_comments) || allowed_in_work_package?(:edit_work_packages)
end
attr_writer :policy
def allowed_in_work_package?(permission)
user.allowed_in_work_package?(permission, model)
end
def policy
@policy ||= WorkPackagePolicy.new(user)
def allowed_in_project?(permission)
user.allowed_in_project?(permission, model.project)
end
end
end
+15 -7
View File
@@ -49,18 +49,18 @@ module WorkPackages
attribute_permission :project_id, :move_work_packages
def can_set_parent?
@can.allowed?(model, :manage_subtasks)
allowed_in_project?(:manage_subtasks)
end
private
def user_allowed_to_edit
with_unchanged_project_id do
next if @can.allowed?(model, :edit) ||
@can.allowed?(model, :assign_version) ||
@can.allowed?(model, :change_status) ||
@can.allowed?(model, :manage_subtasks) ||
@can.allowed?(model, :move)
next if allowed_in_work_package?(:edit_work_packages) ||
allowed_in_project?(:assign_versions) ||
allowed_in_project?(:change_work_package_status) ||
allowed_in_project?(:manage_subtasks) ||
allowed_in_project?(:move_work_packages)
next if allowed_journal_addition?
errors.add :base, :error_unauthorized
@@ -74,7 +74,7 @@ module WorkPackages
end
def allowed_journal_addition?
model.changes.empty? && model.journal_notes && can.allowed?(model, :comment)
model.changes.empty? && model.journal_notes && allowed_in_work_package?(:add_work_package_comments)
end
def can_move_to_milestone
@@ -93,5 +93,13 @@ module WorkPackages
errors.add :parent_id, :error_unauthorized
end
end
def allowed_in_project?(permission)
user.allowed_in_project?(permission, model.project)
end
def allowed_in_work_package?(permission)
user.allowed_in_work_package?(permission, model)
end
end
end
@@ -51,12 +51,6 @@ module Admin
redirect_to action: :index
end
def default_breadcrumb; end
def show_local_breadcrumb
false
end
private
def create_journal(container, user, notes)
@@ -76,12 +76,6 @@ class Admin::BackupsController < ApplicationController
redirect_to action: "show"
end
def default_breadcrumb; end
def show_local_breadcrumb
false
end
def check_enabled
render_404 unless OpenProject::Configuration.backup_enabled?
end
@@ -89,12 +89,6 @@ class Admin::CustomFields::CustomFieldProjectsController < ApplicationController
respond_to_with_turbo_streams(status: delete_service.success? ? :ok : :unprocessable_entity)
end
def default_breadcrumb; end
def show_local_breadcrumb
false
end
private
def render_project_list(url_for_action: action_name)
@@ -30,12 +30,6 @@ module Admin::Settings
class AttachmentsSettingsController < ::Admin::SettingsController
menu_item :attachments
def default_breadcrumb; end
def show_local_breadcrumb
false
end
def settings_params
super.tap do |settings|
settings["attachment_whitelist"] = settings["attachment_whitelist"].split(/\r?\n/)
@@ -103,8 +103,6 @@ module Admin
@other_enumerations = enumeration_class.all - [@enumeration]
end
def default_breadcrumb; end
private
def move_params
@@ -32,10 +32,6 @@ module Admin::Settings
before_action :validate_enabled_modules, only: :update # rubocop:disable Rails/LexicallyScopedActionFilter
def default_breadcrumb
t(:label_project_new)
end
private
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
@@ -48,10 +48,6 @@ module Admin::Settings
before_action :find_unlink_project_custom_field_mapping, only: :unlink
# rubocop:enable Rails/LexicallyScopedActionFilter
def show_local_breadcrumb
false
end
def index
respond_to :html
end
@@ -37,10 +37,6 @@ module Admin::Settings
respond_to :html
end
def show_local_breadcrumb
false
end
def settings_params
super.tap do |settings|
if settings["consent_required"] == "1" && params["toggle_consent_time"] == "1"
@@ -35,10 +35,6 @@ module Admin::Settings
before_action :require_ee, except: :show # rubocop:disable Rails/LexicallyScopedActionFilter
before_action :check_clamav, only: %i[update], if: -> { scan_enabled? }
def show_local_breadcrumb
false
end
def av_form
selected = params.dig(:settings, :antivirus_scan_mode)&.to_sym || :disabled
@@ -81,18 +81,6 @@ module Admin
redirect_to action: :show_plugin, id: @plugin.id
end
def show_local_breadcrumb
false
end
def default_breadcrumb
if @plugin
@plugin.name
else
I18n.t(:label_setting_plural)
end
end
protected
def find_plugin
-6
View File
@@ -90,12 +90,6 @@ class AdminController < ApplicationController
@storage_information = OpenProject::Storage.mount_information
end
def default_breadcrumb; end
def show_local_breadcrumb
false
end
private
def hidden_admin_menu_items
@@ -20,10 +20,6 @@ class AnnouncementsController < ApplicationController
private
def show_local_breadcrumb
false
end
def announcement_params
params.require(:announcement).permit("text", "show_until", "active")
end
+3 -16
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -189,7 +191,7 @@ class ApplicationController < ActionController::Base
# Create CSRF issue
def log_csrf_failure
message = "CSRF validation error"
message << " (No session cookie present)" if openproject_cookie_missing?
message += " (No session cookie present)" if openproject_cookie_missing?
op_handle_error message, reference: :csrf_validation_failed
end
@@ -434,21 +436,6 @@ class ApplicationController < ActionController::Base
api_request? ? nil : super
end
def default_breadcrumb
label = "label_#{controller_name.singularize}"
I18n.t(label + "_plural",
default: label.to_sym)
end
helper_method :default_breadcrumb
def show_local_breadcrumb
false
end
helper_method :show_local_breadcrumb
def admin_first_level_menu_entry
menu_item = admin_menu_item(current_menu_item)
menu_item.parent
@@ -85,12 +85,6 @@ class AttributeHelpTextsController < ApplicationController
protected
def default_breadcrumb; end
def show_local_breadcrumb
false
end
private
def permitted_params_with_attachments
-6
View File
@@ -102,12 +102,6 @@ class ColorsController < ApplicationController
protected
def show_local_breadcrumb
false
end
def default_breadcrumb; end
def require_admin_unless_readonly_api_request
require_admin unless %w[index show].include? action_name and
api_request?
@@ -100,8 +100,10 @@ module OpTurbo
.render_in(view_context)
end
def close_dialog_via_turbo_stream(target)
turbo_streams << OpTurbo::StreamComponent.new(action: :closeDialog, target:).render_in(view_context)
def close_dialog_via_turbo_stream(target, additional: {})
turbo_streams << OpTurbo::StreamComponent
.new(action: :closeDialog, target:, additional: additional.to_json)
.render_in(view_context)
end
def turbo_streams
+1 -7
View File
@@ -86,7 +86,7 @@ class CustomActionsController < ApplicationController
return if EnterpriseToken.allows_to?(:custom_actions)
if request.get?
render template: "custom_actions/upsale"
render template: "custom_actions/upsell"
else
render_403
end
@@ -102,10 +102,4 @@ class CustomActionsController < ApplicationController
params[:custom_action][:conditions] ||= {}
params[:custom_action][:actions] ||= {}
end
def show_local_breadcrumb
false
end
def default_breadcrumb; end
end
@@ -61,12 +61,6 @@ class CustomFieldsController < ApplicationController
protected
def default_breadcrumb; end
def show_local_breadcrumb
false
end
def validate_enterprise_token
if params.dig(:custom_field, :field_format) == "hierarchy" && !EnterpriseToken.allows_to?(:custom_field_hierarchies)
render_403
+3 -3
View File
@@ -39,7 +39,7 @@ class CustomStylesController < ApplicationController
before_action :require_admin,
except: UNGUARDED_ACTIONS
before_action :require_ee_token,
except: UNGUARDED_ACTIONS + %i[upsale]
except: UNGUARDED_ACTIONS + %i[upsell]
skip_before_action :check_if_login_required,
only: UNGUARDED_ACTIONS
no_authorization_required! *UNGUARDED_ACTIONS
@@ -58,7 +58,7 @@ class CustomStylesController < ApplicationController
end
end
def upsale; end
def upsell; end
def create
@custom_style = CustomStyle.create(custom_style_params)
@@ -180,7 +180,7 @@ class CustomStylesController < ApplicationController
def require_ee_token
unless EnterpriseToken.allows_to?(:define_custom_style)
redirect_to custom_style_upsale_path
redirect_to custom_style_upsell_path
end
end
@@ -98,12 +98,6 @@ class EnterprisesController < ApplicationController
helpers.write_augur_to_gon
end
def default_breadcrumb; end
def show_local_breadcrumb
false
end
def check_user_limit
if OpenProject::Enterprise.user_limit_reached?
flash.now[:warning] = I18n.t(
-4
View File
@@ -160,10 +160,6 @@ class GroupsController < ApplicationController
Group.in_project(Project.allowed_to(current_user, :view_members)).exists?
end
def show_local_breadcrumb
false
end
def respond_membership_altered(service_call)
if service_call.success?
flash[:notice] = I18n.t :notice_successful_update
-6
View File
@@ -61,10 +61,4 @@ class HomescreenController < ApplicationController
redirect_to_global_menu_item(params[:jump]) && return
end
end
def default_breadcrumb; end
def show_local_breadcrumb
false
end
end
@@ -106,10 +106,6 @@ class LdapAuthSourcesController < ApplicationController
end
end
def show_local_breadcrumb
false
end
def block_if_password_login_disabled
render_404 if OpenProject::Configuration.disable_password_login?
end
-8
View File
@@ -202,14 +202,6 @@ class MyController < ApplicationController
private
def default_breadcrumb
I18n.t(:label_my_account)
end
def show_local_breadcrumb
false
end
def redirect_if_password_change_not_allowed_for(user)
unless user.change_password_allowed?
flash[:error] = I18n.t(:notice_can_t_change_password)
+3 -5
View File
@@ -31,7 +31,7 @@ class NotificationsController < ApplicationController
before_action :require_login
before_action :filtered_query, only: :mark_all_read
no_authorization_required! :index, :split_view, :update_counter, :mark_all_read, :date_alerts, :share_upsale
no_authorization_required! :index, :split_view, :update_counter, :mark_all_read, :date_alerts, :share_upsell
before_action :check_filter, only: %i[index]
@@ -65,7 +65,7 @@ class NotificationsController < ApplicationController
render_notifications_layout
end
def share_upsale
def share_upsell
render_notifications_layout
end
@@ -73,8 +73,6 @@ class NotificationsController < ApplicationController
def split_view_base_route = notifications_path(request.query_parameters)
def default_breadcrumb; end
def render_notifications_layout
# Frontend will handle rendering
# but we will need to render with notification specific layout
@@ -100,7 +98,7 @@ class NotificationsController < ApplicationController
return if EnterpriseToken.allows_to?(:date_alerts)
if params[:filter] == "reason" && params[:name] == "dateAlert"
redirect_to notifications_date_alert_upsale_path
redirect_to notifications_date_alert_upsell_path
end
end
end
@@ -97,14 +97,6 @@ module OAuth
redirect_to action: :index
end
protected
def default_breadcrumb; end
def show_local_breadcrumb
false
end
private
def prevent_builtin_edits
@@ -84,6 +84,4 @@ class OmniAuthLoginController < ApplicationController
def omniauth_back_url
request.env["omniauth.origin"].presence || params[:RelayState]
end
def default_breadcrumb; end
end
@@ -155,8 +155,4 @@ class PlaceholderUsersController < ApplicationController
render_403 message: I18n.t("placeholder_users.right_to_manage_members_missing")
end
end
def show_local_breadcrumb
false
end
end

Some files were not shown because too many files have changed in this diff Show More