mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge branch 'dev' into merge-release/17.3-20260408042316
# Conflicts: # config/locales/crowdin/el.yml # modules/backlogs/app/forms/my/backlogs_form.rb # modules/backlogs/app/views/shared/_view_my_settings.html.erb # modules/backlogs/config/locales/crowdin/ru.yml # modules/backlogs/config/locales/crowdin/uk.yml # modules/two_factor_authentication/config/locales/crowdin/el.yml # modules/wikis/config/locales/crowdin/uk.yml
This commit is contained in:
@@ -21,7 +21,10 @@ jobs:
|
||||
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
|
||||
uses: contributor-assistant/github-action@v2.6.1
|
||||
env:
|
||||
# https://github.com/contributor-assistant/github-action?tab=readme-ov-file#environmental-variables
|
||||
# Built-in GitHub token to make the API calls for interacting with GitHub. Does not need to be specified the secrets store.
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Access token for repository where the signatures are stored (see below for remote-repository-name)
|
||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.OPENPROJECTCI_GH_LEGAL_TOKEN }}
|
||||
with:
|
||||
path-to-signatures: "contributor-license-agreement/signatures/version1.json"
|
||||
|
||||
@@ -76,6 +76,7 @@ jobs:
|
||||
crowdin_branch_name: ${{ steps.vars.outputs.crowdin_branch }}
|
||||
# Dont create a PR for the updated translations
|
||||
push_translations: false
|
||||
user: auto
|
||||
env:
|
||||
OPENPROJECT_CROWDIN_PROJECT: ${{ secrets.OPENPROJECT_CROWDINV2_PROJECT }}
|
||||
OPENPROJECT_CROWDIN_API_KEY: ${{ secrets.OPENPROJECT_CROWDINV2_API_KEY }}
|
||||
|
||||
@@ -169,6 +169,10 @@ Rails/ContentTag:
|
||||
# dynamic finders cop clashes with capybara ID cop
|
||||
Rails/DynamicFindBy:
|
||||
Enabled: true
|
||||
AllowedMethods:
|
||||
- find_by_id_or_identifier
|
||||
- find_by_id_or_identifier!
|
||||
- find_by_semantic_identifier
|
||||
Exclude:
|
||||
- "spec/features/**/*.rb"
|
||||
- "spec/support/**/*.rb"
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
4.0.1
|
||||
4.0.2
|
||||
|
||||
@@ -41,10 +41,10 @@ gem "activemodel-serializers-xml", "~> 1.0.1"
|
||||
gem "activerecord-import", "~> 2.2.0"
|
||||
gem "activerecord-session_store", "~> 2.2.0"
|
||||
gem "ox"
|
||||
gem "rails", "~> 8.1.2"
|
||||
gem "rails", "~> 8.1.3"
|
||||
gem "responders", "~> 3.2"
|
||||
|
||||
gem "ffi", "~> 1.15"
|
||||
gem "ffi", "~> 1.17"
|
||||
|
||||
gem "connection_pool", "~> 3.0.2"
|
||||
|
||||
@@ -161,7 +161,7 @@ gem "ttfunk", "~> 1.7.0" # remove after https://github.com/prawnpdf/prawn/issues
|
||||
# prawn implicitly depends on matrix gem no longer in ruby core with 3.1
|
||||
gem "matrix", "~> 0.4.3"
|
||||
|
||||
gem "mcp", "~> 0.8.0"
|
||||
gem "mcp", "~> 0.9.2"
|
||||
|
||||
gem "meta-tags", "~> 2.23.0"
|
||||
|
||||
@@ -204,9 +204,9 @@ gem "carrierwave_direct", "~> 3.0.0"
|
||||
gem "ssrf_filter", "~> 1.3"
|
||||
gem "fog-aws"
|
||||
|
||||
gem "aws-sdk-core", "~> 3.241"
|
||||
gem "aws-sdk-core", "~> 3.244"
|
||||
# File upload via fog + screenshots on travis
|
||||
gem "aws-sdk-s3", "~> 1.213"
|
||||
gem "aws-sdk-s3", "~> 1.217"
|
||||
|
||||
gem "openproject-token", "~> 8.8.2"
|
||||
|
||||
@@ -254,7 +254,7 @@ gem "factory_bot_rails", "~> 6.5.0", require: false
|
||||
gem "turbo_power", "~> 0.7.0"
|
||||
gem "turbo-rails", "~> 2.0.20"
|
||||
|
||||
gem "httpx", "~> 1.7.4"
|
||||
gem "httpx", "~> 1.7.5"
|
||||
|
||||
# Brings actual deep-freezing to most ruby objects
|
||||
gem "ice_nine"
|
||||
@@ -266,7 +266,7 @@ group :test do
|
||||
|
||||
# Test prof provides factories from code
|
||||
# and other niceties
|
||||
gem "test-prof", "~> 1.5.0"
|
||||
gem "test-prof", "~> 1.6.0"
|
||||
gem "turbo_tests", github: "opf/turbo_tests", ref: "with-patches"
|
||||
|
||||
gem "rack_session_access"
|
||||
|
||||
+134
-135
@@ -203,7 +203,7 @@ PATH
|
||||
remote: modules/two_factor_authentication
|
||||
specs:
|
||||
openproject-two_factor_authentication (1.0.0)
|
||||
aws-sdk-sns (>= 1.101, < 1.113)
|
||||
aws-sdk-sns (>= 1.101, < 1.114)
|
||||
messagebird-rest (>= 1.4.2, < 5.1.0)
|
||||
rotp (~> 6.1)
|
||||
webauthn (~> 3.0)
|
||||
@@ -228,31 +228,31 @@ GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
Ascii85 (2.0.1)
|
||||
action_text-trix (2.1.17)
|
||||
action_text-trix (2.1.18)
|
||||
railties
|
||||
actioncable (8.1.2.1)
|
||||
actionpack (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
actioncable (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.1.2.1)
|
||||
actionpack (= 8.1.2.1)
|
||||
activejob (= 8.1.2.1)
|
||||
activerecord (= 8.1.2.1)
|
||||
activestorage (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
actionmailbox (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
activejob (= 8.1.3)
|
||||
activerecord (= 8.1.3)
|
||||
activestorage (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.1.2.1)
|
||||
actionpack (= 8.1.2.1)
|
||||
actionview (= 8.1.2.1)
|
||||
activejob (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
actionmailer (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
actionview (= 8.1.3)
|
||||
activejob (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
mail (>= 2.8.0)
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.1.2.1)
|
||||
actionview (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
actionpack (8.1.3)
|
||||
actionview (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
nokogiri (>= 1.8.5)
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
@@ -263,34 +263,34 @@ GEM
|
||||
actionpack-xml_parser (2.0.1)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
actiontext (8.1.2.1)
|
||||
actiontext (8.1.3)
|
||||
action_text-trix (~> 2.1.15)
|
||||
actionpack (= 8.1.2.1)
|
||||
activerecord (= 8.1.2.1)
|
||||
activestorage (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
actionpack (= 8.1.3)
|
||||
activerecord (= 8.1.3)
|
||||
activestorage (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
actionview (8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
active_record_doctor (2.0.1)
|
||||
activerecord (>= 7.0.0)
|
||||
activejob (8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
activejob (8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
activemodel (8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
activemodel-serializers-xml (1.0.3)
|
||||
activemodel (>= 5.0.0.a)
|
||||
activesupport (>= 5.0.0.a)
|
||||
builder (~> 3.1)
|
||||
activerecord (8.1.2.1)
|
||||
activemodel (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
activerecord (8.1.3)
|
||||
activemodel (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
timeout (>= 0.4.0)
|
||||
activerecord-import (2.2.0)
|
||||
activerecord (>= 4.2)
|
||||
@@ -302,13 +302,13 @@ GEM
|
||||
cgi (>= 0.3.6)
|
||||
rack (>= 2.0.8, < 4)
|
||||
railties (>= 7.0)
|
||||
activestorage (8.1.2.1)
|
||||
actionpack (= 8.1.2.1)
|
||||
activejob (= 8.1.2.1)
|
||||
activerecord (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
activestorage (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
activejob (= 8.1.3)
|
||||
activerecord (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.1.2.1)
|
||||
activesupport (8.1.3)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
@@ -347,8 +347,8 @@ GEM
|
||||
awesome_nested_set (3.9.0)
|
||||
activerecord (>= 4.0.0, < 8.2)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1227.0)
|
||||
aws-sdk-core (3.243.0)
|
||||
aws-partitions (1.1234.0)
|
||||
aws-sdk-core (3.244.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@@ -356,15 +356,15 @@ GEM
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.122.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sdk-kms (1.123.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.216.0)
|
||||
aws-sdk-core (~> 3, >= 3.243.0)
|
||||
aws-sdk-s3 (1.217.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-sns (1.112.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sdk-sns (1.113.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
@@ -391,7 +391,7 @@ GEM
|
||||
erubi (~> 1.4)
|
||||
parser (>= 2.4)
|
||||
smart_properties
|
||||
bigdecimal (4.0.1)
|
||||
bigdecimal (4.1.1)
|
||||
bindata (2.5.1)
|
||||
bootsnap (1.23.0)
|
||||
msgpack (~> 1.2)
|
||||
@@ -586,14 +586,14 @@ GEM
|
||||
concurrent-ruby (~> 1.1)
|
||||
webrick (~> 1.7)
|
||||
websocket-driver (~> 0.7)
|
||||
ffi (1.17.3-aarch64-linux-gnu)
|
||||
ffi (1.17.3-aarch64-linux-musl)
|
||||
ffi (1.17.3-arm-linux-gnu)
|
||||
ffi (1.17.3-arm-linux-musl)
|
||||
ffi (1.17.3-arm64-darwin)
|
||||
ffi (1.17.3-x86_64-darwin)
|
||||
ffi (1.17.3-x86_64-linux-gnu)
|
||||
ffi (1.17.3-x86_64-linux-musl)
|
||||
ffi (1.17.4-aarch64-linux-gnu)
|
||||
ffi (1.17.4-aarch64-linux-musl)
|
||||
ffi (1.17.4-arm-linux-gnu)
|
||||
ffi (1.17.4-arm-linux-musl)
|
||||
ffi (1.17.4-arm64-darwin)
|
||||
ffi (1.17.4-x86_64-darwin)
|
||||
ffi (1.17.4-x86_64-linux-gnu)
|
||||
ffi (1.17.4-x86_64-linux-musl)
|
||||
flamegraph (0.9.5)
|
||||
fog-aws (3.33.1)
|
||||
base64 (>= 0.2, < 0.4)
|
||||
@@ -703,7 +703,7 @@ GEM
|
||||
htmlentities (4.3.4)
|
||||
http-2 (1.1.3)
|
||||
http_parser.rb (0.8.1)
|
||||
httpx (1.7.4)
|
||||
httpx (1.7.5)
|
||||
http-2 (>= 1.1.3)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
@@ -744,9 +744,9 @@ GEM
|
||||
reline (>= 0.4.2)
|
||||
iso8601 (0.13.0)
|
||||
jmespath (1.6.2)
|
||||
job-iteration (1.12.0)
|
||||
activejob (>= 6.1)
|
||||
json (2.19.2)
|
||||
job-iteration (1.13.0)
|
||||
activejob (>= 7.0)
|
||||
json (2.19.3)
|
||||
json-jwt (1.17.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
@@ -821,7 +821,7 @@ GEM
|
||||
marcel (1.0.4)
|
||||
markly (0.15.2)
|
||||
matrix (0.4.3)
|
||||
mcp (0.8.0)
|
||||
mcp (0.9.2)
|
||||
json-schema (>= 4.1)
|
||||
messagebird-rest (5.0.0)
|
||||
jwt (< 4)
|
||||
@@ -835,7 +835,7 @@ GEM
|
||||
mini_magick (5.3.1)
|
||||
logger
|
||||
mini_mime (1.1.5)
|
||||
minitest (6.0.2)
|
||||
minitest (6.0.3)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.0)
|
||||
@@ -1091,17 +1091,17 @@ GEM
|
||||
ostruct (0.6.3)
|
||||
ox (2.14.23)
|
||||
bigdecimal (>= 3.0)
|
||||
pagy (43.4.1)
|
||||
pagy (43.4.4)
|
||||
json
|
||||
uri
|
||||
yaml
|
||||
paper_trail (17.0.0)
|
||||
activerecord (>= 7.1)
|
||||
request_store (~> 1.4)
|
||||
parallel (1.27.0)
|
||||
parallel (1.28.0)
|
||||
parallel_tests (4.10.1)
|
||||
parallel
|
||||
parser (3.3.10.2)
|
||||
parser (3.3.11.1)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pdf-core (0.9.0)
|
||||
@@ -1199,7 +1199,7 @@ GEM
|
||||
puma (>= 5.0, < 8)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (2.2.22)
|
||||
rack (2.2.23)
|
||||
rack-attack (6.8.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (2.0.2)
|
||||
@@ -1227,20 +1227,20 @@ GEM
|
||||
rackup (1.0.1)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (8.1.2.1)
|
||||
actioncable (= 8.1.2.1)
|
||||
actionmailbox (= 8.1.2.1)
|
||||
actionmailer (= 8.1.2.1)
|
||||
actionpack (= 8.1.2.1)
|
||||
actiontext (= 8.1.2.1)
|
||||
actionview (= 8.1.2.1)
|
||||
activejob (= 8.1.2.1)
|
||||
activemodel (= 8.1.2.1)
|
||||
activerecord (= 8.1.2.1)
|
||||
activestorage (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
rails (8.1.3)
|
||||
actioncable (= 8.1.3)
|
||||
actionmailbox (= 8.1.3)
|
||||
actionmailer (= 8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
actiontext (= 8.1.3)
|
||||
actionview (= 8.1.3)
|
||||
activejob (= 8.1.3)
|
||||
activemodel (= 8.1.3)
|
||||
activerecord (= 8.1.3)
|
||||
activestorage (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.1.2.1)
|
||||
railties (= 8.1.3)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
@@ -1255,9 +1255,9 @@ GEM
|
||||
rails-i18n (8.1.0)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 8.0.0, < 9)
|
||||
railties (8.1.2.1)
|
||||
actionpack (= 8.1.2.1)
|
||||
activesupport (= 8.1.2.1)
|
||||
railties (8.1.3)
|
||||
actionpack (= 8.1.3)
|
||||
activesupport (= 8.1.3)
|
||||
irb (~> 1.13)
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
@@ -1287,7 +1287,7 @@ GEM
|
||||
redis-client (>= 0.22.0)
|
||||
redis-client (0.28.0)
|
||||
connection_pool
|
||||
regexp_parser (2.11.3)
|
||||
regexp_parser (2.12.0)
|
||||
reline (0.6.3)
|
||||
io-console (~> 0.5)
|
||||
representable (3.2.0)
|
||||
@@ -1331,11 +1331,10 @@ GEM
|
||||
rspec-support (3.13.7)
|
||||
rspec-wait (1.0.2)
|
||||
rspec (>= 3.4)
|
||||
rubocop (1.85.1)
|
||||
rubocop (1.86.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
mcp (~> 0.6)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
@@ -1455,7 +1454,7 @@ GEM
|
||||
table_print (1.5.7)
|
||||
terminal-table (4.0.0)
|
||||
unicode-display_width (>= 1.1.1, < 4)
|
||||
test-prof (1.5.2)
|
||||
test-prof (1.6.0)
|
||||
text-hyphen (1.5.0)
|
||||
thor (1.5.0)
|
||||
thread_safe (0.3.6)
|
||||
@@ -1581,8 +1580,8 @@ DEPENDENCIES
|
||||
appsignal (~> 4.7)
|
||||
auto_strip_attributes (~> 2.5)
|
||||
awesome_nested_set (~> 3.9.0)
|
||||
aws-sdk-core (~> 3.241)
|
||||
aws-sdk-s3 (~> 1.213)
|
||||
aws-sdk-core (~> 3.244)
|
||||
aws-sdk-s3 (~> 1.217)
|
||||
axe-core-rspec
|
||||
bcrypt (~> 3.1.22)
|
||||
bootsnap (~> 1.23.0)
|
||||
@@ -1623,7 +1622,7 @@ DEPENDENCIES
|
||||
escape_utils (~> 1.3)
|
||||
factory_bot (~> 6.5.6)
|
||||
factory_bot_rails (~> 6.5.0)
|
||||
ffi (~> 1.15)
|
||||
ffi (~> 1.17)
|
||||
flamegraph
|
||||
fog-aws
|
||||
friendly_id (~> 5.6.0)
|
||||
@@ -1637,7 +1636,7 @@ DEPENDENCIES
|
||||
grids!
|
||||
html-pipeline (~> 2.14.0)
|
||||
htmldiff
|
||||
httpx (~> 1.7.4)
|
||||
httpx (~> 1.7.5)
|
||||
i18n-js (~> 4.2.4)
|
||||
i18n-tasks (~> 1.1.0)
|
||||
ice_cube (~> 0.17.0)
|
||||
@@ -1656,7 +1655,7 @@ DEPENDENCIES
|
||||
mail (= 2.9.0)
|
||||
markly (~> 0.15)
|
||||
matrix (~> 0.4.3)
|
||||
mcp (~> 0.8.0)
|
||||
mcp (~> 0.9.2)
|
||||
md_to_pdf!
|
||||
meta-tags (~> 2.23.0)
|
||||
mini_magick (~> 5.3.0)
|
||||
@@ -1722,7 +1721,7 @@ DEPENDENCIES
|
||||
rack-test (~> 2.2.0)
|
||||
rack-timeout (~> 0.7.0)
|
||||
rack_session_access
|
||||
rails (~> 8.1.2)
|
||||
rails (~> 8.1.3)
|
||||
rails-controller-testing (~> 1.0.2)
|
||||
rails-i18n (~> 8.1.0)
|
||||
rbtrace
|
||||
@@ -1771,7 +1770,7 @@ DEPENDENCIES
|
||||
svg-graph (~> 2.2.0)
|
||||
sys-filesystem (~> 1.5.0)
|
||||
table_print (~> 1.5.6)
|
||||
test-prof (~> 1.5.0)
|
||||
test-prof (~> 1.6.0)
|
||||
timecop (~> 0.9.0)
|
||||
ttfunk (~> 1.7.0)
|
||||
turbo-rails (~> 2.0.20)
|
||||
@@ -1794,24 +1793,24 @@ DEPENDENCIES
|
||||
|
||||
CHECKSUMS
|
||||
Ascii85 (2.0.1) sha256=15cb5d941808543cbb9e7e6aea3c8ec3877f154c3461e8b3673e97f7ecedbe5a
|
||||
action_text-trix (2.1.17) sha256=b44691639d77e67169dc054ceacd1edc04d44dc3e4c6a427aa155a2beb4cc951
|
||||
actioncable (8.1.2.1) sha256=a2f88cecce148b3fcb63d2e517d7694e119830a85baa7d6cf59e5453dcf32e8d
|
||||
actionmailbox (8.1.2.1) sha256=c2e45c0c1e5687e35e050838c94a8aed0d954c56a32ea411d54cd848c338c54e
|
||||
actionmailer (8.1.2.1) sha256=d7d62fbc2197f1a7006bb5af4c665edf999adf79ab6c10337c088d27e6622071
|
||||
actionpack (8.1.2.1) sha256=a6b69cd10ec4c8d978c8eee51206e34152b1c1be017e534236dbc89a3d00ffb8
|
||||
action_text-trix (2.1.18) sha256=3fdb83f8bff4145d098be283cdd47ac41caf5110bfa6df4695ed7127d7fb3642
|
||||
actioncable (8.1.3) sha256=e5bc7f75e44e6a22de29c4f43176927c3a9ce4824464b74ed18d8226e75a80f0
|
||||
actionmailbox (8.1.3) sha256=df7da474eaa0e70df4ed5a6fef66eb3b3b0f2dbf7f14518deee8d77f1b4aae59
|
||||
actionmailer (8.1.3) sha256=831f724891bb70d0aaa4d76581a6321124b6a752cb655c9346aae5479318448d
|
||||
actionpack (8.1.3) sha256=af998cae4d47c5d581a2cc363b5c77eb718b7c4b45748d81b1887b25621c29a3
|
||||
actionpack-xml_parser (2.0.1) sha256=40cb461ee99445314ab580a783fb7413580deb8b28113c9e70ecd7c1b334d5e6
|
||||
actiontext (8.1.2.1) sha256=1e503ce600a6ab2e12a46f999959a7d8e2fdaff910ca01dcf3b968934b55d957
|
||||
actionview (8.1.2.1) sha256=38daa7b87bca427e2967f139e5b7f0d1081271bdafd0e015d8ef97a006f570a6
|
||||
actiontext (8.1.3) sha256=d291019c00e1ea9e6463011fa214f6081a56d7b9a1d224e7d3f6384c1dafc7d2
|
||||
actionview (8.1.3) sha256=1347c88c7f3edb38100c5ce0e9fb5e62d7755f3edc1b61cce2eb0b2c6ea2fd5d
|
||||
active_record_doctor (2.0.1) sha256=7af0ac02195385c8f2f67d0e4ebe72b1fc79d65eaaf329e0db07f4d12a84069a
|
||||
activejob (8.1.2.1) sha256=c89c311d07fd358b76c581ed8fee87c5b4351fb44994f3389385c014d22182fe
|
||||
activemodel (8.1.2.1) sha256=8f31a6f9c12fecb8e5a0fce8a8950cfd94f0d75829322935f99e8217a3e5f3c6
|
||||
activejob (8.1.3) sha256=a149b1766aa8204c3c3da7309e4becd40fcd5529c348cffbf6c9b16b565fe8d3
|
||||
activemodel (8.1.3) sha256=90c05cbe4cef3649b8f79f13016191ea94c4525ce4a5c0fb7ef909c4b91c8219
|
||||
activemodel-serializers-xml (1.0.3) sha256=fa1b16305e7254cc58a59c68833e3c0a593a59c8ab95d3be5aaea7cd9416c397
|
||||
activerecord (8.1.2.1) sha256=3f79140318ff6d23376f5d9b1b5b5e2c7d3cc8979dd71367e9a8394378ca630a
|
||||
activerecord (8.1.3) sha256=8003be7b2466ba0a2a670e603eeb0a61dd66058fccecfc49901e775260ac70ab
|
||||
activerecord-import (2.2.0) sha256=f8ca99b196e50775723d1f1d192c379f656378dc9f5628240992a0d78807fa4b
|
||||
activerecord-nulldb-adapter (1.2.2) sha256=01e0b2e49af11ad56a92e274a3d8c9fb3c50a12a5460218c4c4b45355d9ef968
|
||||
activerecord-session_store (2.2.0) sha256=65918054573683bf4f87af89e765e1fece14c9d71cfac1f11abe4687c96e2743
|
||||
activestorage (8.1.2.1) sha256=36794c9b8853ac9276b0386cb1f8973374d8e71e8a9666bb02e70f5b7c9c5391
|
||||
activesupport (8.1.2.1) sha256=beec20ced12ad569194554399449a6372fdab03061b8f48a9ed6ef9b7dc251b2
|
||||
activestorage (8.1.3) sha256=0564ce9309143951a67615e1bb4e090ee54b8befed417133cae614479b46384d
|
||||
activesupport (8.1.3) sha256=21a5e0dfbd4c3ddd9e1317ec6a4d782fa226e7867dc70b0743acda81a1dca20e
|
||||
acts_as_list (1.2.6) sha256=8345380900b7bee620c07ad00991ccee59af3d8c9e8574f426e321da2865fdc8
|
||||
acts_as_tree (2.9.1) sha256=b869eb10a8de38616b64ffcf9e882d3d99c8e06909c4057078a76c3b89a9a2f3
|
||||
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
|
||||
@@ -1827,11 +1826,11 @@ CHECKSUMS
|
||||
auto_strip_attributes (2.6.0) sha256=a7e2e0cf744de2bcd947fd68014220702bcc88c81274c1cd9ce6f7316aae39b0
|
||||
awesome_nested_set (3.9.0) sha256=3ce99e816550f97f4de118e621630070aacf24928b920fe4a68846578a8daaed
|
||||
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
|
||||
aws-partitions (1.1227.0) sha256=122dd20fe108cb38d38cccbc1f2592408bc1b30ca6e0d05797a7af2501567e29
|
||||
aws-sdk-core (3.243.0) sha256=a014eef785124b71d28325783fa422a1512f8421ec9b6e3931c8b0ca3fbb0f1c
|
||||
aws-sdk-kms (1.122.0) sha256=47ce3f51b26bd7d76f1270cfdfca17b40073ecd3219c8c9400788712abfb4eb8
|
||||
aws-sdk-s3 (1.216.0) sha256=a3bf6191e6f7a3dfb04b7cc73409f059394be559e4aff92d2a764341e4d90af4
|
||||
aws-sdk-sns (1.112.0) sha256=aff1b1b5bbcb4229599221c558a41790c1cd1a1fed47ac3d27d27512ad24b254
|
||||
aws-partitions (1.1234.0) sha256=8f74aa6ca2e945fa50d83a23fc10f5406cb0becd32ac9f8940001ba8b04dcf8a
|
||||
aws-sdk-core (3.244.0) sha256=3e458c078b0c5bdee95bc370c3a483374b3224cf730c1f9f0faf849a5d9a18ea
|
||||
aws-sdk-kms (1.123.0) sha256=d405f37e82f8fa32045ca8980be266c0b45b37aaf2012afe0254321a1e811f20
|
||||
aws-sdk-s3 (1.217.0) sha256=6ea709272c666888b14e9c62345abd9a6a967759ae13667c28f01fde6823c24b
|
||||
aws-sdk-sns (1.113.0) sha256=15fe37d010e86f4c28b4c2f2133c463ce5c14189ec3673a1f43c30dfee511b0f
|
||||
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
|
||||
axe-core-api (4.11.1) sha256=a6460506449a692030620a0574fee7afa6cd38cfbbf6620d20bf4d53d33a80cc
|
||||
axe-core-rspec (4.11.1) sha256=dc6c0e166405cd3a28c4a0937f6521ee5b511c12c0ca1627144a1ee7d5014aec
|
||||
@@ -1840,7 +1839,7 @@ CHECKSUMS
|
||||
bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032
|
||||
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
|
||||
better_html (2.2.0) sha256=e68ab66ab09696b708333bbf35e8aa3c107500ba7892f528e2111624bdd8cf76
|
||||
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
||||
bigdecimal (4.1.1) sha256=1c09efab961da45203c8316b0cdaec0ff391dfadb952dd459584b63ebf8054ca
|
||||
bindata (2.5.1) sha256=53186a1ec2da943d4cb413583d680644eb810aacbf8902497aac8f191fad9e58
|
||||
bootsnap (1.23.0) sha256=c1254f458d58558b58be0f8eb8f6eec2821456785b7cdd1e16248e2020d3f214
|
||||
brakeman (8.0.4) sha256=7bf921fa9638544835df9aa7b3e720a9a72c0267f34f92135955edd80d4dcf6f
|
||||
@@ -1927,14 +1926,14 @@ CHECKSUMS
|
||||
faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
|
||||
fastimage (2.4.1) sha256=c64bebd46b6fd8943ab70c1e6e85ff728f970f2e48f92ecd249b6bc3a540ad20
|
||||
ferrum (0.17.1) sha256=51d591120fc593e5a13b5d9d6474389f5145bb92a91e36eab147b5d096c8cbe7
|
||||
ffi (1.17.3-aarch64-linux-gnu) sha256=28ad573df26560f0aedd8a90c3371279a0b2bd0b4e834b16a2baa10bd7a97068
|
||||
ffi (1.17.3-aarch64-linux-musl) sha256=020b33b76775b1abacc3b7d86b287cef3251f66d747092deec592c7f5df764b2
|
||||
ffi (1.17.3-arm-linux-gnu) sha256=5bd4cea83b68b5ec0037f99c57d5ce2dd5aa438f35decc5ef68a7d085c785668
|
||||
ffi (1.17.3-arm-linux-musl) sha256=0d7626bb96265f9af78afa33e267d71cfef9d9a8eb8f5525344f8da6c7d76053
|
||||
ffi (1.17.3-arm64-darwin) sha256=0c690555d4cee17a7f07c04d59df39b2fba74ec440b19da1f685c6579bb0717f
|
||||
ffi (1.17.3-x86_64-darwin) sha256=1f211811eb5cfaa25998322cdd92ab104bfbd26d1c4c08471599c511f2c00bb5
|
||||
ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f
|
||||
ffi (1.17.3-x86_64-linux-musl) sha256=086b221c3a68320b7564066f46fed23449a44f7a1935f1fe5a245bd89d9aea56
|
||||
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
||||
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
||||
ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564
|
||||
ffi (1.17.4-arm-linux-musl) sha256=9d4838ded0465bef6e2426935f6bcc93134b6616785a84ffd2a3d82bc3cf6f95
|
||||
ffi (1.17.4-arm64-darwin) sha256=19071aaf1419251b0a46852abf960e77330a3b334d13a4ab51d58b31a937001b
|
||||
ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
|
||||
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
||||
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
||||
flamegraph (0.9.5) sha256=a683020637ffa0e14a72640fa41babf14d926bfeaed87e31907cfd06ab2de8dc
|
||||
fog-aws (3.33.1) sha256=20c7336ed978be6cbf2765844c53f30676288af98f1cb49945aa7b7b45a799a5
|
||||
fog-core (2.6.0) sha256=3fe08aa83a23cddce42f4ba412040c08f890d7ff04c175c0ee59119371245be6
|
||||
@@ -1976,7 +1975,7 @@ CHECKSUMS
|
||||
htmlentities (4.3.4) sha256=125a73c6c9f2d1b62100b7c3c401e3624441b663762afa7fe428476435a673da
|
||||
http-2 (1.1.3) sha256=1b2f379d35a11dbae94f8a1a52c053d8c161eb4a0c98b5d1605ff1b2bf171c9c
|
||||
http_parser.rb (0.8.1) sha256=9ae8df145b39aa5398b2f90090d651c67bd8e2ebfe4507c966579f641e11097a
|
||||
httpx (1.7.4) sha256=91fb3e0f7325966a5da4d463a1dd7240e8550d8b0de79e346cc5dc1df1eacd2b
|
||||
httpx (1.7.5) sha256=f002b7728ad108b721ab9c4e02b17b5c780581d490cd13245fc3d5c5b7127b6e
|
||||
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
|
||||
i18n-js (4.2.4) sha256=61390d372f8fa68c495c5907d577657e8cc3a7031f4945db1e91f935e1391355
|
||||
i18n-tasks (1.1.2) sha256=4dcfba49e52a623f30661cb316cb80d84fbba5cb8c6d88ef5e02545fffa3637a
|
||||
@@ -1990,8 +1989,8 @@ CHECKSUMS
|
||||
irb (1.17.0) sha256=168c4ddb93d8a361a045c41d92b2952c7a118fa73f23fe14e55609eb7a863aae
|
||||
iso8601 (0.13.0) sha256=298c2b15b7be5fa95a1372813d36a2257656cd8e906dfbc1f5cb409851425aa2
|
||||
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
|
||||
job-iteration (1.12.0) sha256=0164057417750f6e9c3ed548f029f1136b18eb53975fa438b09304a525d6c6c0
|
||||
json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf
|
||||
job-iteration (1.13.0) sha256=3300844e81309fbd06fd2310d6aa8e1f43bf30fe03a3fc5067580b62f456b7e1
|
||||
json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
|
||||
json-jwt (1.17.0) sha256=6ff99026b4c54281a9431179f76ceb81faa14772d710ef6169785199caadc4cc
|
||||
json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666
|
||||
json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
|
||||
@@ -2014,7 +2013,7 @@ CHECKSUMS
|
||||
marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4
|
||||
markly (0.15.2) sha256=65dae965d4dd4ecd997fba43b93acc0fe7dadfec6f07a748640c7a9299a8551e
|
||||
matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b
|
||||
mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
|
||||
mcp (0.9.2) sha256=d413926f4f9dc1274f462196c2a9fbf9cb261ecd3c66a60c82eb71ee62bd2932
|
||||
md_to_pdf (0.2.6)
|
||||
messagebird-rest (5.0.0) sha256=da4cc1efba3d5e4aa021fad07426c2cb6b326ce5670da5104bb8f6056a39d59c
|
||||
meta-tags (2.23.0) sha256=ffe78b5bee398de4ff5ac3316f5a786049538a651643b8476def06c3acc762c1
|
||||
@@ -2023,7 +2022,7 @@ CHECKSUMS
|
||||
mime-types-data (3.2026.0317) sha256=77f078a4d8631d52b842ba77099734b06eddb7ad339d792e746d2272b67e511b
|
||||
mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4
|
||||
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||
minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d
|
||||
minitest (6.0.3) sha256=88ac8a1de36c00692420e7cb3cc11a0773bbcb126aee1c249f320160a7d11411
|
||||
msgpack (1.8.0) sha256=e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732
|
||||
multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7
|
||||
mustermann (3.0.4) sha256=85fadcb6b3c6493a8b511b42426f904b7f27b282835502233dd154daab13aa22
|
||||
@@ -2142,11 +2141,11 @@ CHECKSUMS
|
||||
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
|
||||
overviews (1.0.0)
|
||||
ox (2.14.23) sha256=4a9aedb4d6c78c5ebac1d7287dc7cc6808e14a8831d7adb727438f6a1b461b66
|
||||
pagy (43.4.1) sha256=a211fc64b2c396f84db08f8de7bf919e73732de9e9e170a33eec7960bbdd3822
|
||||
pagy (43.4.4) sha256=b41a57328a0aabfd222266a89e9de3dc3a735c17bd57f8113829c95fece5bef6
|
||||
paper_trail (17.0.0) sha256=1c2842061d3874ca7015908e821e2aa14f9b982af2acb2a7974713bf79021c85
|
||||
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
||||
parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970
|
||||
parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70
|
||||
parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357
|
||||
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
|
||||
pdf-core (0.9.0) sha256=4f368b2f12b57ec979872d4bf4bd1a67e8648e0c81ab89801431d2fc89f4e0bb
|
||||
pdf-inspector (1.3.0) sha256=fc107579d6f29b636e2da3d6743479b2624d9e390bf2d84beef8fd4ebe1a05bd
|
||||
pdf-reader (2.15.1) sha256=18c6a986a84a3117fa49f4279fc2de51f5d2399b71833df5d2bccd595c7068ce
|
||||
@@ -2181,7 +2180,7 @@ CHECKSUMS
|
||||
puma-plugin-statsd (2.7.0) sha256=04f243a7233f4d06ec0e26f1a3522bce18a5910ae711763fabff22681bdad08b
|
||||
raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882
|
||||
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
||||
rack (2.2.22) sha256=c5cf0b7f872559966d974abe3101a57d51caf12504ee76290b98720004f64542
|
||||
rack (2.2.23) sha256=a8fe9d7e07064770b8ec123663fded8a59ef7e2b6db5cda7173d45a5718ab69c
|
||||
rack-attack (6.8.0) sha256=f2499fdebf85bcc05573a22dff57d24305ac14ec2e4156cd3c28d47cafeeecf2
|
||||
rack-cors (2.0.2) sha256=415d4e1599891760c5dc9ef0349c7fecdf94f7c6a03e75b2e7c2b54b82adda1b
|
||||
rack-mini-profiler (4.0.1) sha256=485810c23211f908196c896ea10cad72ed68780ee2998bec1f1dfd7558263d78
|
||||
@@ -2192,12 +2191,12 @@ CHECKSUMS
|
||||
rack-timeout (0.7.0) sha256=757337e9793cca999bb73a61fe2a7d4280aa9eefbaf787ce3b98d860749c87d9
|
||||
rack_session_access (0.2.0) sha256=03eb98f2027429ccbbeb18556006dfb6d928b0557ad3770783b8e2f368198d6b
|
||||
rackup (1.0.1) sha256=ba86604a28989fe1043bff20d819b360944ca08156406812dca6742b24b3c249
|
||||
rails (8.1.2.1) sha256=93ebf1efc792c9bc47e9795259c920312d3920008dad3ae634b7a0457ffe0af8
|
||||
rails (8.1.3) sha256=6d017ba5348c98fc909753a8169b21d44de14d2a0b92d140d1a966834c3c9cd3
|
||||
rails-controller-testing (1.0.5) sha256=741448db59366073e86fc965ba403f881c636b79a2c39a48d0486f2607182e94
|
||||
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
|
||||
rails-html-sanitizer (1.7.0) sha256=28b145cceaf9cc214a9874feaa183c3acba036c9592b19886e0e45efc62b1e89
|
||||
rails-i18n (8.1.0) sha256=52d5fd6c0abef28d84223cc05647f6ae0fd552637a1ede92deee9545755b6cf3
|
||||
railties (8.1.2.1) sha256=f4d902869541af4e5b5552d726062fa59ec0fd9078f7ab87720dbd93f22c43ee
|
||||
railties (8.1.3) sha256=913eb0e0cb520aac687ffd74916bd726d48fa21f47833c6292576ef6a286de22
|
||||
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
||||
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
||||
rake-compiler-dock (1.11.0) sha256=eab51f2cd533eb35cea6b624a75281f047123e70a64c58b607471bb49428f8c2
|
||||
@@ -2211,7 +2210,7 @@ CHECKSUMS
|
||||
redcarpet (3.6.1) sha256=d444910e6aa55480c6bcdc0cdb057626e8a32c054c29e793fa642ba2f155f445
|
||||
redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae
|
||||
redis-client (0.28.0) sha256=888892f9cd8787a41c0ece00bdf5f556dfff7770326ce40bb2bc11f1bfec824b
|
||||
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
|
||||
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
|
||||
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
||||
representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
|
||||
request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb
|
||||
@@ -2230,7 +2229,7 @@ CHECKSUMS
|
||||
rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858
|
||||
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
||||
rspec-wait (1.0.2) sha256=865f921239325d3d26fc10ded4bdd485d8b58bcaaad1a28dd85ed15266b5a912
|
||||
rubocop (1.85.1) sha256=3dbcf9e961baa4c376eeeb2a03913dca5e3987033b04d38fa538aa1e7406cc77
|
||||
rubocop (1.86.0) sha256=4ff1186fe16ebe9baff5e7aad66bb0ad4cabf5cdcd419f773146dbba2565d186
|
||||
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
||||
rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c
|
||||
rubocop-factory_bot (2.28.0) sha256=4b17fc02124444173317e131759d195b0d762844a71a29fe8139c1105d92f0cb
|
||||
@@ -2280,7 +2279,7 @@ CHECKSUMS
|
||||
sys-filesystem (1.5.5) sha256=6f995890a734b9f0aa55df5e09d99adeb9fd1c288f2c4097269a1f8c95e15033
|
||||
table_print (1.5.7) sha256=436664281f93387b882335795e16cfeeb839ad0c785ff7f9110fc0f17c68b5cb
|
||||
terminal-table (4.0.0) sha256=f504793203f8251b2ea7c7068333053f0beeea26093ec9962e62ea79f94301d2
|
||||
test-prof (1.5.2) sha256=185839fb7d3745b3770ec48e3e5718eff9e28c327a50a1e18a3a9ef1060f8576
|
||||
test-prof (1.6.0) sha256=b04cee7b9684b9b31e23e258f835ba67ee5b4fd5f6cc7204d076ef695805c335
|
||||
text-hyphen (1.5.0) sha256=c44a4533b8a554e7ff7c955e131bcccc78a0b4c56ce1d73f2c8c11f43b075a06
|
||||
thor (1.5.0) sha256=e3a9e55fe857e44859ce104a84675ab6e8cd59c650a49106a05f55f136425e73
|
||||
thread_safe (0.3.6) sha256=9ed7072821b51c57e8d6b7011a8e282e25aeea3a4065eab326e43f66f063b05a
|
||||
@@ -2328,7 +2327,7 @@ CHECKSUMS
|
||||
zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
|
||||
|
||||
RUBY VERSION
|
||||
ruby 4.0.1
|
||||
ruby 4.0.2
|
||||
|
||||
BUNDLED WITH
|
||||
4.0.6
|
||||
4.0.9
|
||||
|
||||
@@ -50,7 +50,11 @@ module Admin::Import::Jira::ImportRuns
|
||||
return nil if imported_projects.none?
|
||||
|
||||
ids = imported_projects.pluck(:op_entity_id).map(&:to_s)
|
||||
helpers.projects_path(filters: [{ id: { operator: "=", values: ids } }].to_json)
|
||||
if ids.length == 1
|
||||
helpers.project_path(id: ids[0])
|
||||
else
|
||||
helpers.projects_path(filters: [{ id: { operator: "=", values: ids } }].to_json)
|
||||
end
|
||||
end
|
||||
|
||||
def imported_work_packages
|
||||
@@ -61,8 +65,13 @@ module Admin::Import::Jira::ImportRuns
|
||||
def imported_work_packages_url
|
||||
return nil if imported_work_packages.none?
|
||||
|
||||
project_ids = imported_projects.pluck(:op_entity_id).map(&:to_s)
|
||||
helpers.work_packages_path(query_props: { f: [{ n: "project", o: "=", v: project_ids }] }.to_json)
|
||||
wp_ids = imported_work_packages.pluck(:op_entity_id).map(&:to_s)
|
||||
if wp_ids.length == 1
|
||||
helpers.work_package_path(id: wp_ids[0])
|
||||
else
|
||||
project_ids = imported_projects.pluck(:op_entity_id).map(&:to_s)
|
||||
helpers.work_packages_path(query_props: { f: [{ n: "project", o: "=", v: project_ids }] }.to_json)
|
||||
end
|
||||
end
|
||||
|
||||
def imported_users
|
||||
|
||||
@@ -75,7 +75,7 @@ class IndividualPrincipalBaseFilterComponent < ApplicationComponent
|
||||
end
|
||||
|
||||
def base_query
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -93,7 +93,7 @@ class IndividualPrincipalBaseFilterComponent < ApplicationComponent
|
||||
# INSTANCE METHODS:
|
||||
|
||||
def filter_path
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def initially_visible?
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<%#-- 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::Alpha::Dialog.new(
|
||||
id: DIALOG_ID,
|
||||
title: t("my_account.notifications.project_specific_settings.dialog_title"),
|
||||
position: :right,
|
||||
test_selector: "project-specific-settings-form"
|
||||
)
|
||||
) do |d|
|
||||
d.with_body do
|
||||
primer_form_with(
|
||||
model: notification_setting,
|
||||
scope: :notification_setting,
|
||||
url: @form_url,
|
||||
method: edit_mode? ? :patch : :post,
|
||||
html: { id: FORM_ID },
|
||||
data: {
|
||||
turbo: false,
|
||||
controller: "show-when-checked",
|
||||
show_when_checked_visibility_class: "d-none"
|
||||
}
|
||||
) do |form|
|
||||
forms = [My::Notifications::ProjectAutocompleterForm.new(form, readonly: edit_mode?, user: @user)]
|
||||
forms << My::Notifications::ParticipatingForm.new(form, show_submit: false)
|
||||
forms << My::Notifications::DateAlertsForm.new(form, show_submit: false) if date_alerts_available?
|
||||
forms << My::Notifications::NonParticipatingForm.new(form, show_submit: false)
|
||||
render(Primer::Forms::FormList.new(*forms))
|
||||
end
|
||||
end
|
||||
d.with_footer(show_divider: true) do
|
||||
concat(render(Primer::Beta::Button.new(data: { "close-dialog-id": DIALOG_ID })) { t("button_cancel") })
|
||||
concat(render(Primer::Beta::Button.new(scheme: :primary, type: :submit, form: FORM_ID)) { t("button_save") })
|
||||
end
|
||||
end %>
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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 My
|
||||
module Notifications
|
||||
class ProjectSettingsDialogComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::FormHelpers
|
||||
|
||||
DIALOG_ID = "project-notification-settings-dialog"
|
||||
FORM_ID = "project-notification-settings-form"
|
||||
|
||||
def initialize(user:, form_url:, notification_setting: nil)
|
||||
super
|
||||
@user = user
|
||||
@form_url = form_url
|
||||
@provided_setting = notification_setting
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notification_setting
|
||||
@notification_setting ||= @provided_setting || @user.notification_settings.build
|
||||
end
|
||||
|
||||
def edit_mode?
|
||||
notification_setting.persisted?
|
||||
end
|
||||
|
||||
def date_alerts_available?
|
||||
EnterpriseToken.allows_to?(:date_alerts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,135 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
model: global_notification_setting,
|
||||
scope: :notification_setting,
|
||||
url: update_participating_url,
|
||||
method: :patch,
|
||||
data: { turbo: false, test_selector: "participating-form" }
|
||||
) do |form|
|
||||
render(My::Notifications::ParticipatingForm.new(form))
|
||||
end
|
||||
%>
|
||||
|
||||
<% if date_alerts_available? %>
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
model: global_notification_setting,
|
||||
scope: :notification_setting,
|
||||
url: update_date_alerts_url,
|
||||
method: :patch,
|
||||
data: {
|
||||
turbo: false,
|
||||
controller: "show-when-checked",
|
||||
show_when_checked_visibility_class: "d-none",
|
||||
test_selector: "date-alerts-form"
|
||||
}
|
||||
) do |form|
|
||||
render(My::Notifications::DateAlertsForm.new(form))
|
||||
end
|
||||
%>
|
||||
<% else %>
|
||||
<%= render(Primer::Beta::Subhead.new(mt: 3)) do |component|
|
||||
component.with_heading(size: :medium) { t("my_account.notifications.date_alerts.title") }
|
||||
end %>
|
||||
<%= render(EnterpriseEdition::BannerComponent.new(:date_alerts, variant: :inline)) %>
|
||||
<% end %>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
model: global_notification_setting,
|
||||
scope: :notification_setting,
|
||||
url: update_non_participating_url,
|
||||
method: :patch,
|
||||
data: { turbo: false, test_selector: "non-participating-form" }
|
||||
) do |form|
|
||||
render(My::Notifications::NonParticipatingForm.new(form))
|
||||
end
|
||||
%>
|
||||
|
||||
<%= render(Primer::BaseComponent.new(tag: :div, classes: "op-admin-settings-form-wrapper")) do %>
|
||||
<%= render(Primer::Beta::Subhead.new(mt: 3)) do |component|
|
||||
component.with_heading(size: :medium) { t("my_account.notifications.project_specific_settings.title") }
|
||||
end %>
|
||||
|
||||
<% if project_notification_settings.any? %>
|
||||
<%= render(Primer::Beta::BorderBox.new(mb: 3)) do |box| %>
|
||||
<% box.with_header { t("my_account.notifications.project_specific_settings.list_header") } %>
|
||||
<% project_notification_settings.each do |setting| %>
|
||||
<% box.with_row(test_selector: "project-specific-settings-list") do %>
|
||||
<%= flex_layout(justify_content: :space_between, align_items: :center, width: :full) do |flex| %>
|
||||
<%= flex.with_column do %>
|
||||
<span><%= setting.project.name %></span>
|
||||
<% end %>
|
||||
<%= flex.with_column do %>
|
||||
<%= render(Primer::Alpha::ActionMenu.new(test_selector: "project-specific-settings-list--action-menu")) do |menu|
|
||||
menu.with_show_button(
|
||||
scheme: :invisible,
|
||||
size: :small,
|
||||
icon: :"kebab-horizontal",
|
||||
"aria-label": t(:label_open_menu),
|
||||
tooltip_direction: :w
|
||||
)
|
||||
menu.with_item(
|
||||
label: t("button_edit"),
|
||||
href: edit_project_settings_url(setting.project_id),
|
||||
content_arguments: { data: { controller: "async-dialog" } }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :pencil)
|
||||
end
|
||||
menu.with_item(
|
||||
label: t("button_delete"),
|
||||
scheme: :danger,
|
||||
href: project_setting_url(setting.project_id),
|
||||
content_arguments: { data: { turbo_method: :delete, turbo_confirm: t("text_are_you_sure") } }
|
||||
) do |item|
|
||||
item.with_leading_visual_icon(icon: :trash)
|
||||
end
|
||||
end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= render(
|
||||
Primer::Beta::Button.new(
|
||||
tag: :a,
|
||||
href: new_project_settings_url,
|
||||
data: { controller: "async-dialog" },
|
||||
mb: 3
|
||||
)
|
||||
) do |b|
|
||||
b.with_leading_visual_icon(icon: :plus)
|
||||
helpers.t("my_account.notifications.project_specific_settings.add_button")
|
||||
end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,78 @@
|
||||
# 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 My
|
||||
module Notifications
|
||||
class ShowPageComponent < ApplicationComponent
|
||||
include OpPrimer::FormHelpers
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
attr_reader :global_notification_setting,
|
||||
:update_participating_url,
|
||||
:update_non_participating_url,
|
||||
:update_date_alerts_url,
|
||||
:new_project_settings_url,
|
||||
:project_notification_settings
|
||||
|
||||
def initialize(user:,
|
||||
global_notification_setting:,
|
||||
update_participating_url:,
|
||||
update_non_participating_url:,
|
||||
update_date_alerts_url:,
|
||||
new_project_settings_url:,
|
||||
edit_project_settings_url:,
|
||||
project_setting_url:)
|
||||
super
|
||||
|
||||
@user = user
|
||||
@global_notification_setting = global_notification_setting
|
||||
@update_participating_url = update_participating_url
|
||||
@update_non_participating_url = update_non_participating_url
|
||||
@update_date_alerts_url = update_date_alerts_url
|
||||
@new_project_settings_url = new_project_settings_url
|
||||
@edit_project_settings_url_builder = edit_project_settings_url
|
||||
@project_setting_url_builder = project_setting_url
|
||||
@project_notification_settings = user.notification_settings.where.not(project: nil).includes(:project)
|
||||
end
|
||||
|
||||
def edit_project_settings_url(project_id)
|
||||
@edit_project_settings_url_builder.call(project_id)
|
||||
end
|
||||
|
||||
def project_setting_url(project_id)
|
||||
@project_setting_url_builder.call(project_id)
|
||||
end
|
||||
|
||||
def date_alerts_available?
|
||||
EnterpriseToken.allows_to?(:date_alerts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
# 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 My
|
||||
module Notifications
|
||||
class ShowPageHeaderComponent < ApplicationComponent
|
||||
def call
|
||||
render(Primer::OpenProject::PageHeader.new) do |header|
|
||||
header.with_title { t("my_account.notifications_and_email.title") }
|
||||
header.with_breadcrumbs(
|
||||
[{ href: helpers.my_account_path, text: t(:label_my_account) },
|
||||
t("my_account.notifications_and_email.title")]
|
||||
)
|
||||
|
||||
helpers.render_tab_header_nav(header, tabs)
|
||||
end
|
||||
end
|
||||
|
||||
def tabs
|
||||
[
|
||||
{
|
||||
name: "notifications",
|
||||
path: helpers.my_notifications_path(tab: "notifications"),
|
||||
label: t("my_account.notifications_and_email.tabs.notifications"),
|
||||
data: { turbo: false }
|
||||
},
|
||||
{
|
||||
name: "reminders",
|
||||
path: helpers.my_notifications_path(tab: "reminders"),
|
||||
label: t("my_account.notifications_and_email.tabs.email_reminders"),
|
||||
data: { turbo: false }
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,111 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<div data-controller="my--daily-reminders">
|
||||
<div data-my--daily-reminders-target="list">
|
||||
<% @times.each do |time| %>
|
||||
<%=
|
||||
flex_layout(mb: 2, data: { "my--daily-reminders-target": "row" }) do |flex|
|
||||
flex.with_column do
|
||||
render(
|
||||
Primer::Alpha::Select.new(
|
||||
name: field_name,
|
||||
label: t("my_account.email_reminders.daily_reminders.time_slot_label"),
|
||||
visually_hide_label: true,
|
||||
data: { test_selector: "settings-daily-time" }
|
||||
)
|
||||
) do |select|
|
||||
time_options.each do |label, value|
|
||||
select.option(label:, value:, selected: selected_value_for(time) == value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
flex.with_column do
|
||||
render(
|
||||
Primer::Beta::IconButton.new(
|
||||
icon: :x,
|
||||
scheme: :invisible,
|
||||
type: :button,
|
||||
hidden: @times.size <= 1,
|
||||
aria: { label: t("my_account.email_reminders.daily_reminders.remove_time") },
|
||||
data: { action: "my--daily-reminders#removeTime", test_selector: "settings-daily-time--remove" }
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
%>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<template data-my--daily-reminders-target="rowTemplate">
|
||||
<%=
|
||||
flex_layout(mb: 2, data: { "my--daily-reminders-target": "row" }) do |flex|
|
||||
flex.with_column do
|
||||
render(
|
||||
Primer::Alpha::Select.new(
|
||||
name: field_name,
|
||||
label: t("my_account.email_reminders.daily_reminders.time_slot_label"),
|
||||
visually_hide_label: true,
|
||||
id: nil,
|
||||
data: { test_selector: "settings-daily-time" }
|
||||
)
|
||||
) do |select|
|
||||
time_options.each do |label, value|
|
||||
select.option(label:, value:)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
flex.with_column do
|
||||
render(
|
||||
Primer::Beta::IconButton.new(
|
||||
icon: :x,
|
||||
scheme: :invisible,
|
||||
type: :button,
|
||||
aria: { label: t("my_account.email_reminders.daily_reminders.remove_time") },
|
||||
data: { action: "my--daily-reminders#removeTime", test_selector: "settings-daily-time--remove" }
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
%>
|
||||
</template>
|
||||
|
||||
<%= render(
|
||||
Primer::Beta::Button.new(
|
||||
scheme: :invisible,
|
||||
type: :button,
|
||||
data: { action: "my--daily-reminders#addTime" }
|
||||
)
|
||||
) do |btn|
|
||||
btn.with_leading_visual_icon(icon: :plus)
|
||||
t("my_account.email_reminders.daily_reminders.add_time")
|
||||
end %>
|
||||
</div>
|
||||
@@ -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 My
|
||||
module Reminders
|
||||
class DailyTimesComponent < ApplicationComponent
|
||||
include OpPrimer::ComponentHelpers
|
||||
|
||||
def initialize(times:, scope:)
|
||||
super
|
||||
|
||||
@times = Array(times)
|
||||
@scope = scope
|
||||
end
|
||||
|
||||
def field_name
|
||||
"#{@scope}[times][]"
|
||||
end
|
||||
|
||||
def time_options
|
||||
(0..23).map do |hour|
|
||||
time = Time.utc(2000, 1, 1, hour)
|
||||
[I18n.l(time, format: :time), time.strftime("%H:00:00+00:00")]
|
||||
end
|
||||
end
|
||||
|
||||
def selected_value_for(time_str)
|
||||
Time.zone.parse(time_str.to_s).strftime("%H:00:00+00:00")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,98 @@
|
||||
<%#-- 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.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
model: @user.pref,
|
||||
scope: "pref[immediate_reminders]",
|
||||
url: update_url,
|
||||
method: :patch,
|
||||
data: { turbo: false, test_selector: "immediate-reminders-form" }
|
||||
) do |form|
|
||||
render(My::Reminders::ImmediateRemindersForm.new(form))
|
||||
end
|
||||
%>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
model: daily_reminders_form_model,
|
||||
scope: "pref[daily_reminders]",
|
||||
url: update_url,
|
||||
method: :patch,
|
||||
data: {
|
||||
turbo: false,
|
||||
controller: "show-when-checked",
|
||||
show_when_checked_visibility_class: "d-none",
|
||||
test_selector: "daily-reminders-form"
|
||||
}
|
||||
) do |form|
|
||||
render(My::Reminders::DailyRemindersForm.new(form))
|
||||
end
|
||||
%>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
model: @user.pref,
|
||||
scope: :pref,
|
||||
url: update_workdays_url,
|
||||
method: :patch,
|
||||
data: { turbo: false, test_selector: "workdays-form" }
|
||||
) do |form|
|
||||
render(My::Reminders::WorkdaysForm.new(form))
|
||||
end
|
||||
%>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
model: pause_reminders_form_model,
|
||||
scope: "pref[pause_reminders]",
|
||||
url: update_url,
|
||||
method: :patch,
|
||||
data: {
|
||||
turbo: false,
|
||||
controller: "show-when-checked",
|
||||
show_when_checked_visibility_class: "d-none",
|
||||
test_selector: "pause-reminders-form"
|
||||
}
|
||||
) do |form|
|
||||
render(My::Reminders::PauseRemindersForm.new(form))
|
||||
end
|
||||
%>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
model: global_notification_setting,
|
||||
scope: :notification_setting,
|
||||
url: update_email_alerts_url,
|
||||
method: :patch,
|
||||
data: { turbo: false }
|
||||
) do |form|
|
||||
render(My::Reminders::EmailAlertsForm.new(form))
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,66 @@
|
||||
# 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 My
|
||||
module Reminders
|
||||
class ShowPageComponent < ApplicationComponent
|
||||
include OpPrimer::FormHelpers
|
||||
|
||||
attr_reader :global_notification_setting, :update_url, :update_workdays_url, :update_email_alerts_url
|
||||
|
||||
def initialize(user:, global_notification_setting:, update_url:, update_workdays_url:, update_email_alerts_url:)
|
||||
super
|
||||
|
||||
@user = user
|
||||
@global_notification_setting = global_notification_setting
|
||||
@update_url = update_url
|
||||
@update_workdays_url = update_workdays_url
|
||||
@update_email_alerts_url = update_email_alerts_url
|
||||
end
|
||||
|
||||
def daily_reminders_form_model
|
||||
daily_reminders = @user.pref.daily_reminders
|
||||
My::Reminders::DailyRemindersForm::DailyRemindersFormModel.new(
|
||||
enabled: daily_reminders[:enabled],
|
||||
times: daily_reminders[:times]
|
||||
)
|
||||
end
|
||||
|
||||
def pause_reminders_form_model
|
||||
pause_reminders = @user.pref.pause_reminders
|
||||
My::Reminders::PauseRemindersForm::PauseRemindersFormModel.new(
|
||||
enabled: pause_reminders[:enabled],
|
||||
first_day: pause_reminders[:first_day],
|
||||
last_day: pause_reminders[:last_day]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -38,7 +38,7 @@ module OpPrimer
|
||||
super()
|
||||
|
||||
if !show_button && alt_text.blank?
|
||||
raise NotImplementedError, "alt_text must be provided when the button is shown conditionally"
|
||||
raise ArgumentError, "alt_text must be provided when the button is shown conditionally"
|
||||
|
||||
end
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ module OpPrimer
|
||||
end
|
||||
|
||||
def default_button_title
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def disabled?
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
size: :large,
|
||||
data: {
|
||||
hover_card_trigger_target: "trigger",
|
||||
hover_card_popover_id: sub_status_hover_card_id,
|
||||
hover_card_popover_template_id: sub_status_hover_card_id,
|
||||
test_selector: "op-portfolios--sub-status-bar"
|
||||
}
|
||||
)
|
||||
@@ -166,9 +166,8 @@
|
||||
<%=
|
||||
# Card that appears when hovering over the progress bar
|
||||
if render_sub_status_bar?
|
||||
content_tag(:div, class: "op-hover-card--hidden-container") do
|
||||
content_tag(:template, id: sub_status_hover_card_id) do
|
||||
flex_layout(
|
||||
id: sub_status_hover_card_id,
|
||||
classes: "op-portfolios--popover",
|
||||
data: {
|
||||
test_selector: "op-portfolios--hover-card-#{portfolio.id}"
|
||||
|
||||
@@ -51,3 +51,6 @@ $status_not-set: var(--progressBar-track-bgColor) // invisible background color
|
||||
@media screen and (max-width: $breakpoint-sm)
|
||||
margin-top: var(--base-size-16, 1rem)
|
||||
margin-bottom: var(--base-size-16, 1rem) !important
|
||||
|
||||
.op-hover-card:has(> .op-portfolios--popover)
|
||||
width: fit-content
|
||||
|
||||
@@ -32,7 +32,7 @@ module Projects
|
||||
module Concerns
|
||||
module IdentifierSuggestion
|
||||
def identifier_suggestion_data
|
||||
suggestion_mode = Setting::WorkPackageIdentifier.alphanumeric? ? "semantic" : "legacy"
|
||||
suggestion_mode = Setting::WorkPackageIdentifier.semantic? ? "semantic" : "classic"
|
||||
|
||||
{
|
||||
controller: "projects--identifier-suggestion",
|
||||
|
||||
+1
-2
@@ -50,9 +50,8 @@
|
||||
# As a courtesy to users, we show a hover card explaining why the toggle is disabled.
|
||||
if toggle_disabled?
|
||||
concat(
|
||||
content_tag(:div, class: "op-hover-card--hidden-container") do
|
||||
content_tag(:template, id: unique_hovercard_id) do
|
||||
flex_layout(
|
||||
id: unique_hovercard_id,
|
||||
classes: "op-project-custom-field--popover",
|
||||
data: {
|
||||
test_selector: "op-project-custom-field--hover-card-#{@project_custom_field.id}"
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ module Projects
|
||||
if toggle_disabled?
|
||||
# Add hover card that explains why this toggle switch is disabled
|
||||
data[:hover_card_trigger_target] = "trigger"
|
||||
data[:hover_card_popover_id] = unique_hovercard_id
|
||||
data[:hover_card_popover_template_id] = unique_hovercard_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+1
-2
@@ -56,9 +56,8 @@
|
||||
# As a courtesy to users, we show a hover card explaining why the toggle is disabled.
|
||||
if toggle_disabled?
|
||||
concat(
|
||||
content_tag(:div, class: "op-hover-card--hidden-container") do
|
||||
content_tag(:template, id: unique_hovercard_id) do
|
||||
flex_layout(
|
||||
id: unique_hovercard_id,
|
||||
classes: "op-project-custom-field--popover",
|
||||
data: {
|
||||
test_selector: "op-project-custom-field--hover-card-#{@project_custom_field.id}"
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ module Projects
|
||||
if toggle_disabled?
|
||||
# Add hover card that explains why this toggle switch is disabled
|
||||
data[:hover_card_trigger_target] = "trigger"
|
||||
data[:hover_card_popover_id] = unique_hovercard_id
|
||||
data[:hover_card_popover_template_id] = unique_hovercard_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@
|
||||
)
|
||||
|
||||
dialog.with_additional_details(display: :none) do
|
||||
hidden_field_tag("settings[work_packages_identifier]", Setting::WorkPackageIdentifier::ALPHANUMERIC)
|
||||
hidden_field_tag("settings[work_packages_identifier]", Setting::WorkPackageIdentifier::SEMANTIC)
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
@@ -64,7 +64,7 @@ module WorkPackages
|
||||
def form_id = "wp-identifier-settings-form"
|
||||
|
||||
def show_autofix_section?
|
||||
state == :edit && Setting::WorkPackageIdentifier.alphanumeric? && has_problematic_projects?
|
||||
state == :edit && Setting::WorkPackageIdentifier.semantic? && has_problematic_projects?
|
||||
end
|
||||
|
||||
def change_in_progress? = state == :change_in_progress
|
||||
|
||||
@@ -40,7 +40,7 @@ module WorkPackages
|
||||
end
|
||||
|
||||
def format
|
||||
raise NotImplementedError, "Must be overridden in subclass"
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def export_settings
|
||||
|
||||
@@ -122,7 +122,7 @@ module Projects
|
||||
end
|
||||
|
||||
def manage_permission
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def with_unchanged_id
|
||||
|
||||
@@ -48,7 +48,7 @@ module Shares
|
||||
end
|
||||
|
||||
def user_allowed_to_manage?
|
||||
raise NotImplementedError, "Must be overridden by subclass"
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def single_non_inherited_role
|
||||
@@ -76,7 +76,7 @@ module Shares
|
||||
end
|
||||
|
||||
def assignable_role_class
|
||||
raise NotImplementedError, "Must be overridden by subclass"
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -227,7 +227,7 @@ module Admin
|
||||
end
|
||||
|
||||
def find_custom_field
|
||||
raise NotImplementedError, "SubclassResponsibility"
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def find_active_item
|
||||
|
||||
@@ -136,7 +136,7 @@ module Admin
|
||||
end
|
||||
|
||||
def enumeration_class
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def enumeration_permitted_params
|
||||
|
||||
@@ -51,11 +51,11 @@ module CustomFields
|
||||
end
|
||||
|
||||
def show_path
|
||||
raise NotImplementedError, "#{self.class} must implement #show_path"
|
||||
raise SubclassResponsibilityError, "#{self.class} must implement #show_path"
|
||||
end
|
||||
|
||||
def render_attribute_help_text_form(status: :ok)
|
||||
raise NotImplementedError, "#{self.class} must implement #render_attribute_help_text_form"
|
||||
raise SubclassResponsibilityError, "#{self.class} must implement #render_attribute_help_text_form"
|
||||
end
|
||||
|
||||
def find_or_initialize_attribute_help_text
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# 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 Notifications
|
||||
module NotificationSettingsActions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include OpTurbo::ComponentStream
|
||||
end
|
||||
|
||||
def update_workdays
|
||||
call = ::Users::UpdateService.new(model: @user, user: current_user).call(pref: workdays_pref_params)
|
||||
flash[call.success? ? :notice : :error] = update_service_flash_message(call)
|
||||
redirect_back_or_to(workdays_redirect_path)
|
||||
end
|
||||
|
||||
def new_project_settings
|
||||
respond_with_dialog My::Notifications::ProjectSettingsDialogComponent.new(
|
||||
user: @user,
|
||||
form_url: project_notifications_create_url
|
||||
)
|
||||
end
|
||||
|
||||
def create_project_settings
|
||||
update_project_notification_setting
|
||||
redirect_back_or_to(notifications_settings_path)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
flash[:error] = t(:notice_bad_request)
|
||||
redirect_back_or_to(notifications_settings_path)
|
||||
end
|
||||
|
||||
def edit_project_settings
|
||||
setting = @user.notification_settings.find_by!(project_id: params[:project_id])
|
||||
respond_with_dialog My::Notifications::ProjectSettingsDialogComponent.new(
|
||||
user: @user,
|
||||
notification_setting: setting,
|
||||
form_url: project_setting_form_url(setting.project_id)
|
||||
)
|
||||
end
|
||||
|
||||
def update_project_settings
|
||||
update_project_notification_setting(params[:project_id])
|
||||
redirect_back_or_to(notifications_settings_path)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
flash[:error] = t(:notice_bad_request)
|
||||
redirect_back_or_to(notifications_settings_path)
|
||||
end
|
||||
|
||||
def destroy_project_settings
|
||||
@user.notification_settings.find_by!(project_id: params[:project_id]).destroy!
|
||||
flash[:notice] = I18n.t(:notice_successful_delete)
|
||||
redirect_back_or_to(notifications_settings_path)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
flash[:error] = t(:notice_bad_request)
|
||||
redirect_back_or_to(notifications_settings_path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_project_notification_setting(project_id = params.dig(:notification_setting, :project_id))
|
||||
project = Project.find(project_id)
|
||||
setting = @user.notification_settings.find_or_initialize_by(project:)
|
||||
persist_notification_setting(setting, project_notification_params)
|
||||
end
|
||||
|
||||
def persist_notification_setting(setting, update_params)
|
||||
if setting.update(update_params)
|
||||
flash[:notice] = I18n.t(:notice_successful_update)
|
||||
else
|
||||
flash[:error] = I18n.t(:notice_failed_to_save_messages,
|
||||
count: setting.errors.count,
|
||||
object: setting.class.model_name.human)
|
||||
end
|
||||
end
|
||||
|
||||
def project_notification_params
|
||||
permitted_params.notification_setting_project.except(:project_id).merge(build_date_alerts_params)
|
||||
end
|
||||
|
||||
def build_date_alerts_params
|
||||
ns_params = params.fetch(:notification_setting, {})
|
||||
{
|
||||
start_date: date_alert_value(ns_params, :start_date),
|
||||
due_date: date_alert_value(ns_params, :due_date),
|
||||
overdue: date_alert_value(ns_params, :overdue)
|
||||
}
|
||||
end
|
||||
|
||||
def workdays_pref_params
|
||||
pref_params = permitted_params.pref.to_h
|
||||
pref_params.merge("workdays" => pref_params.fetch("workdays", []))
|
||||
end
|
||||
|
||||
def update_service_flash_message(call)
|
||||
if call.success?
|
||||
I18n.t(:notice_successful_update)
|
||||
else
|
||||
call.errors.full_messages.join(", ")
|
||||
end
|
||||
end
|
||||
|
||||
def date_alert_value(ns_params, field)
|
||||
return nil unless ns_params["#{field}_active"] == "1"
|
||||
|
||||
ns_params[field.to_s].presence&.to_i
|
||||
end
|
||||
|
||||
# To be implemented by the including controller
|
||||
def notifications_settings_path
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def workdays_redirect_path
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def project_notifications_create_url
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def project_setting_form_url(_project_id)
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -34,6 +34,7 @@ class MyController < ApplicationController
|
||||
include ActionView::Helpers::TagHelper
|
||||
include OpTurbo::ComponentStream
|
||||
include FlashMessagesOutputSafetyHelper
|
||||
include Notifications::NotificationSettingsActions
|
||||
|
||||
layout "my"
|
||||
|
||||
@@ -46,20 +47,28 @@ class MyController < ApplicationController
|
||||
:locale,
|
||||
:interface,
|
||||
:update_settings,
|
||||
:update_workdays,
|
||||
:update_email_alerts,
|
||||
:update_participating,
|
||||
:update_non_participating,
|
||||
:update_date_alerts,
|
||||
:password,
|
||||
:change_password,
|
||||
:password_confirmation_dialog,
|
||||
:notifications,
|
||||
:reminders,
|
||||
:non_working_times,
|
||||
:working_hours
|
||||
:working_hours,
|
||||
:new_project_settings,
|
||||
:create_project_settings,
|
||||
:edit_project_settings,
|
||||
:update_project_settings,
|
||||
:destroy_project_settings
|
||||
|
||||
menu_item :account, only: [:account]
|
||||
menu_item :locale, only: [:locale]
|
||||
menu_item :interface, only: [:interface]
|
||||
menu_item :password, only: [:password]
|
||||
menu_item :notifications, only: [:notifications]
|
||||
menu_item :reminders, only: [:reminders]
|
||||
menu_item :working_hours, only: %i[working_hours non_working_times]
|
||||
|
||||
def account; end
|
||||
@@ -74,6 +83,22 @@ class MyController < ApplicationController
|
||||
write_settings
|
||||
end
|
||||
|
||||
def update_email_alerts
|
||||
update_global_notification_setting(permitted_params.notification_setting_email_alerts)
|
||||
end
|
||||
|
||||
def update_participating
|
||||
update_global_notification_setting(permitted_params.notification_setting_participating)
|
||||
end
|
||||
|
||||
def update_non_participating
|
||||
update_global_notification_setting(permitted_params.notification_setting_non_participating)
|
||||
end
|
||||
|
||||
def update_date_alerts
|
||||
update_global_notification_setting(build_date_alerts_params)
|
||||
end
|
||||
|
||||
def interface; end
|
||||
|
||||
# Manage user's password
|
||||
@@ -93,11 +118,10 @@ class MyController < ApplicationController
|
||||
respond_with_dialog My::PasswordConfirmationDialog.new
|
||||
end
|
||||
|
||||
# Configure user's in app notifications
|
||||
def notifications; end
|
||||
|
||||
# Configure user's mail reminders
|
||||
def reminders; end
|
||||
# Configure user's notifications and email reminders
|
||||
def notifications
|
||||
set_global_notification_setting
|
||||
end
|
||||
|
||||
def working_hours
|
||||
render_403 unless OpenProject::FeatureDecisions.user_working_times_active?
|
||||
@@ -158,10 +182,25 @@ class MyController < ApplicationController
|
||||
end
|
||||
|
||||
def user_params
|
||||
# The Users::UpdateService updates the user's pref using the UserPreferences::UpdateService
|
||||
# which has a contract/schema applied to the values which is why it is ok
|
||||
# to blindly allow all scalar values in pref.
|
||||
permitted_params.user.to_h.merge(params.permit(pref: {}))
|
||||
permitted_params.my_account_settings.to_h
|
||||
end
|
||||
|
||||
def update_global_notification_setting(update_params)
|
||||
set_global_notification_setting
|
||||
persist_notification_setting(@global_notification_setting, update_params)
|
||||
redirect_back_or_to(my_notifications_path)
|
||||
end
|
||||
|
||||
def set_global_notification_setting
|
||||
@global_notification_setting = @user.notification_settings.find_or_initialize_by(project: nil)
|
||||
end
|
||||
|
||||
def persist_notification_setting(setting, update_params)
|
||||
if setting.update(update_params)
|
||||
flash[:notice] = notice_account_updated
|
||||
else
|
||||
flash[:error] = error_account_update_failed(nil)
|
||||
end
|
||||
end
|
||||
|
||||
def notice_account_updated
|
||||
@@ -175,6 +214,22 @@ class MyController < ApplicationController
|
||||
[t(:notice_account_update_failed), errors]
|
||||
end
|
||||
|
||||
def notifications_settings_path
|
||||
my_notifications_path
|
||||
end
|
||||
|
||||
def workdays_redirect_path
|
||||
my_notifications_path
|
||||
end
|
||||
|
||||
def project_notifications_create_url
|
||||
my_project_notifications_path
|
||||
end
|
||||
|
||||
def project_setting_form_url(project_id)
|
||||
my_project_setting_path(project_id:)
|
||||
end
|
||||
|
||||
def set_current_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
@@ -38,15 +38,9 @@ module ScimV2
|
||||
rescue_from "ActiveRecord::RecordNotFound", with: :handle_resource_not_found
|
||||
|
||||
def index
|
||||
query = if params[:filter].blank?
|
||||
storage_scope
|
||||
else
|
||||
attribute_map = storage_class.new.scim_queryable_attributes
|
||||
parser = ::Scimitar::Lists::QueryParser.new(attribute_map)
|
||||
|
||||
parser.parse(params[:filter])
|
||||
parser.to_activerecord_query(storage_scope)
|
||||
end
|
||||
# Applies .distinct to avoid duplicate records caused by
|
||||
# left_joins in storage_scope (e.g. groups, auth provider links).
|
||||
query = scim_index_storage_query.distinct
|
||||
|
||||
pagination_info = scim_pagination_info(query.count)
|
||||
page_of_results = query
|
||||
@@ -75,19 +69,37 @@ module ScimV2
|
||||
|
||||
private
|
||||
|
||||
def include_attributes
|
||||
first_level_attrs = storage_class.scim_attributes_map.keys.map(&:to_s)
|
||||
second_level_attrs =
|
||||
storage_class
|
||||
.scim_attributes_map
|
||||
.find_all { |_, v| v.is_a? Hash }
|
||||
.flat_map { |parent, childs| childs.map { |child, _| "#{parent}.#{child}" } }
|
||||
all_possible_attributes = (first_level_attrs + second_level_attrs)
|
||||
# Builds the base query for the SCIM index action,
|
||||
# applying any SCIM filter params if present.
|
||||
def scim_index_storage_query
|
||||
return storage_scope if params[:filter].blank?
|
||||
|
||||
# Returns the list of SCIM attributes to include in the response,
|
||||
# excluding any attributes specified in the excludedAttributes param.
|
||||
attribute_map = storage_class.new.scim_queryable_attributes
|
||||
parser = ::Scimitar::Lists::QueryParser.new(attribute_map)
|
||||
|
||||
parser.parse(params[:filter])
|
||||
parser.to_activerecord_query(storage_scope)
|
||||
end
|
||||
|
||||
def include_attributes
|
||||
# Collects all possible SCIM attribute names (top-level and nested)
|
||||
# from the storage class's scim_attributes_map.
|
||||
excluded_attributes = params.fetch(:excludedAttributes, "").split(",")
|
||||
excluded_parents = excluded_attributes.filter_map { |attr| attr.split(".")[-2] }
|
||||
|
||||
all_possible_attributes - excluded_attributes - excluded_parents
|
||||
scim_all_attribute_names - excluded_attributes - excluded_parents
|
||||
end
|
||||
|
||||
def scim_all_attribute_names
|
||||
map = storage_class.scim_attributes_map
|
||||
nested =
|
||||
map
|
||||
.find_all { |_, v| v.is_a? Hash }
|
||||
.flat_map { |parent, childs| childs.map { |child, _| "#{parent}.#{child}" } }
|
||||
|
||||
map.keys.map(&:to_s) + nested
|
||||
end
|
||||
|
||||
def raise_result_errors_for_scim(result)
|
||||
|
||||
@@ -31,19 +31,33 @@
|
||||
class UsersController < ApplicationController
|
||||
include OpTurbo::ComponentStream
|
||||
include WorkingTimesAuthorization
|
||||
include Notifications::NotificationSettingsActions
|
||||
|
||||
layout "admin"
|
||||
|
||||
before_action :authorize_global, except: %i[show deletion_info destroy]
|
||||
|
||||
# rubocop:disable Rails/LexicallyScopedActionFilter
|
||||
before_action :find_user, only: %i[show
|
||||
edit
|
||||
update
|
||||
update_reminders
|
||||
update_workdays
|
||||
update_email_alerts
|
||||
update_participating
|
||||
update_non_participating
|
||||
update_date_alerts
|
||||
new_project_settings
|
||||
create_project_settings
|
||||
edit_project_settings
|
||||
update_project_settings
|
||||
destroy_project_settings
|
||||
change_status_info
|
||||
change_status
|
||||
destroy
|
||||
deletion_info
|
||||
resend_invitation]
|
||||
# rubocop:enable Rails/LexicallyScopedActionFilter
|
||||
# should also contain destroy but post data can not be redirected
|
||||
before_action :require_login, only: [:deletion_info]
|
||||
before_action :authorize_for_user, only: [:destroy]
|
||||
@@ -109,6 +123,30 @@ class UsersController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def update_email_alerts
|
||||
global_setting = @user.notification_settings.find_or_initialize_by(project: nil)
|
||||
persist_notification_setting(global_setting, permitted_params.notification_setting_email_alerts)
|
||||
redirect_back_or_to edit_user_path(@user, tab: "reminders")
|
||||
end
|
||||
|
||||
def update_reminders
|
||||
call = ::Users::UpdateService.new(model: @user, user: current_user).call(pref: permitted_params.pref.to_h)
|
||||
flash[call.success? ? :notice : :error] = update_service_flash_message(call)
|
||||
redirect_back_or_to edit_user_path(@user, tab: "reminders")
|
||||
end
|
||||
|
||||
def update_participating
|
||||
update_user_notification_setting(permitted_params.notification_setting_participating)
|
||||
end
|
||||
|
||||
def update_non_participating
|
||||
update_user_notification_setting(permitted_params.notification_setting_non_participating)
|
||||
end
|
||||
|
||||
def update_date_alerts
|
||||
update_user_notification_setting(build_date_alerts_params)
|
||||
end
|
||||
|
||||
def update # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
||||
update_params = build_user_update_params
|
||||
call = ::Users::UpdateService.new(model: @user, user: current_user).call(update_params)
|
||||
@@ -261,6 +299,28 @@ class UsersController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def update_user_notification_setting(update_params)
|
||||
global_setting = @user.notification_settings.find_or_initialize_by(project: nil)
|
||||
persist_notification_setting(global_setting, update_params)
|
||||
redirect_back_or_to edit_user_path(@user, tab: "notifications")
|
||||
end
|
||||
|
||||
def notifications_settings_path
|
||||
edit_user_path(@user, tab: "notifications")
|
||||
end
|
||||
|
||||
def workdays_redirect_path
|
||||
edit_user_path(@user, tab: "reminders")
|
||||
end
|
||||
|
||||
def project_notifications_create_url
|
||||
project_notifications_user_path(@user)
|
||||
end
|
||||
|
||||
def project_setting_form_url(project_id)
|
||||
project_setting_user_path(@user, project_id:)
|
||||
end
|
||||
|
||||
def can_show_user?
|
||||
return true if can_manage_or_create_users?
|
||||
return true if @user == User.current
|
||||
|
||||
@@ -33,7 +33,8 @@ class WorkPackages::MovesController < ApplicationController
|
||||
|
||||
default_search_scope :work_packages
|
||||
before_action :find_work_packages, :check_project_uniqueness
|
||||
before_action :authorize
|
||||
before_action :authorize_move_or_copy
|
||||
authorization_checked! :new, :create
|
||||
|
||||
def new
|
||||
prepare_for_work_package_move
|
||||
@@ -47,6 +48,11 @@ class WorkPackages::MovesController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def authorize_move_or_copy
|
||||
permission = params.has_key?(:copy) ? :copy_work_packages : :move_work_packages
|
||||
do_authorize(permission)
|
||||
end
|
||||
|
||||
def perform_operation
|
||||
if within_frontend_treshold?
|
||||
perform_in_frontend
|
||||
|
||||
@@ -69,7 +69,7 @@ module CustomFields::CustomFieldRendering
|
||||
end
|
||||
|
||||
def custom_fields
|
||||
raise NotImplementedError, "#custom_fields method needs to be overwritten and provide all custom fields we want to show"
|
||||
raise SubclassResponsibilityError, "#custom_fields needs to be overwritten and provide all custom fields we want to show"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -49,7 +49,7 @@ class CustomFields::Inputs::Base::Autocomplete::MultiValueInput < CustomFields::
|
||||
end
|
||||
|
||||
def decorated?
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def custom_values
|
||||
|
||||
@@ -49,6 +49,6 @@ class CustomFields::Inputs::Base::Autocomplete::SingleValueInput < CustomFields:
|
||||
end
|
||||
|
||||
def decorated?
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,13 +30,15 @@
|
||||
|
||||
class My::AlertsForm < ApplicationForm
|
||||
form do |f|
|
||||
f.check_box name: :warn_on_leaving_unsaved,
|
||||
label: I18n.t("activerecord.attributes.user_preference.warn_on_leaving_unsaved")
|
||||
f.fieldset_group(title: helpers.t("activerecord.attributes.user_preference.header_alerts"), mt: 3) do |fg|
|
||||
fg.check_box name: :warn_on_leaving_unsaved,
|
||||
label: helpers.t("activerecord.attributes.user_preference.warn_on_leaving_unsaved")
|
||||
|
||||
f.check_box name: :auto_hide_popups,
|
||||
label: I18n.t("activerecord.attributes.user_preference.auto_hide_popups"),
|
||||
caption: I18n.t("activerecord.attributes.user_preference.auto_hide_popups_caption")
|
||||
fg.check_box name: :auto_hide_popups,
|
||||
label: helpers.t("activerecord.attributes.user_preference.auto_hide_popups"),
|
||||
caption: helpers.t("activerecord.attributes.user_preference.auto_hide_popups_caption")
|
||||
|
||||
f.submit(name: :submit, label: I18n.t("activerecord.attributes.user_preference.button_update_alerts"), scheme: :default)
|
||||
fg.submit(name: :submit, label: helpers.t("activerecord.attributes.user_preference.button_update_alerts"), scheme: :default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,53 +32,55 @@ class My::LookAndFeelForm < ApplicationForm
|
||||
include ApplicationHelper
|
||||
|
||||
form do |f|
|
||||
f.select_list(
|
||||
name: :theme,
|
||||
label: attribute_name(:theme),
|
||||
caption: attribute_name(:mode_guideline),
|
||||
required: true,
|
||||
include_blank: false,
|
||||
input_width: :small,
|
||||
data: {
|
||||
my__look_and_feel_target: "themeSelect",
|
||||
action: "my--look-and-feel#updateContrastOptions"
|
||||
}
|
||||
) do |select|
|
||||
theme_options_for_select.each { |(label, value)| select.option(value:, label:) }
|
||||
f.fieldset_group(title: helpers.t("activerecord.attributes.user_preference.header_look_and_feel")) do |fg|
|
||||
fg.select_list(
|
||||
name: :theme,
|
||||
label: attribute_name(:theme),
|
||||
caption: attribute_name(:mode_guideline),
|
||||
required: true,
|
||||
include_blank: false,
|
||||
input_width: :small,
|
||||
data: {
|
||||
my__look_and_feel_target: "themeSelect",
|
||||
action: "my--look-and-feel#updateContrastOptions"
|
||||
}
|
||||
) do |select|
|
||||
theme_options_for_select.each { |(label, value)| select.option(value:, label:) }
|
||||
end
|
||||
|
||||
fg.check_box_group(data: { my__look_and_feel_target: "autoThemeContrast" }) do |group|
|
||||
group.check_box name: :force_light_theme_contrast,
|
||||
label: attribute_name(:force_light_theme_contrast),
|
||||
caption: attribute_name(:force_light_theme_contrast_caption)
|
||||
group.check_box name: :force_dark_theme_contrast,
|
||||
label: attribute_name(:force_dark_theme_contrast),
|
||||
caption: attribute_name(:force_dark_theme_contrast_caption)
|
||||
end
|
||||
|
||||
fg.check_box_group(data: { my__look_and_feel_target: "singleThemeContrast" }) do |group|
|
||||
group.check_box name: :increase_theme_contrast,
|
||||
label: attribute_name(:increase_contrast),
|
||||
caption: attribute_name(:increase_contrast_caption)
|
||||
end
|
||||
|
||||
fg.select_list(
|
||||
name: :comments_sorting,
|
||||
label: attribute_name(:comments_sorting),
|
||||
required: true,
|
||||
include_blank: false,
|
||||
input_width: :small
|
||||
) do |select|
|
||||
comment_sort_order_options.each { |(label, value)| select.option(value:, label:) }
|
||||
end
|
||||
|
||||
fg.check_box name: :disable_keyboard_shortcuts,
|
||||
label: attribute_name(:disable_keyboard_shortcuts),
|
||||
caption: disable_keyboard_shortcuts_caption
|
||||
|
||||
fg.submit(name: :submit,
|
||||
label: attribute_name(:button_update_look_and_feel),
|
||||
scheme: :default)
|
||||
end
|
||||
|
||||
f.check_box_group(data: { my__look_and_feel_target: "autoThemeContrast" }) do |group|
|
||||
group.check_box name: :force_light_theme_contrast,
|
||||
label: attribute_name(:force_light_theme_contrast),
|
||||
caption: attribute_name(:force_light_theme_contrast_caption)
|
||||
group.check_box name: :force_dark_theme_contrast,
|
||||
label: attribute_name(:force_dark_theme_contrast),
|
||||
caption: attribute_name(:force_dark_theme_contrast_caption)
|
||||
end
|
||||
|
||||
f.check_box_group(data: { my__look_and_feel_target: "singleThemeContrast" }) do |group|
|
||||
group.check_box name: :increase_theme_contrast,
|
||||
label: attribute_name(:increase_contrast),
|
||||
caption: attribute_name(:increase_contrast_caption)
|
||||
end
|
||||
|
||||
f.select_list(
|
||||
name: :comments_sorting,
|
||||
label: attribute_name(:comments_sorting),
|
||||
required: true,
|
||||
include_blank: false,
|
||||
input_width: :small
|
||||
) do |select|
|
||||
comment_sort_order_options.each { |(label, value)| select.option(value:, label:) }
|
||||
end
|
||||
|
||||
f.check_box name: :disable_keyboard_shortcuts,
|
||||
label: attribute_name(:disable_keyboard_shortcuts),
|
||||
caption: disable_keyboard_shortcuts_caption
|
||||
|
||||
f.submit(name: :submit,
|
||||
label: attribute_name(:button_update_look_and_feel),
|
||||
scheme: :default)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
# 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 My::Notifications::DateAlertsForm < ApplicationForm
|
||||
START_DUE_TIMES = %w[0 1 3 7].freeze
|
||||
OVERDUE_TIMES = %w[1 3 7].freeze
|
||||
|
||||
def initialize(show_submit: true)
|
||||
super()
|
||||
@show_submit = show_submit
|
||||
end
|
||||
|
||||
form do |f|
|
||||
f.fieldset_group(title: helpers.t("my_account.notifications.date_alerts.title"), mt: 3) do |fg|
|
||||
%i[start_date due_date].each do |field|
|
||||
active = model.send(:"#{field}_active")
|
||||
|
||||
fg.check_box(
|
||||
name: :"#{field}_active",
|
||||
label: helpers.t("my_account.notifications.date_alerts.#{field}"),
|
||||
id: "op-notification-type-#{field}-date-active--#{SecureRandom.uuid}}",
|
||||
data: {
|
||||
show_when_checked_target: "cause",
|
||||
target_name: field.to_s,
|
||||
test_selector: "global-notification-type-op-settings-#{field}-date-active"
|
||||
}
|
||||
) do |cb|
|
||||
cb.nested_form(
|
||||
classes: [{ "d-none" => !active }],
|
||||
data: {
|
||||
show_when_checked_target: "effect",
|
||||
target_name: field.to_s,
|
||||
show_when: "checked"
|
||||
}
|
||||
) do |builder|
|
||||
TimeSelectForm.new(builder, field:, times: START_DUE_TIMES)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
active_overdue = model.overdue_active
|
||||
|
||||
fg.check_box(
|
||||
name: :overdue_active,
|
||||
label: helpers.t("my_account.notifications.date_alerts.overdue"),
|
||||
id: "op-notification-type-overdue-date-active--#{SecureRandom.uuid}}",
|
||||
data: {
|
||||
show_when_checked_target: "cause",
|
||||
target_name: "overdue",
|
||||
test_selector: "global-notification-type-op-settings-overdue-date-active"
|
||||
}
|
||||
) do |cb|
|
||||
cb.nested_form(
|
||||
classes: [{ "d-none" => !active_overdue }],
|
||||
data: {
|
||||
show_when_checked_target: "effect",
|
||||
target_name: "overdue",
|
||||
show_when: "checked"
|
||||
}
|
||||
) do |builder|
|
||||
TimeSelectForm.new(builder, field: :overdue, times: OVERDUE_TIMES)
|
||||
end
|
||||
end
|
||||
|
||||
if @show_submit
|
||||
fg.submit(name: :submit, label: helpers.t("my_account.notifications.date_alerts.submit_button"),
|
||||
scheme: :default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TimeSelectForm < ApplicationForm
|
||||
def initialize(field:, times:)
|
||||
super()
|
||||
@field = field
|
||||
@times = times
|
||||
end
|
||||
|
||||
form do |f|
|
||||
f.select_list(
|
||||
name: @field,
|
||||
label: helpers.t("my_account.notifications.date_alerts.#{@field}"),
|
||||
visually_hide_label: true,
|
||||
input_width: :xsmall,
|
||||
data: { test_selector: "global-notification-type-op-reminder-settings-#{@field.to_s.underscore}-alerts" }
|
||||
) do |list|
|
||||
@times.each do |value|
|
||||
list.option(
|
||||
label: helpers.t("my_account.notifications.date_alerts.times.#{time_key(value)}"),
|
||||
value:
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def time_key(value)
|
||||
case value
|
||||
when "0" then "same_day"
|
||||
when "1" then @field == :overdue ? "one_day_after" : "one_day_before"
|
||||
when "3" then @field == :overdue ? "three_days_after" : "three_days_before"
|
||||
when "7" then @field == :overdue ? "seven_days_after" : "seven_days_before"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class My::Notifications::NonParticipatingForm < ApplicationForm
|
||||
def initialize(show_submit: true)
|
||||
super()
|
||||
@show_submit = show_submit
|
||||
end
|
||||
|
||||
form do |f|
|
||||
f.fieldset_group(title: helpers.t("my_account.notifications.non_participating.title"), mt: 3) do |fg|
|
||||
NotificationSetting.non_participating_settings.each do |setting|
|
||||
fg.check_box(
|
||||
name: setting,
|
||||
label: helpers.t("my_account.notifications.non_participating.#{setting}"),
|
||||
data: { test_selector: "global-notification-type-#{setting}" },
|
||||
id: "op-notification-type-#{setting}--#{SecureRandom.uuid}}"
|
||||
)
|
||||
end
|
||||
|
||||
if @show_submit
|
||||
fg.submit(name: :submit, label: helpers.t("my_account.notifications.non_participating.submit_button"),
|
||||
scheme: :default)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
# 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 My::Notifications::ParticipatingForm < ApplicationForm
|
||||
def initialize(show_submit: true)
|
||||
super()
|
||||
@show_submit = show_submit
|
||||
end
|
||||
|
||||
form do |f|
|
||||
f.fieldset_group(title: helpers.t("my_account.notifications.participating.title"), mt: 2) do |fg|
|
||||
fg.check_box(
|
||||
name: :mentioned,
|
||||
label: helpers.t("my_account.notifications.participating.mentioned"),
|
||||
disabled: true,
|
||||
data: { test_selector: "global-notification-type-mentioned" },
|
||||
id: "op-notification-mentioned--#{SecureRandom.uuid}}"
|
||||
)
|
||||
fg.check_box(
|
||||
name: :watched,
|
||||
label: helpers.t("my_account.notifications.participating.watched"),
|
||||
disabled: true,
|
||||
data: { test_selector: "global-notification-type-watched" },
|
||||
id: "op-notification-watched--#{SecureRandom.uuid}}"
|
||||
)
|
||||
fg.check_box(
|
||||
name: :assignee,
|
||||
label: helpers.t("my_account.notifications.participating.assignee"),
|
||||
data: { test_selector: "global-notification-type-assignee" },
|
||||
id: "op-notification-assignee--#{SecureRandom.uuid}}"
|
||||
)
|
||||
fg.check_box(
|
||||
name: :responsible,
|
||||
label: helpers.t("my_account.notifications.participating.responsible"),
|
||||
data: { test_selector: "global-notification-type-responsible" },
|
||||
id: "op-notification-responsible--#{SecureRandom.uuid}}"
|
||||
)
|
||||
fg.check_box(
|
||||
name: :shared,
|
||||
label: helpers.t("my_account.notifications.participating.shared"),
|
||||
data: { test_selector: "global-notification-type-shared" },
|
||||
id: "op-notification-shared--#{SecureRandom.uuid}}"
|
||||
)
|
||||
|
||||
if @show_submit
|
||||
fg.submit(name: :submit, label: helpers.t("my_account.notifications.participating.submit_button"),
|
||||
scheme: :default)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
# 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 My::Notifications::ProjectAutocompleterForm < ApplicationForm
|
||||
def initialize(readonly: false, user: nil)
|
||||
super()
|
||||
@readonly = readonly
|
||||
@excluded_project_ids = if !readonly && user
|
||||
user.notification_settings.where.not(project: nil).pluck(:project_id).map(&:to_s)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
form do |f|
|
||||
filters = [{ name: "active", operator: "=", values: ["t"] }]
|
||||
filters << { name: "id", operator: "!", values: @excluded_project_ids } if @excluded_project_ids.any?
|
||||
|
||||
f.project_autocompleter(
|
||||
name: :project_id,
|
||||
label: Project.model_name.human,
|
||||
required: true,
|
||||
autocomplete_options: {
|
||||
data: { test_selector: "my-notifications-project-autocompleter" },
|
||||
appendTo: "##{My::Notifications::ProjectSettingsDialogComponent::DIALOG_ID}",
|
||||
readonly: @readonly,
|
||||
filters:
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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 My::Reminders::DailyRemindersForm < ApplicationForm
|
||||
DailyRemindersFormModel = Data.define(:enabled, :times)
|
||||
|
||||
form do |f|
|
||||
f.fieldset_group(title: helpers.t("my_account.email_reminders.daily_reminders.title"), mt: 3) do |fg|
|
||||
fg.check_box(
|
||||
name: :enabled,
|
||||
label: helpers.t("my_account.email_reminders.daily_reminders.enabled"),
|
||||
data: {
|
||||
target_name: "daily-enabled",
|
||||
show_when_checked_target: "cause"
|
||||
}
|
||||
) do |cb|
|
||||
cb.nested_form(
|
||||
classes: [{ "d-none" => !model.enabled }],
|
||||
data: {
|
||||
target_name: "daily-enabled",
|
||||
show_when_checked_target: "effect",
|
||||
show_when: "checked"
|
||||
}
|
||||
) do |times_builder|
|
||||
My::Reminders::DailyTimesComponent.new(
|
||||
times: model.times || [],
|
||||
scope: times_builder.object_name
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
fg.submit(name: :submit, label: helpers.t("button_save"), scheme: :default)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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 My::Reminders::EmailAlertsForm < ApplicationForm
|
||||
form do |f|
|
||||
f.fieldset_group(title: helpers.t("my_account.email_reminders.email_alerts.title"), mt: 3) do |fg|
|
||||
NotificationSetting.email_settings.each do |setting|
|
||||
fg.check_box(
|
||||
name: setting,
|
||||
label: helpers.t("my_account.email_reminders.email_alerts.#{setting}")
|
||||
)
|
||||
end
|
||||
|
||||
fg.submit(name: :submit, label: helpers.t("my_account.email_reminders.email_alerts.submit_button"), scheme: :default)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
# 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 My::Reminders::ImmediateRemindersForm < ApplicationForm
|
||||
form do |f|
|
||||
f.fieldset_group(title: helpers.t("my_account.email_reminders.immediate_reminders.title"), mt: 2) do |fg|
|
||||
fg.check_box(
|
||||
name: :mentioned,
|
||||
label: helpers.t("my_account.email_reminders.immediate_reminders.mentioned"),
|
||||
data: { test_selector: "immediate-reminder-mentioned" }
|
||||
)
|
||||
fg.check_box(
|
||||
name: :personal_reminder,
|
||||
label: helpers.t("my_account.email_reminders.immediate_reminders.personal_reminder"),
|
||||
data: { test_selector: "immediate-reminder-personal_reminder" }
|
||||
)
|
||||
|
||||
fg.submit(name: :submit, label: helpers.t("button_save"), scheme: :default)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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 My::Reminders::PauseRemindersForm < ApplicationForm
|
||||
PauseRemindersFormModel = Data.define(:enabled, :first_day, :last_day)
|
||||
|
||||
form do |f|
|
||||
f.fieldset_group(title: helpers.t("my_account.email_reminders.pause_reminders.title"), mt: 3) do |fg|
|
||||
fg.check_box(
|
||||
name: :enabled,
|
||||
id: "pause-reminders-enabled",
|
||||
label: helpers.t("my_account.email_reminders.pause_reminders.enabled"),
|
||||
data: {
|
||||
target_name: "pause-enabled",
|
||||
show_when_checked_target: "cause"
|
||||
}
|
||||
) do |cb|
|
||||
cb.nested_form(
|
||||
classes: [{ "d-none" => !model.enabled }],
|
||||
data: {
|
||||
target_name: "pause-enabled",
|
||||
show_when_checked_target: "effect",
|
||||
show_when: "checked"
|
||||
}
|
||||
) do |builder|
|
||||
DateRangeForm.new(builder)
|
||||
end
|
||||
end
|
||||
|
||||
fg.submit(name: :submit, label: helpers.t("button_save"), scheme: :default)
|
||||
end
|
||||
end
|
||||
|
||||
class DateRangeForm < ApplicationForm
|
||||
form do |f|
|
||||
f.range_date_picker(
|
||||
name: :date_range,
|
||||
visually_hide_label: true,
|
||||
input_width: :small,
|
||||
label: helpers.t("my_account.email_reminders.pause_reminders.date_range"),
|
||||
value: [model.first_day, model.last_day].compact_blank.join(" - ")
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
# 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 My::Reminders::WorkdaysForm < ApplicationForm
|
||||
WEEKDAY_NUMBERS = [1, 2, 3, 4, 5, 6, 7].freeze
|
||||
|
||||
form do |f|
|
||||
f.fieldset_group(title: helpers.t("my_account.email_reminders.workdays.title"), mt: 3) do |fg|
|
||||
fg.check_box_group(name: :workdays) do |group|
|
||||
WEEKDAY_NUMBERS.each do |day_num|
|
||||
day_label = helpers.t("date.day_names")[day_num % 7]
|
||||
group.check_box(label: day_label, value: day_num)
|
||||
end
|
||||
end
|
||||
|
||||
fg.submit(name: :submit, label: helpers.t("my_account.email_reminders.workdays.submit_button"), scheme: :default)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -31,7 +31,7 @@ module Projects
|
||||
module Settings
|
||||
class EditableIdentifierForm < ApplicationForm
|
||||
form do |f|
|
||||
if Setting::WorkPackageIdentifier.alphanumeric?
|
||||
if Setting::WorkPackageIdentifier.semantic?
|
||||
f.text_field(
|
||||
name: :identifier,
|
||||
label: attribute_name(:identifier),
|
||||
|
||||
@@ -31,7 +31,7 @@ module Projects
|
||||
module Settings
|
||||
class IdentifierForm < ApplicationForm
|
||||
form do |f|
|
||||
caption_key = if Setting::WorkPackageIdentifier.alphanumeric?
|
||||
caption_key = if Setting::WorkPackageIdentifier.semantic?
|
||||
:text_project_identifier_description
|
||||
else
|
||||
:text_project_identifier_url_description
|
||||
|
||||
@@ -72,7 +72,7 @@ class Submenu
|
||||
end
|
||||
|
||||
def default_queries
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def global_queries
|
||||
@@ -150,7 +150,7 @@ class Submenu
|
||||
end
|
||||
|
||||
def query_path(query_params)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def url_helpers
|
||||
|
||||
@@ -77,11 +77,11 @@ module Activities
|
||||
end
|
||||
|
||||
def event_data(journal)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def event_title(journal, data)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -80,15 +80,15 @@ class AttributeHelpText < ApplicationRecord
|
||||
end
|
||||
|
||||
def type_caption
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def self.visible_condition(_user = nil)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def self.available_attributes
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class AuthProvider < ApplicationRecord
|
||||
end
|
||||
|
||||
def human_type
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def auth_url
|
||||
|
||||
@@ -34,7 +34,7 @@ module Capabilities::Scopes
|
||||
|
||||
class_methods do
|
||||
def visible(user = User.current)
|
||||
scope = if user.admin?
|
||||
scope = if user.active_admin?
|
||||
all
|
||||
else
|
||||
where(context_id: nil)
|
||||
|
||||
@@ -42,7 +42,7 @@ class CustomActions::Actions::Base
|
||||
end
|
||||
|
||||
def allowed_values
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def value_objects
|
||||
@@ -52,11 +52,11 @@ class CustomActions::Actions::Base
|
||||
end
|
||||
|
||||
def type
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def apply(_work_package)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def human_name
|
||||
@@ -64,7 +64,7 @@ class CustomActions::Actions::Base
|
||||
end
|
||||
|
||||
def self.key
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def self.all
|
||||
|
||||
@@ -35,7 +35,7 @@ class CustomActions::Actions::CustomField < CustomActions::Actions::Base
|
||||
end
|
||||
|
||||
def custom_field
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def all
|
||||
|
||||
@@ -67,7 +67,7 @@ class CustomActions::Conditions::Base
|
||||
end
|
||||
|
||||
def self.key
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def validate(errors)
|
||||
|
||||
@@ -34,9 +34,13 @@ module CustomFields::Scopes
|
||||
|
||||
class_methods do
|
||||
def visible(user = User.current)
|
||||
known_subclasses
|
||||
.inject(none) do |scope, klass|
|
||||
scope.or(where(type: klass.name).and(klass.visible(user)))
|
||||
if user.active_admin?
|
||||
all
|
||||
else
|
||||
known_subclasses
|
||||
.inject(none) do |scope, klass|
|
||||
scope.or(where(type: klass.name).and(klass.visible(user)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class CustomValue::ARObjectStrategy < CustomValue::FormatStrategy
|
||||
end
|
||||
|
||||
def ar_class
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def ar_object(value)
|
||||
|
||||
@@ -42,7 +42,7 @@ class CustomValue::FormatStrategy
|
||||
# Returns the value of the CustomValue in a typed fashion (i.e. not as the string
|
||||
# that is used for representation in the database)
|
||||
def typed_value
|
||||
raise "SubclassResponsibility"
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
# Returns the value of the CustomValue formatted to a string
|
||||
@@ -59,6 +59,6 @@ class CustomValue::FormatStrategy
|
||||
# Validates the type of the custom field and returns a symbol indicating the validation error
|
||||
# if an error occurred; returns nil if no error occurred
|
||||
def validate_type_of_value
|
||||
raise "SubclassResponsibility"
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
|
||||
@@ -63,7 +63,7 @@ module Exports
|
||||
|
||||
# Run the export, yielding the result of the render output
|
||||
def export!
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
@@ -34,7 +34,7 @@ module GroupCustomFields::Scopes
|
||||
|
||||
class_methods do
|
||||
def visible(user = User.current)
|
||||
if user.admin?
|
||||
if user.active_admin?
|
||||
all
|
||||
else
|
||||
where(admin_only: false)
|
||||
|
||||
@@ -35,7 +35,7 @@ module Members::Scopes
|
||||
class_methods do
|
||||
# Find all members visible to the inquiring user
|
||||
def visible(user = User.current)
|
||||
if user.admin?
|
||||
if user.active_admin?
|
||||
visible_for_admins
|
||||
else
|
||||
visible_for_non_admins(user)
|
||||
|
||||
@@ -68,6 +68,16 @@ class NotificationSetting < ApplicationRecord
|
||||
]
|
||||
end
|
||||
|
||||
def self.non_participating_settings
|
||||
[
|
||||
WORK_PACKAGE_CREATED,
|
||||
WORK_PACKAGE_COMMENTED,
|
||||
WORK_PACKAGE_PROCESSED,
|
||||
WORK_PACKAGE_PRIORITIZED,
|
||||
WORK_PACKAGE_SCHEDULED
|
||||
]
|
||||
end
|
||||
|
||||
def self.date_alert_settings
|
||||
[
|
||||
START_DATE,
|
||||
@@ -94,4 +104,18 @@ class NotificationSetting < ApplicationRecord
|
||||
|
||||
include Scopes::Scoped
|
||||
scopes :applicable
|
||||
|
||||
# rubocop:disable Naming/PredicateMethod
|
||||
def start_date_active
|
||||
start_date.present?
|
||||
end
|
||||
|
||||
def due_date_active
|
||||
due_date.present?
|
||||
end
|
||||
|
||||
def overdue_active
|
||||
overdue.present?
|
||||
end
|
||||
# rubocop:enable Naming/PredicateMethod
|
||||
end
|
||||
|
||||
@@ -199,6 +199,10 @@ class PermittedParams
|
||||
params.require(:placeholder_user).permit(*self.class.permitted_attributes[:placeholder_user])
|
||||
end
|
||||
|
||||
def my_account_settings
|
||||
user.merge(pref:)
|
||||
end
|
||||
|
||||
def user_register_via_omniauth
|
||||
permitted_params = params
|
||||
.require(:user)
|
||||
@@ -274,7 +278,31 @@ class PermittedParams
|
||||
:comments_sorting,
|
||||
:disable_keyboard_shortcuts,
|
||||
:warn_on_leaving_unsaved,
|
||||
:auto_hide_popups)
|
||||
:auto_hide_popups,
|
||||
immediate_reminders: %i[mentioned personal_reminder],
|
||||
daily_reminders: [:enabled, { times: [] }],
|
||||
workdays: [],
|
||||
pause_reminders: %i[enabled date_range])
|
||||
end
|
||||
|
||||
def notification_setting_email_alerts
|
||||
params.fetch(:notification_setting, {}).permit(*NotificationSetting.email_settings)
|
||||
end
|
||||
|
||||
def notification_setting_participating
|
||||
params.fetch(:notification_setting, {}).permit(:assignee, :responsible, :shared)
|
||||
end
|
||||
|
||||
def notification_setting_non_participating
|
||||
params.fetch(:notification_setting, {}).permit(*NotificationSetting.non_participating_settings)
|
||||
end
|
||||
|
||||
def notification_setting_project
|
||||
params.fetch(:notification_setting, {}).permit(
|
||||
:project_id,
|
||||
:assignee, :responsible, :shared,
|
||||
*NotificationSetting.non_participating_settings
|
||||
)
|
||||
end
|
||||
|
||||
def project
|
||||
|
||||
@@ -137,7 +137,7 @@ class Principal < ApplicationRecord
|
||||
|
||||
# Columns required for formatting the principal's name.
|
||||
def self.columns_for_name(formatter = nil)
|
||||
raise NotImplementedError, "Redefine in subclass" unless self == Principal
|
||||
raise SubclassResponsibilityError, "Redefine in subclass" unless self == Principal
|
||||
|
||||
[User, Group, PlaceholderUser].map { it.columns_for_name(formatter) }.inject(:|)
|
||||
end
|
||||
|
||||
@@ -42,7 +42,7 @@ module Principals::Scopes
|
||||
|
||||
class_methods do
|
||||
def visible(user = ::User.current)
|
||||
if user.allowed_globally?(:view_all_principals) || user.admin?
|
||||
if user.allowed_globally?(:view_all_principals)
|
||||
all
|
||||
else
|
||||
in_visible_project_or_me_or_same_groups(user)
|
||||
|
||||
@@ -39,6 +39,7 @@ class Project < ApplicationRecord
|
||||
include Projects::WorkPackageCustomFields
|
||||
include Projects::CreationWizard
|
||||
include Projects::Identifier
|
||||
include Projects::SemanticIdentifier
|
||||
|
||||
include ::Scopes::Scoped
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class ProjectCustomField < CustomField
|
||||
|
||||
class << self
|
||||
def visible(user = User.current, project: nil)
|
||||
if user.admin?
|
||||
if user.active_admin?
|
||||
all
|
||||
elsif user.allowed_in_any_project?(:select_project_custom_fields) || user.allowed_globally?(:add_project)
|
||||
where(admin_only: false)
|
||||
|
||||
@@ -34,7 +34,7 @@ module ProjectCustomFields::Scopes
|
||||
|
||||
class_methods do
|
||||
def visible(user = User.current, project: nil)
|
||||
if user.admin?
|
||||
if user.active_admin?
|
||||
all
|
||||
elsif user.allowed_in_any_project?(:select_project_custom_fields) || user.allowed_globally?(:add_project)
|
||||
where(admin_only: false)
|
||||
|
||||
@@ -53,36 +53,30 @@ module Projects::Identifier
|
||||
limit: IDENTIFIER_MAX_LENGTH,
|
||||
blacklist: RESERVED_IDENTIFIERS,
|
||||
adapter: OpenProject::ActsAsUrl::Adapter::OpActiveRecord, # use a custom adapter able to handle edge cases
|
||||
skip_if: -> { Setting::WorkPackageIdentifier.alphanumeric? }
|
||||
skip_if: -> { Setting::WorkPackageIdentifier.semantic? }
|
||||
|
||||
# Generate semantic identifier (when in the semantic mode)
|
||||
before_validation :generate_semantic_identifier,
|
||||
on: :create,
|
||||
if: -> { Setting::WorkPackageIdentifier.alphanumeric? && identifier.blank? }
|
||||
if: -> { Setting::WorkPackageIdentifier.semantic? && identifier.blank? }
|
||||
|
||||
### ID validators
|
||||
# Validators for the legacy underscored identifier format (e.g. "project_one")
|
||||
# Shared validators for all identifier formats
|
||||
validates :identifier,
|
||||
presence: true,
|
||||
uniqueness: { case_sensitive: true },
|
||||
uniqueness: { case_sensitive: false },
|
||||
length: { maximum: IDENTIFIER_MAX_LENGTH },
|
||||
exclusion: RESERVED_IDENTIFIERS,
|
||||
if: ->(p) { p.persisted? || p.identifier.present? }
|
||||
# Contains only a-z, 0-9, dashes and underscores but cannot consist of numbers only as it would clash with the id.
|
||||
validates :identifier,
|
||||
format: { with: /\A(?!^\d+\z)[a-z0-9\-_]+\z/ },
|
||||
if: ->(p) {
|
||||
p.identifier_changed? && p.identifier.present? && Setting::WorkPackageIdentifier.numeric?
|
||||
}
|
||||
|
||||
# Validators for the semantic identifier format
|
||||
validates :identifier,
|
||||
format: { with: /\A[A-Z]/, message: :must_start_with_letter },
|
||||
if: ->(p) { p.identifier_changed? && p.identifier.present? && Setting::WorkPackageIdentifier.alphanumeric? }
|
||||
validates :identifier,
|
||||
format: { with: /\A[A-Z][A-Z0-9_]*\z/, message: :no_special_characters },
|
||||
length: { maximum: SEMANTIC_IDENTIFIER_MAX_LENGTH },
|
||||
if: ->(p) { p.identifier_changed? && p.identifier.present? && Setting::WorkPackageIdentifier.alphanumeric? }
|
||||
# Validators for the numeric (legacy) identifier format (e.g. "my-project", "project_one")
|
||||
validate :identifier_numeric_format,
|
||||
if: ->(p) { p.identifier_changed? && p.identifier.present? && Setting::WorkPackageIdentifier.classic? }
|
||||
|
||||
# Validators for the semantic (alphanumeric) identifier format (e.g. "PROJ1")
|
||||
validate :identifier_alphanumeric_format,
|
||||
if: ->(p) { p.identifier_changed? && p.identifier.present? && Setting::WorkPackageIdentifier.semantic? }
|
||||
|
||||
validate :identifier_not_reserved, if: -> { identifier.present? }
|
||||
|
||||
# Complements the uniqueness validation above: once an identifier has been used by a
|
||||
# project, it remains reserved for that project even after the project moves to a new
|
||||
@@ -102,7 +96,7 @@ module Projects::Identifier
|
||||
|
||||
class_methods do
|
||||
def suggest_identifier(name)
|
||||
if Setting::WorkPackageIdentifier.alphanumeric?
|
||||
if Setting::WorkPackageIdentifier.semantic?
|
||||
WorkPackages::IdentifierAutofix::ProjectIdentifierSuggestionGenerator.suggest_identifier(name)
|
||||
else # This should closely enough emulate Project models' usage of acts_as_url
|
||||
name.to_url.first(IDENTIFIER_MAX_LENGTH).presence || "project"
|
||||
@@ -130,14 +124,38 @@ module Projects::Identifier
|
||||
|
||||
private
|
||||
|
||||
# Contains only a-z, 0-9, dashes and underscores but cannot consist of numbers only
|
||||
# as that would clash with the numeric id.
|
||||
def identifier_numeric_format
|
||||
unless identifier.match?(/\A(?!^\d+\z)[a-z0-9\-_]+\z/)
|
||||
errors.add(:identifier, :invalid)
|
||||
end
|
||||
end
|
||||
|
||||
def identifier_alphanumeric_format
|
||||
errors.add(:identifier, :must_start_with_letter) unless identifier.match?(/\A[A-Z]/)
|
||||
errors.add(:identifier, :no_special_characters) unless identifier.match?(/\A[A-Z0-9_]*\z/)
|
||||
if identifier.length > SEMANTIC_IDENTIFIER_MAX_LENGTH
|
||||
errors.add(:identifier, :too_long, count: SEMANTIC_IDENTIFIER_MAX_LENGTH)
|
||||
end
|
||||
end
|
||||
|
||||
def identifier_not_reserved
|
||||
if RESERVED_IDENTIFIERS.include?(identifier&.downcase)
|
||||
errors.add(:identifier, :exclusion)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks friendly_id_slugs for any project that previously used this identifier and
|
||||
# has since changed it. It allows to switch back to an identifier the project itself
|
||||
# has used before.
|
||||
# has since changed it. It allows a project to switch back to an identifier it has
|
||||
# used before. Uses LOWER() because slugs may be stored in a different case than the
|
||||
# incoming identifier (e.g. old lowercase slug vs new uppercase alphanumeric identifier).
|
||||
def identifier_not_historically_reserved
|
||||
return if errors.any? { |error| error.attribute == :identifier && error.type == :taken }
|
||||
|
||||
already_existing = FriendlyId::Slug
|
||||
.where(slug: identifier, sluggable_type: self.class.to_s)
|
||||
.where("LOWER(slug) = LOWER(?)", identifier)
|
||||
.where(sluggable_type: self.class.to_s)
|
||||
.where.not(sluggable_id: id)
|
||||
.exists?
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ module Projects::Scopes
|
||||
def visible(user = User.current)
|
||||
# Use a shortcut for admins and anonymous where
|
||||
# we don't need to calculate for work package roles which is more expensive
|
||||
if user.admin? || user.anonymous?
|
||||
if user.active_admin? || user.anonymous?
|
||||
allowed_to(user, :view_project)
|
||||
else
|
||||
active.public_projects.or(active.where(id: user.members.select(:project_id)))
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# 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::SemanticIdentifier
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Atomically allocates the next sequence number for a work package in this project
|
||||
# and returns it paired with the resulting semantic identifier (e.g. [42, "PROJ-42"]).
|
||||
# Uses an advisory lock scoped to this project to serialize concurrent allocations
|
||||
# without blocking unrelated project row writes.
|
||||
def allocate_wp_semantic_identifier!
|
||||
seq = OpenProject::Mutex.with_advisory_lock(
|
||||
self.class,
|
||||
"wp_sequence_#{id}"
|
||||
) do
|
||||
self.class.connection.select_value(<<~SQL.squish)
|
||||
UPDATE projects
|
||||
SET wp_sequence_counter = wp_sequence_counter + 1
|
||||
WHERE id = #{self.class.connection.quote(id)}
|
||||
RETURNING wp_sequence_counter
|
||||
SQL
|
||||
end
|
||||
|
||||
[seq, "#{identifier}-#{seq}"]
|
||||
end
|
||||
|
||||
# Called after this project's identifier is renamed. Atomically:
|
||||
# 1. Appends new-prefix aliases for every WP that ever carried an old-prefix alias.
|
||||
# 2. Updates identifier on resident WPs to the new prefix.
|
||||
def handle_semantic_rename(old_identifier, batch_size: 1000)
|
||||
like_pattern = "#{self.class.sanitize_sql_like(old_identifier)}-%"
|
||||
prefix = "#{old_identifier}-"
|
||||
new_prefix = "#{identifier}-"
|
||||
|
||||
WorkPackageSemanticAlias.transaction do
|
||||
append_aliases_with_new_prefix(like_pattern:, prefix:, new_prefix:, batch_size:)
|
||||
rewrite_semantic_ids(like_pattern:, prefix:, new_prefix:, batch_size:)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# For every alias row whose identifier starts with the old prefix, inserts a
|
||||
# corresponding row with the new prefix. This covers WPs still in the project
|
||||
# as well as any that have moved out but still carry old-prefix alias rows.
|
||||
def append_aliases_with_new_prefix(like_pattern:, prefix:, new_prefix:, batch_size:)
|
||||
WorkPackageSemanticAlias
|
||||
.where("identifier LIKE ?", like_pattern)
|
||||
.in_batches(of: batch_size) do |relation|
|
||||
now = Time.current
|
||||
WorkPackageSemanticAlias.connection.execute(
|
||||
WorkPackageSemanticAlias.sanitize_sql([<<~SQL.squish, { prefix:, new_prefix:, now: }])
|
||||
INSERT INTO work_package_semantic_aliases (identifier, work_package_id, created_at, updated_at)
|
||||
SELECT REPLACE(identifier, :prefix, :new_prefix), work_package_id, :now, :now
|
||||
FROM (#{relation.to_sql}) AS batch
|
||||
ON CONFLICT (identifier) DO NOTHING
|
||||
SQL
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# Updates the identifier column on all resident WPs to replace the old prefix with the new one.
|
||||
def rewrite_semantic_ids(like_pattern:, prefix:, new_prefix:, batch_size:)
|
||||
WorkPackage
|
||||
.where("identifier LIKE ?", like_pattern)
|
||||
.in_batches(of: batch_size) do |relation|
|
||||
relation.update_all(["identifier = REPLACE(identifier, ?, ?)", prefix, new_prefix])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -79,11 +79,11 @@ class Queries::Filters::Base
|
||||
end
|
||||
|
||||
def human_name
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def type
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def allowed_values
|
||||
|
||||
@@ -50,11 +50,11 @@ module Queries::Filters::Shared::ParsedFilter
|
||||
private
|
||||
|
||||
def split_values
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def value_conditions
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def validate_values
|
||||
|
||||
@@ -42,11 +42,11 @@ module Queries::Filters::Strategies
|
||||
private
|
||||
|
||||
def numeric_class
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def error_message
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def validate_values_all_numeric
|
||||
|
||||
@@ -45,7 +45,7 @@ module Queries
|
||||
end
|
||||
|
||||
def self.key
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def association_class
|
||||
|
||||
@@ -53,7 +53,7 @@ module Queries::Operators
|
||||
end
|
||||
|
||||
def self.sql_for_field(_values, _db_table, _db_field)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def self.connection
|
||||
|
||||
@@ -50,7 +50,7 @@ module Queries
|
||||
end
|
||||
|
||||
def self.key
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def apply_to(query_scope)
|
||||
|
||||
@@ -56,7 +56,7 @@ module Queries::Projects::Filters::DynamicallyFromProjectPhase
|
||||
end
|
||||
|
||||
def key
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
private
|
||||
@@ -77,7 +77,7 @@ module Queries::Projects::Filters::DynamicallyFromProjectPhase
|
||||
end
|
||||
|
||||
def create_from_phase(_phase, _context)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def accessor_matches?(definition, match)
|
||||
|
||||
@@ -74,23 +74,23 @@ module Queries::Projects::Filters::FilterOnProjectPhase
|
||||
private
|
||||
|
||||
def on_date
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def on_today
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def between_date
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def this_week
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def none
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def project_phase_scope_limit(scope)
|
||||
|
||||
@@ -54,7 +54,7 @@ module Queries
|
||||
private
|
||||
|
||||
def visibility_checked_sql(_operator, _values, _visible_sql)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,7 +32,7 @@ class Queries::Selects::Base
|
||||
include ActiveModel::Validations
|
||||
|
||||
def self.key
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def self.available?
|
||||
|
||||
@@ -68,7 +68,7 @@ class Queries::WorkPackages::Filter::AttachmentBaseFilter < Queries::WorkPackage
|
||||
end
|
||||
|
||||
def search_column
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def normalization_type
|
||||
|
||||
@@ -47,7 +47,7 @@ module Queries::WorkPackages::Filter::FilterOnDirectedRelationsMixin
|
||||
end
|
||||
|
||||
def relation_type
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def normalized_relation_type
|
||||
@@ -57,10 +57,10 @@ module Queries::WorkPackages::Filter::FilterOnDirectedRelationsMixin
|
||||
private
|
||||
|
||||
def relation_filter
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def relation_select
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +42,7 @@ module Queries::WorkPackages::Filter::FilterOnUndirectedRelationsMixin
|
||||
end
|
||||
|
||||
def relation_type
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -50,7 +50,7 @@ module Queries::WorkPackages::Filter::OrFilterForWpMixin
|
||||
end
|
||||
|
||||
def filter_configurations
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def create_instances
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
|
||||
class Setting
|
||||
module WorkPackageIdentifier
|
||||
NUMERIC = "numeric"
|
||||
ALPHANUMERIC = "alphanumeric"
|
||||
ALLOWED_VALUES = [NUMERIC, ALPHANUMERIC].freeze
|
||||
CLASSIC = "classic"
|
||||
SEMANTIC = "semantic"
|
||||
ALLOWED_VALUES = [CLASSIC, SEMANTIC].freeze
|
||||
|
||||
def self.alphanumeric? = Setting[:work_packages_identifier] == ALPHANUMERIC
|
||||
def self.numeric? = Setting[:work_packages_identifier] == NUMERIC
|
||||
def self.semantic? = Setting[:work_packages_identifier] == SEMANTIC
|
||||
def self.classic? = Setting[:work_packages_identifier] == CLASSIC
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,36 +40,36 @@ module SharingStrategies
|
||||
|
||||
def available_roles
|
||||
# format: [{ label: "Role name", value: 42, description: "Role description", default: true }]
|
||||
raise NotImplementedError, "Override in a subclass and return an array of roles that should be displayed"
|
||||
raise SubclassResponsibilityError, "Override in a subclass and return an array of roles that should be displayed"
|
||||
end
|
||||
|
||||
def viewable?
|
||||
raise NotImplementedError,
|
||||
raise SubclassResponsibilityError,
|
||||
"Override in a subclass and return true if the current user can view who the entity is shared with"
|
||||
end
|
||||
|
||||
def manageable?
|
||||
raise NotImplementedError, "Override in a subclass and return true if the current user can manage sharing"
|
||||
raise SubclassResponsibilityError, "Override in a subclass and return true if the current user can manage sharing"
|
||||
end
|
||||
|
||||
def create_contract_class
|
||||
raise NotImplementedError, "Override in a subclass and return the contract class for creating a share"
|
||||
raise SubclassResponsibilityError, "Override in a subclass and return the contract class for creating a share"
|
||||
end
|
||||
|
||||
def update_contract_class
|
||||
raise NotImplementedError, "Override in a subclass and return the contract class for updating a share"
|
||||
raise SubclassResponsibilityError, "Override in a subclass and return the contract class for updating a share"
|
||||
end
|
||||
|
||||
def delete_contract_class
|
||||
raise NotImplementedError, "Override in a subclass and return the contract class for deleting a share"
|
||||
raise SubclassResponsibilityError, "Override in a subclass and return the contract class for deleting a share"
|
||||
end
|
||||
|
||||
def share_description(share)
|
||||
raise NotImplementedError, "Override in a subclass and return a description for the shared user"
|
||||
raise SubclassResponsibilityError, "Override in a subclass and return a description for the shared user"
|
||||
end
|
||||
|
||||
def title
|
||||
raise NotImplementedError, "Override in a subclass and return a title for the sharing dialog"
|
||||
raise SubclassResponsibilityError, "Override in a subclass and return a title for the sharing dialog"
|
||||
end
|
||||
|
||||
def enterprise_feature
|
||||
|
||||
@@ -57,10 +57,10 @@ class Type::FormGroup
|
||||
end
|
||||
|
||||
def members
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def active_members(_project)
|
||||
raise NotImplementedError
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
|
||||
@@ -514,6 +514,10 @@ class User < Principal
|
||||
!logged?
|
||||
end
|
||||
|
||||
def active_admin?
|
||||
admin? && active?
|
||||
end
|
||||
|
||||
def consent_expired?
|
||||
# Always if the user has not consented
|
||||
return true if consented_at.blank?
|
||||
|
||||
@@ -34,7 +34,7 @@ module UserCustomFields::Scopes
|
||||
|
||||
class_methods do
|
||||
def visible(user = User.current)
|
||||
if user.admin?
|
||||
if user.active_admin?
|
||||
all
|
||||
else
|
||||
where(admin_only: false)
|
||||
|
||||
@@ -103,10 +103,10 @@ class UserPassword < ApplicationRecord
|
||||
|
||||
# Require the implementation to provide a secure comparison
|
||||
def hash_matches?(_plain)
|
||||
raise NotImplementedError, "Must be overridden by subclass"
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def derive_password!(_input)
|
||||
raise NotImplementedError, "Must be overridden by subclass"
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
end
|
||||
|
||||
@@ -173,18 +173,51 @@ class UserPreference < ApplicationRecord
|
||||
super.presence || { enabled: true, times: ["08:00:00+00:00"] }.with_indifferent_access
|
||||
end
|
||||
|
||||
def daily_reminders=(value)
|
||||
hash = value.to_h.with_indifferent_access
|
||||
self.settings = settings.merge(
|
||||
"daily_reminders" => {
|
||||
"enabled" => ActiveRecord::Type::Boolean.new.cast(hash[:enabled]),
|
||||
"times" => Array(hash[:times]).compact_blank
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def workdays
|
||||
super || WORKDAYS_FROM_MONDAY_TO_FRIDAY
|
||||
end
|
||||
|
||||
def workdays=(value)
|
||||
self.settings = settings.merge("workdays" => Array(value).map(&:to_i))
|
||||
end
|
||||
|
||||
def immediate_reminders
|
||||
super.presence || { mentioned: true, personal_reminder: true }.with_indifferent_access
|
||||
end
|
||||
|
||||
def immediate_reminders=(value)
|
||||
self.settings = settings.merge(
|
||||
"immediate_reminders" => value.to_h.with_indifferent_access.transform_values { |v| ActiveRecord::Type::Boolean.new.cast(v) }
|
||||
)
|
||||
end
|
||||
|
||||
def mentioned
|
||||
immediate_reminders[:mentioned]
|
||||
end
|
||||
|
||||
def personal_reminder
|
||||
immediate_reminders[:personal_reminder]
|
||||
end
|
||||
|
||||
def pause_reminders
|
||||
super.presence || { enabled: false }.with_indifferent_access
|
||||
end
|
||||
|
||||
def pause_reminders=(value)
|
||||
hash = value.to_h.with_indifferent_access
|
||||
self.settings = settings.merge("pause_reminders" => pause_reminders_hash(hash))
|
||||
end
|
||||
|
||||
def dismissed_banner?(feature)
|
||||
dismissed_enterprise_banners.key?(feature.to_s)
|
||||
end
|
||||
@@ -206,4 +239,21 @@ class UserPreference < ApplicationRecord
|
||||
def attribute?(name)
|
||||
%i[user user_id].include?(name.to_sym)
|
||||
end
|
||||
|
||||
def pause_reminders_hash(hash)
|
||||
result = { "enabled" => ActiveRecord::Type::Boolean.new.cast(hash[:enabled]) }
|
||||
date_fields = if hash[:date_range].present?
|
||||
parsed_date_range(hash[:date_range])
|
||||
else
|
||||
{ "first_day" => hash[:first_day].presence, "last_day" => hash[:last_day].presence }
|
||||
end
|
||||
result.merge(date_fields).compact
|
||||
end
|
||||
|
||||
def parsed_date_range(date_range)
|
||||
return {} if date_range.blank?
|
||||
|
||||
first_day, last_day = date_range.split(" - ", 2)
|
||||
{ "first_day" => first_day.presence, "last_day" => last_day.presence }
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user