Merge branch 'dev' into epic/workflows-ux-quick-wins

This commit is contained in:
Mir Bhatia
2026-03-24 15:46:36 +01:00
committed by GitHub
845 changed files with 29424 additions and 4568 deletions
+2
View File
@@ -84,6 +84,8 @@ jobs:
wielinde,
yanzubrytskyi,
ehassan01,
JohannaStriebing,
fereshtehnm,
thykel
# the followings are the optional inputs - If the optional inputs are not given, then default values will be taken
remote-organization-name: opf
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Send mail
uses: dawidd6/action-send-mail@v15
uses: dawidd6/action-send-mail@v16
with:
subject: ${{ inputs.subject }}
body: ${{ inputs.body }}
+9 -2
View File
@@ -1,10 +1,11 @@
name: Package
on:
push:
tags:
- v*
branches:
- packaging/*
- release/*
- stable/*
workflow_dispatch:
schedule:
- cron: '0 3 * * *' # Daily at 03:00
@@ -42,6 +43,12 @@ jobs:
run: |
VERSION=$(ruby -r ./lib/open_project/version.rb -e "puts OpenProject::VERSION")
echo "version=$VERSION" >> $GITHUB_OUTPUT
if [[ "${{ github.ref_type }}" == "tag" ]]; then
MAJOR=$(ruby -r ./lib/open_project/version.rb -e "puts OpenProject::VERSION::MAJOR")
echo "channel=stable/${MAJOR}" >> $GITHUB_OUTPUT
else
echo "channel=${{ github.ref_name }}" >> $GITHUB_OUTPUT
fi
- name: Package
uses: pkgr/action/package@main
id: package
@@ -58,5 +65,5 @@ jobs:
target: ${{ matrix.target }}
token: ${{ secrets.PACKAGER_PUBLISH_TOKEN }}
repository: opf/openproject
channel: ${{ github.ref_name }}
channel: ${{ steps.setup.outputs.channel }}
file: ${{ steps.package.outputs.package_path }}
+6 -1
View File
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.OPENPROJECTCI_GH_FORK_TOKEN }}
- name: Configure Git
run: |
@@ -47,3 +47,8 @@ jobs:
git reset --hard "upstream/$branch"
git push origin "$branch" --force
done
- name: Sync tags
run: |
git fetch upstream --tags
git push origin --tags --force
+10 -12
View File
@@ -87,7 +87,7 @@ gem "htmldiff"
gem "stringex", "~> 2.8.5"
# CommonMark markdown parser with GFM extension
gem "commonmarker", "~> 2.6.0"
gem "commonmarker", "~> 2.7.0"
# HTML pipeline for transformations on text formatter output
# such as sanitization or additional features
@@ -121,10 +121,10 @@ gem "csv", "~> 3.3"
# provide compatible filesystem information for available storage
gem "sys-filesystem", "~> 1.5.0", require: false
gem "bcrypt", "~> 3.1.6"
gem "bcrypt", "~> 3.1.22"
gem "multi_json", "~> 1.19.0"
gem "oj", "~> 3.16.12"
gem "oj", "~> 3.16.16"
gem "daemons"
gem "good_job", "~> 4.13.3" # update should be done manually in sync with saas-openproject version.
@@ -163,7 +163,7 @@ gem "matrix", "~> 0.4.3"
gem "mcp", "~> 0.8.0"
gem "meta-tags", "~> 2.22.3"
gem "meta-tags", "~> 2.23.0"
gem "paper_trail", "~> 17.0.0"
@@ -208,7 +208,7 @@ gem "aws-sdk-core", "~> 3.241"
# File upload via fog + screenshots on travis
gem "aws-sdk-s3", "~> 1.213"
gem "openproject-token", "~> 8.8.0"
gem "openproject-token", "~> 8.8.2"
gem "plaintext", "~> 0.3.7"
@@ -236,8 +236,8 @@ gem "yabeda-puma-plugin"
gem "yabeda-rails"
# opentelemetry
gem "opentelemetry-exporter-otlp", "~> 0.31.0", require: false
gem "opentelemetry-instrumentation-all", "~> 0.90.0", require: false
gem "opentelemetry-exporter-otlp", "~> 0.32.0", require: false
gem "opentelemetry-instrumentation-all", "~> 0.91.0", require: false
gem "opentelemetry-sdk", "~> 1.10", require: false
gem "view_component", "~> 4.5.0"
@@ -254,9 +254,7 @@ gem "factory_bot_rails", "~> 6.5.0", require: false
gem "turbo_power", "~> 0.7.0"
gem "turbo-rails", "~> 2.0.20"
# There is a problem with version 1.4.0. Do not update until you're sure there is no infinite hang
# happenning in failing tests when WebMock or VCR stub cannot be found.
gem "httpx", "~> 1.7.3"
gem "httpx", "~> 1.7.4"
# Brings actual deep-freezing to most ruby objects
gem "ice_nine"
@@ -277,7 +275,7 @@ group :test do
gem "rspec-rails", "~> 8.0.4", group: :development
# Retry failures within the same environment
gem "retriable", "~> 3.2.1"
gem "retriable", "~> 3.4.1"
gem "rspec-retry", "~> 0.6.1"
# Accessibility tests
@@ -434,4 +432,4 @@ end
gem "openproject-octicons", "~>19.32.0"
gem "openproject-octicons_helper", "~>19.32.0"
gem "openproject-primer_view_components", "~>0.82.0"
gem "openproject-primer_view_components", "~>0.83.0"
+198 -188
View File
@@ -213,6 +213,11 @@ PATH
specs:
openproject-webhooks (1.0.0)
PATH
remote: modules/wikis
specs:
openproject-wikis (1.0.0)
PATH
remote: modules/xls_export
specs:
@@ -225,29 +230,29 @@ GEM
Ascii85 (2.0.1)
action_text-trix (2.1.17)
railties
actioncable (8.1.2)
actionpack (= 8.1.2)
activesupport (= 8.1.2)
actioncable (8.1.2.1)
actionpack (= 8.1.2.1)
activesupport (= 8.1.2.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.1.2)
actionpack (= 8.1.2)
activejob (= 8.1.2)
activerecord (= 8.1.2)
activestorage (= 8.1.2)
activesupport (= 8.1.2)
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)
mail (>= 2.8.0)
actionmailer (8.1.2)
actionpack (= 8.1.2)
actionview (= 8.1.2)
activejob (= 8.1.2)
activesupport (= 8.1.2)
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)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.1.2)
actionview (= 8.1.2)
activesupport (= 8.1.2)
actionpack (8.1.2.1)
actionview (= 8.1.2.1)
activesupport (= 8.1.2.1)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@@ -258,34 +263,34 @@ GEM
actionpack-xml_parser (2.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
actiontext (8.1.2)
actiontext (8.1.2.1)
action_text-trix (~> 2.1.15)
actionpack (= 8.1.2)
activerecord (= 8.1.2)
activestorage (= 8.1.2)
activesupport (= 8.1.2)
actionpack (= 8.1.2.1)
activerecord (= 8.1.2.1)
activestorage (= 8.1.2.1)
activesupport (= 8.1.2.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.1.2)
activesupport (= 8.1.2)
actionview (8.1.2.1)
activesupport (= 8.1.2.1)
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)
activesupport (= 8.1.2)
activejob (8.1.2.1)
activesupport (= 8.1.2.1)
globalid (>= 0.3.6)
activemodel (8.1.2)
activesupport (= 8.1.2)
activemodel (8.1.2.1)
activesupport (= 8.1.2.1)
activemodel-serializers-xml (1.0.3)
activemodel (>= 5.0.0.a)
activesupport (>= 5.0.0.a)
builder (~> 3.1)
activerecord (8.1.2)
activemodel (= 8.1.2)
activesupport (= 8.1.2)
activerecord (8.1.2.1)
activemodel (= 8.1.2.1)
activesupport (= 8.1.2.1)
timeout (>= 0.4.0)
activerecord-import (2.2.0)
activerecord (>= 4.2)
@@ -297,13 +302,13 @@ GEM
cgi (>= 0.3.6)
rack (>= 2.0.8, < 4)
railties (>= 7.0)
activestorage (8.1.2)
actionpack (= 8.1.2)
activejob (= 8.1.2)
activerecord (= 8.1.2)
activesupport (= 8.1.2)
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)
marcel (~> 1.0)
activesupport (8.1.2)
activesupport (8.1.2.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
@@ -342,8 +347,8 @@ GEM
awesome_nested_set (3.9.0)
activerecord (>= 4.0.0, < 8.2)
aws-eventstream (1.4.0)
aws-partitions (1.1221.0)
aws-sdk-core (3.242.0)
aws-partitions (1.1227.0)
aws-sdk-core (3.243.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -354,8 +359,8 @@ GEM
aws-sdk-kms (1.122.0)
aws-sdk-core (~> 3, >= 3.241.4)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.214.0)
aws-sdk-core (~> 3, >= 3.241.4)
aws-sdk-s3 (1.216.0)
aws-sdk-core (~> 3, >= 3.243.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
aws-sdk-sns (1.112.0)
@@ -377,7 +382,7 @@ GEM
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
base64 (0.3.0)
bcrypt (3.1.21)
bcrypt (3.1.22)
benchmark (0.5.0)
better_html (2.2.0)
actionview (>= 7.0)
@@ -433,12 +438,13 @@ GEM
descendants_tracker (~> 0.0.1)
color_conversion (0.1.2)
colored2 (4.0.3)
commonmarker (2.6.3-aarch64-linux)
commonmarker (2.6.3-arm-linux)
commonmarker (2.6.3-arm64-darwin)
commonmarker (2.6.3-x86_64-darwin)
commonmarker (2.6.3-x86_64-linux)
commonmarker (2.6.3-x86_64-linux-musl)
commonmarker (2.7.0-aarch64-linux)
commonmarker (2.7.0-aarch64-linux-musl)
commonmarker (2.7.0-arm-linux)
commonmarker (2.7.0-arm64-darwin)
commonmarker (2.7.0-x86_64-darwin)
commonmarker (2.7.0-x86_64-linux)
commonmarker (2.7.0-x86_64-linux-musl)
compare-xml (0.66)
nokogiri (~> 1.8)
concurrent-ruby (1.3.6)
@@ -697,7 +703,7 @@ GEM
htmlentities (4.3.4)
http-2 (1.1.3)
http_parser.rb (0.8.1)
httpx (1.7.3)
httpx (1.7.4)
http-2 (>= 1.1.3)
i18n (1.14.8)
concurrent-ruby (~> 1.0)
@@ -740,7 +746,7 @@ GEM
jmespath (1.6.2)
job-iteration (1.12.0)
activejob (>= 6.1)
json (2.19.1)
json (2.19.2)
json-jwt (1.17.0)
activesupport (>= 4.2)
aes_key_wrap
@@ -767,7 +773,7 @@ GEM
addressable (~> 2.8)
childprocess (~> 5.0)
logger (~> 1.6)
lefthook (2.1.3)
lefthook (2.1.4)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (3.0.0)
@@ -790,7 +796,7 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.25.0)
loofah (2.25.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
lookbook (2.3.14)
@@ -818,13 +824,13 @@ GEM
json-schema (>= 4.1)
messagebird-rest (5.0.0)
jwt (< 4)
meta-tags (2.22.3)
meta-tags (2.23.0)
actionpack (>= 6.0.0)
method_source (1.1.0)
mime-types (3.7.0)
logger
mime-types-data (~> 3.2025, >= 3.2025.0507)
mime-types-data (3.2026.0303)
mime-types-data (3.2026.0317)
mini_magick (5.3.1)
logger
mini_mime (1.1.5)
@@ -868,7 +874,7 @@ GEM
racc (~> 1.4)
nokogiri (1.19.1-x86_64-linux-musl)
racc (~> 1.4)
oj (3.16.15)
oj (3.16.16)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
okcomputer (1.19.1)
@@ -895,27 +901,28 @@ GEM
actionview
openproject-octicons (= 19.32.1)
railties
openproject-primer_view_components (0.82.0)
openproject-primer_view_components (0.83.0)
actionview (>= 7.2.0)
activesupport (>= 7.2.0)
openproject-octicons (>= 19.30.1)
view_component (>= 3.1, < 5.0)
openproject-token (8.8.0)
openproject-token (8.8.2)
activemodel
openssl (4.0.1)
openssl-signature_algorithm (1.3.0)
openssl (> 2.0)
opentelemetry-api (1.7.0)
opentelemetry-api (1.8.0)
logger
opentelemetry-common (0.23.0)
opentelemetry-api (~> 1.0)
opentelemetry-exporter-otlp (0.31.1)
opentelemetry-exporter-otlp (0.32.0)
google-protobuf (>= 3.18)
googleapis-common-protos-types (~> 1.3)
opentelemetry-api (~> 1.1)
opentelemetry-common (~> 0.20)
opentelemetry-sdk (~> 1.10)
opentelemetry-semantic_conventions
opentelemetry-helpers-mysql (0.4.0)
opentelemetry-helpers-mysql (0.5.0)
opentelemetry-api (~> 1.7)
opentelemetry-common (~> 0.21)
opentelemetry-helpers-sql (0.3.0)
@@ -925,7 +932,7 @@ GEM
opentelemetry-common (~> 0.21)
opentelemetry-instrumentation-action_mailer (0.6.1)
opentelemetry-instrumentation-active_support (~> 0.10)
opentelemetry-instrumentation-action_pack (0.15.1)
opentelemetry-instrumentation-action_pack (0.16.0)
opentelemetry-instrumentation-rack (~> 0.29)
opentelemetry-instrumentation-action_view (0.11.2)
opentelemetry-instrumentation-active_support (~> 0.10)
@@ -939,45 +946,45 @@ GEM
opentelemetry-instrumentation-active_support (~> 0.10)
opentelemetry-instrumentation-active_support (0.10.1)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-all (0.90.1)
opentelemetry-instrumentation-all (0.91.0)
opentelemetry-instrumentation-active_model_serializers (~> 0.24.0)
opentelemetry-instrumentation-anthropic (~> 0.3.0)
opentelemetry-instrumentation-anthropic (~> 0.4.0)
opentelemetry-instrumentation-aws_lambda (~> 0.6.0)
opentelemetry-instrumentation-aws_sdk (~> 0.11.0)
opentelemetry-instrumentation-bunny (~> 0.24.0)
opentelemetry-instrumentation-concurrent_ruby (~> 0.24.0)
opentelemetry-instrumentation-dalli (~> 0.29.0)
opentelemetry-instrumentation-dalli (~> 0.29.2)
opentelemetry-instrumentation-delayed_job (~> 0.25.1)
opentelemetry-instrumentation-ethon (~> 0.27.0)
opentelemetry-instrumentation-excon (~> 0.27.0)
opentelemetry-instrumentation-faraday (~> 0.31.0)
opentelemetry-instrumentation-grape (~> 0.5.0)
opentelemetry-instrumentation-graphql (~> 0.31.1)
opentelemetry-instrumentation-ethon (~> 0.28.0)
opentelemetry-instrumentation-excon (~> 0.28.0)
opentelemetry-instrumentation-faraday (~> 0.32.0)
opentelemetry-instrumentation-grape (~> 0.6.0)
opentelemetry-instrumentation-graphql (~> 0.31.2)
opentelemetry-instrumentation-grpc (~> 0.4.1)
opentelemetry-instrumentation-gruf (~> 0.5.0)
opentelemetry-instrumentation-http (~> 0.28.0)
opentelemetry-instrumentation-http_client (~> 0.27.0)
opentelemetry-instrumentation-httpx (~> 0.6.0)
opentelemetry-instrumentation-http (~> 0.29.0)
opentelemetry-instrumentation-http_client (~> 0.28.0)
opentelemetry-instrumentation-httpx (~> 0.7.0)
opentelemetry-instrumentation-koala (~> 0.23.0)
opentelemetry-instrumentation-lmdb (~> 0.25.0)
opentelemetry-instrumentation-mongo (~> 0.25.0)
opentelemetry-instrumentation-mysql2 (~> 0.33.0)
opentelemetry-instrumentation-net_http (~> 0.27.0)
opentelemetry-instrumentation-net_http (~> 0.28.0)
opentelemetry-instrumentation-pg (~> 0.35.0)
opentelemetry-instrumentation-que (~> 0.12.0)
opentelemetry-instrumentation-racecar (~> 0.6.0)
opentelemetry-instrumentation-rack (~> 0.29.0)
opentelemetry-instrumentation-rails (~> 0.39.1)
opentelemetry-instrumentation-racecar (~> 0.6.1)
opentelemetry-instrumentation-rack (~> 0.30.0)
opentelemetry-instrumentation-rails (~> 0.40.0)
opentelemetry-instrumentation-rake (~> 0.5.0)
opentelemetry-instrumentation-rdkafka (~> 0.9.0)
opentelemetry-instrumentation-redis (~> 0.28.0)
opentelemetry-instrumentation-resque (~> 0.8.0)
opentelemetry-instrumentation-restclient (~> 0.26.0)
opentelemetry-instrumentation-restclient (~> 0.27.0)
opentelemetry-instrumentation-ruby_kafka (~> 0.24.0)
opentelemetry-instrumentation-sidekiq (~> 0.28.1)
opentelemetry-instrumentation-sinatra (~> 0.28.0)
opentelemetry-instrumentation-trilogy (~> 0.66.0)
opentelemetry-instrumentation-anthropic (0.3.0)
opentelemetry-instrumentation-sinatra (~> 0.29.0)
opentelemetry-instrumentation-trilogy (~> 0.67.0)
opentelemetry-instrumentation-anthropic (0.4.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-aws_lambda (0.6.0)
opentelemetry-instrumentation-base (~> 0.25)
@@ -995,13 +1002,13 @@ GEM
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-delayed_job (0.25.1)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-ethon (0.27.0)
opentelemetry-instrumentation-ethon (0.28.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-excon (0.27.0)
opentelemetry-instrumentation-excon (0.28.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-faraday (0.31.0)
opentelemetry-instrumentation-faraday (0.32.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-grape (0.5.1)
opentelemetry-instrumentation-grape (0.6.0)
opentelemetry-instrumentation-rack (~> 0.29)
opentelemetry-instrumentation-graphql (0.31.2)
opentelemetry-instrumentation-base (~> 0.25)
@@ -1009,11 +1016,11 @@ GEM
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-gruf (0.5.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-http (0.28.0)
opentelemetry-instrumentation-http (0.29.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-http_client (0.27.0)
opentelemetry-instrumentation-http_client (0.28.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-httpx (0.6.1)
opentelemetry-instrumentation-httpx (0.7.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-koala (0.23.0)
opentelemetry-instrumentation-base (~> 0.25)
@@ -1026,7 +1033,7 @@ GEM
opentelemetry-helpers-sql
opentelemetry-helpers-sql-processor
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-net_http (0.27.0)
opentelemetry-instrumentation-net_http (0.28.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-pg (0.35.0)
opentelemetry-helpers-sql
@@ -1036,9 +1043,9 @@ GEM
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-racecar (0.6.1)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-rack (0.29.0)
opentelemetry-instrumentation-rack (0.30.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-rails (0.39.1)
opentelemetry-instrumentation-rails (0.40.0)
opentelemetry-instrumentation-action_mailer (~> 0.6)
opentelemetry-instrumentation-action_pack (~> 0.15)
opentelemetry-instrumentation-action_view (~> 0.11)
@@ -1055,15 +1062,15 @@ GEM
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-resque (0.8.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-restclient (0.26.0)
opentelemetry-instrumentation-restclient (0.27.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-ruby_kafka (0.24.0)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-sidekiq (0.28.1)
opentelemetry-instrumentation-base (~> 0.25)
opentelemetry-instrumentation-sinatra (0.28.0)
opentelemetry-instrumentation-sinatra (0.29.0)
opentelemetry-instrumentation-rack (~> 0.29)
opentelemetry-instrumentation-trilogy (0.66.0)
opentelemetry-instrumentation-trilogy (0.67.0)
opentelemetry-helpers-mysql
opentelemetry-helpers-sql
opentelemetry-helpers-sql-processor
@@ -1083,7 +1090,7 @@ GEM
ostruct (0.6.3)
ox (2.14.23)
bigdecimal (>= 3.0)
pagy (43.3.2)
pagy (43.4.0)
json
uri
yaml
@@ -1176,7 +1183,7 @@ GEM
date
stringio
public_suffix (7.0.5)
puffing-billy (4.0.3)
puffing-billy (4.0.4)
addressable (~> 2.5)
cgi
em-http-request (~> 1.1, >= 1.1.0)
@@ -1219,20 +1226,20 @@ GEM
rackup (1.0.1)
rack (< 3)
webrick
rails (8.1.2)
actioncable (= 8.1.2)
actionmailbox (= 8.1.2)
actionmailer (= 8.1.2)
actionpack (= 8.1.2)
actiontext (= 8.1.2)
actionview (= 8.1.2)
activejob (= 8.1.2)
activemodel (= 8.1.2)
activerecord (= 8.1.2)
activestorage (= 8.1.2)
activesupport (= 8.1.2)
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)
bundler (>= 1.15.0)
railties (= 8.1.2)
railties (= 8.1.2.1)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@@ -1247,9 +1254,9 @@ GEM
rails-i18n (8.1.0)
i18n (>= 0.7, < 2)
railties (>= 8.0.0, < 9)
railties (8.1.2)
actionpack (= 8.1.2)
activesupport (= 8.1.2)
railties (8.1.2.1)
actionpack (= 8.1.2.1)
activesupport (= 8.1.2.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
@@ -1277,7 +1284,7 @@ GEM
redcarpet (3.6.1)
redis (5.4.1)
redis-client (>= 0.22.0)
redis-client (0.26.4)
redis-client (0.28.0)
connection_pool
regexp_parser (2.11.3)
reline (0.6.3)
@@ -1291,7 +1298,7 @@ GEM
responders (3.2.0)
actionpack (>= 7.0)
railties (>= 7.0)
retriable (3.2.1)
retriable (3.4.1)
rexml (3.4.4)
rinku (2.0.6)
roar (1.2.0)
@@ -1335,7 +1342,7 @@ GEM
rubocop-ast (>= 1.49.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.49.0)
rubocop-ast (1.49.1)
parser (>= 3.3.7.2)
prism (~> 1.7)
rubocop-capybara (2.22.1)
@@ -1389,10 +1396,10 @@ GEM
sanitize (7.0.0)
crass (~> 1.0.2)
nokogiri (>= 1.16.8)
scimitar (2.14.0)
scimitar (2.15.0)
rails (>= 7.0)
securerandom (0.4.1)
selenium-devtools (0.143.0)
selenium-devtools (0.145.0)
selenium-webdriver (~> 4.2)
selenium-webdriver (4.41.0)
base64 (~> 0.2)
@@ -1507,7 +1514,7 @@ GEM
activesupport
faraday (~> 2.0)
faraday-follow_redirects
webmock (3.26.1)
webmock (3.26.2)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@@ -1523,7 +1530,7 @@ GEM
zeitwerk (>= 2.7)
xpath (3.2.0)
nokogiri (~> 1.8)
yabeda (0.15.0)
yabeda (0.16.0)
anyway_config (>= 1.0, < 3)
concurrent-ruby
dry-initializer
@@ -1576,7 +1583,7 @@ DEPENDENCIES
aws-sdk-core (~> 3.241)
aws-sdk-s3 (~> 1.213)
axe-core-rspec
bcrypt (~> 3.1.6)
bcrypt (~> 3.1.22)
bootsnap (~> 1.23.0)
brakeman (~> 8.0.1)
browser (~> 6.2.0)
@@ -1590,7 +1597,7 @@ DEPENDENCIES
climate_control
closure_tree (~> 9.6.1)
colored2
commonmarker (~> 2.6.0)
commonmarker (~> 2.7.0)
compare-xml (~> 0.66)
connection_pool (~> 3.0.2)
costs!
@@ -1629,7 +1636,7 @@ DEPENDENCIES
grids!
html-pipeline (~> 2.14.0)
htmldiff
httpx (~> 1.7.3)
httpx (~> 1.7.4)
i18n-js (~> 4.2.4)
i18n-tasks (~> 1.1.0)
ice_cube (~> 0.17.0)
@@ -1650,13 +1657,13 @@ DEPENDENCIES
matrix (~> 0.4.3)
mcp (~> 0.8.0)
md_to_pdf!
meta-tags (~> 2.22.3)
meta-tags (~> 2.23.0)
mini_magick (~> 5.3.0)
multi_json (~> 1.19.0)
my_page!
net-ldap (~> 0.20.0)
nokogiri (~> 1.19.1)
oj (~> 3.16.12)
oj (~> 3.16.16)
okcomputer (~> 1.19.1)
omniauth!
omniauth-openid-connect!
@@ -1679,17 +1686,18 @@ DEPENDENCIES
openproject-octicons (~> 19.32.0)
openproject-octicons_helper (~> 19.32.0)
openproject-openid_connect!
openproject-primer_view_components (~> 0.82.0)
openproject-primer_view_components (~> 0.83.0)
openproject-recaptcha!
openproject-reporting!
openproject-storages!
openproject-team_planner!
openproject-token (~> 8.8.0)
openproject-token (~> 8.8.2)
openproject-two_factor_authentication!
openproject-webhooks!
openproject-wikis!
openproject-xls_export!
opentelemetry-exporter-otlp (~> 0.31.0)
opentelemetry-instrumentation-all (~> 0.90.0)
opentelemetry-exporter-otlp (~> 0.32.0)
opentelemetry-instrumentation-all (~> 0.91.0)
opentelemetry-sdk (~> 1.10)
overviews!
ox
@@ -1721,7 +1729,7 @@ DEPENDENCIES
redis (~> 5.4.0)
request_store (~> 1.7.0)
responders (~> 3.2)
retriable (~> 3.2.1)
retriable (~> 3.4.1)
rinku (~> 2.0.4)
roar (~> 1.2.0)
rouge (~> 4.7.0)
@@ -1786,23 +1794,23 @@ DEPENDENCIES
CHECKSUMS
Ascii85 (2.0.1) sha256=15cb5d941808543cbb9e7e6aea3c8ec3877f154c3461e8b3673e97f7ecedbe5a
action_text-trix (2.1.17) sha256=b44691639d77e67169dc054ceacd1edc04d44dc3e4c6a427aa155a2beb4cc951
actioncable (8.1.2) sha256=dc31efc34cca9cdefc5c691ddb8b4b214c0ea5cd1372108cbc1377767fb91969
actionmailbox (8.1.2) sha256=058b2fb1980e5d5a894f675475fcfa45c62631103d5a2596d9610ec81581889b
actionmailer (8.1.2) sha256=f4c1d2060f653bfe908aa7fdc5a61c0e5279670de992146582f2e36f8b9175e9
actionpack (8.1.2) sha256=ced74147a1f0daafaa4bab7f677513fd4d3add574c7839958f7b4f1de44f8423
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
actionpack-xml_parser (2.0.1) sha256=40cb461ee99445314ab580a783fb7413580deb8b28113c9e70ecd7c1b334d5e6
actiontext (8.1.2) sha256=0bf57da22a9c19d970779c3ce24a56be31b51c7640f2763ec64aa72e358d2d2d
actionview (8.1.2) sha256=80455b2588911c9b72cec22d240edacb7c150e800ef2234821269b2b2c3e2e5b
actiontext (8.1.2.1) sha256=1e503ce600a6ab2e12a46f999959a7d8e2fdaff910ca01dcf3b968934b55d957
actionview (8.1.2.1) sha256=38daa7b87bca427e2967f139e5b7f0d1081271bdafd0e015d8ef97a006f570a6
active_record_doctor (2.0.1) sha256=7af0ac02195385c8f2f67d0e4ebe72b1fc79d65eaaf329e0db07f4d12a84069a
activejob (8.1.2) sha256=908dab3713b101859536375819f4156b07bdf4c232cc645e7538adb9e302f825
activemodel (8.1.2) sha256=e21358c11ce68aed3f9838b7e464977bc007b4446c6e4059781e1d5c03bcf33e
activejob (8.1.2.1) sha256=c89c311d07fd358b76c581ed8fee87c5b4351fb44994f3389385c014d22182fe
activemodel (8.1.2.1) sha256=8f31a6f9c12fecb8e5a0fce8a8950cfd94f0d75829322935f99e8217a3e5f3c6
activemodel-serializers-xml (1.0.3) sha256=fa1b16305e7254cc58a59c68833e3c0a593a59c8ab95d3be5aaea7cd9416c397
activerecord (8.1.2) sha256=acfbe0cadfcc50fa208011fe6f4eb01cae682ebae0ef57145ba45380c74bcc44
activerecord (8.1.2.1) sha256=3f79140318ff6d23376f5d9b1b5b5e2c7d3cc8979dd71367e9a8394378ca630a
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) sha256=8a63a48c3999caeee26a59441f813f94681fc35cc41aba7ce1f836add04fba76
activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae
activestorage (8.1.2.1) sha256=36794c9b8853ac9276b0386cb1f8973374d8e71e8a9666bb02e70f5b7c9c5391
activesupport (8.1.2.1) sha256=beec20ced12ad569194554399449a6372fdab03061b8f48a9ed6ef9b7dc251b2
acts_as_list (1.2.6) sha256=8345380900b7bee620c07ad00991ccee59af3d8c9e8574f426e321da2865fdc8
acts_as_tree (2.9.1) sha256=b869eb10a8de38616b64ffcf9e882d3d99c8e06909c4057078a76c3b89a9a2f3
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
@@ -1818,17 +1826,17 @@ 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.1221.0) sha256=f09304480191f5ff03f8994705067779bc8fe5b4731183ce45f092afb706e8eb
aws-sdk-core (3.242.0) sha256=c17b3003acc78d80c1a8437b285a1cfc5e4d7749ce7821cf3071e847535a29a0
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.214.0) sha256=923135327634c873ecedbd8f396ea9939874d524b14fa65eced766660c8dd62e
aws-sdk-s3 (1.216.0) sha256=a3bf6191e6f7a3dfb04b7cc73409f059394be559e4aff92d2a764341e4d90af4
aws-sdk-sns (1.112.0) sha256=aff1b1b5bbcb4229599221c558a41790c1cd1a1fed47ac3d27d27512ad24b254
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
axe-core-api (4.11.1) sha256=a6460506449a692030620a0574fee7afa6cd38cfbbf6620d20bf4d53d33a80cc
axe-core-rspec (4.11.1) sha256=dc6c0e166405cd3a28c4a0937f6521ee5b511c12c0ca1627144a1ee7d5014aec
axiom-types (0.1.1) sha256=c1ff113f3de516fa195b2db7e0a9a95fd1b08475a502ff660d04507a09980383
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
bcrypt (3.1.21) sha256=5964613d750a42c7ee5dc61f7b9336fb6caca429ba4ac9f2011609946e4a2dcf
bcrypt (3.1.22) sha256=1f0072e88c2d705d94aff7f2c5cb02eb3f1ec4b8368671e19112527489f29032
benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c
better_html (2.2.0) sha256=e68ab66ab09696b708333bbf35e8aa3c107500ba7892f528e2111624bdd8cf76
bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218
@@ -1853,12 +1861,13 @@ CHECKSUMS
coercible (1.0.0) sha256=5081ad24352cc8435ce5472bc2faa30260c7ea7f2102cc6a9f167c4d9bffaadc
color_conversion (0.1.2) sha256=99bea5fa412e1527a11389975aa6ad445ff8528ebae202c11d08c45ea2b94c96
colored2 (4.0.3) sha256=63e1038183976287efc43034f5cca17fb180b4deef207da8ba78d051cbce2b37
commonmarker (2.6.3-aarch64-linux) sha256=73795e80ab5ef1e4b5b83ada6f082bccb0ed7eae0b910232e13af1b2d71b14d6
commonmarker (2.6.3-arm-linux) sha256=62b9f32d7d3f85d47988a4a98a2e66e60ca42b894687047db8332f1e80caff7b
commonmarker (2.6.3-arm64-darwin) sha256=d6c1e4955619da3f68fed22de99dec49a24925611770c039bf870823846c8b21
commonmarker (2.6.3-x86_64-darwin) sha256=cd8ab974bb24f675a250ea91a811b3ff70405be1c219f0052446995db6ca90c6
commonmarker (2.6.3-x86_64-linux) sha256=e861ba1812721113725ebd8e46e4fee20dc732842f5555db2cfb8dcd74056583
commonmarker (2.6.3-x86_64-linux-musl) sha256=2c62d2dc0d5c4efc6dde39bc5c5fac292169206601a3daf75e562d70b795d49e
commonmarker (2.7.0-aarch64-linux) sha256=a15a47bb901f4cfcb3b4fe54d0daf91ec46e70b6e9704c67abec0ba30064b2dc
commonmarker (2.7.0-aarch64-linux-musl) sha256=fa08eb19ffc3cf2f7132d86d74622338a0c35b480b23814a7ae63deaeafb56d3
commonmarker (2.7.0-arm-linux) sha256=85274d47feca40bd574ac49c2959a015464936f7f830c1e2918ee147d5be1a13
commonmarker (2.7.0-arm64-darwin) sha256=ef00f7efef4822e8e7f88be88920a319f71a251e894e02fbc82f6db5d4291db3
commonmarker (2.7.0-x86_64-darwin) sha256=a8f5928f36138347b5e672d080b8f76dc430c6207566128b117cdd02f3e48c34
commonmarker (2.7.0-x86_64-linux) sha256=53243eeb30e4ddb6a474672597ff7e59334e55a7653126bb87e65e6ab2629a4c
commonmarker (2.7.0-x86_64-linux-musl) sha256=e4f5c06975f37a405bd157d276609e1c02502d2fbaf576f6e3616fba2e7b5662
compare-xml (0.66) sha256=e21aa5c0f69ef1177eced997c688fd4df989084e74a1b612257af32e1dd05319
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
@@ -1966,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.3) sha256=126914109a58350e5ad0c13786092f35e2419857dad15745f31fc81c371f93a8
httpx (1.7.4) sha256=91fb3e0f7325966a5da4d463a1dd7240e8550d8b0de79e346cc5dc1df1eacd2b
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
i18n-js (4.2.4) sha256=61390d372f8fa68c495c5907d577657e8cc3a7031f4945db1e91f935e1391355
i18n-tasks (1.1.2) sha256=4dcfba49e52a623f30661cb316cb80d84fbba5cb8c6d88ef5e02545fffa3637a
@@ -1981,7 +1990,7 @@ CHECKSUMS
iso8601 (0.13.0) sha256=298c2b15b7be5fa95a1372813d36a2257656cd8e906dfbc1f5cb409851425aa2
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
job-iteration (1.12.0) sha256=0164057417750f6e9c3ed548f029f1136b18eb53975fa438b09304a525d6c6c0
json (2.19.1) sha256=dd94fdc59e48bff85913829a32350b3148156bc4fd2a95a2568a78b11344082d
json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf
json-jwt (1.17.0) sha256=6ff99026b4c54281a9431179f76ceb81faa14772d710ef6169785199caadc4cc
json-schema (4.3.1) sha256=d5e68dc32b94408d0b06ad04f9382ccbb6fe5a44910e066f8547f56c471a7825
json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
@@ -1990,7 +1999,7 @@ CHECKSUMS
ladle (1.0.1) sha256=e8586964108c798d48bf57d2a65bd5602e8e5223a176b6602a0fb36c0bda90dc
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
launchy (3.1.1) sha256=72b847b5cc961589dde2c395af0108c86ff0119f42d4648d25b5440ebb10059e
lefthook (2.1.3) sha256=399eae9411d5a65fbeff38230a685073fefac6ef9ae18165a81f5f7ffa2df7a7
lefthook (2.1.4) sha256=b3c5bba86911e85b239fea3861ba8c74740fc084ba9ac79dba3fe79267572d6a
letter_opener (1.10.0) sha256=2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2
letter_opener_web (3.0.0) sha256=3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
@@ -1998,7 +2007,7 @@ CHECKSUMS
lobby_boy (0.1.3) sha256=9460bb3c052aef158eb3f137b8f7679ca756b8e2983d140dbdc0caa85c018172
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
lograge (0.14.0) sha256=42371a75823775f166f727639f5ddce73dd149452a55fc94b90c303213dc9ae1
loofah (2.25.0) sha256=df5ed7ac3bac6a4ec802df3877ee5cc86d027299f8952e6243b3dac446b060e6
loofah (2.25.1) sha256=d436c73dbd0c1147b16c4a41db097942d217303e1f7728704b37e4df9f6d2e04
lookbook (2.3.14) sha256=c11a693bde9915b553c4463440ad5e750829f90bff08abdb6b8610373864cd7c
mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941
marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4
@@ -2007,10 +2016,10 @@ CHECKSUMS
mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
md_to_pdf (0.2.5)
messagebird-rest (5.0.0) sha256=da4cc1efba3d5e4aa021fad07426c2cb6b326ce5670da5104bb8f6056a39d59c
meta-tags (2.22.3) sha256=41ead5437140869717cbdd659cc6f1caa3e498b3e74b03ed63503b5b38ed504f
meta-tags (2.23.0) sha256=ffe78b5bee398de4ff5ac3316f5a786049538a651643b8476def06c3acc762c1
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56
mime-types-data (3.2026.0303) sha256=164af1de5824c5195d4b503b0a62062383b65c08671c792412450cd22d3bc224
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
@@ -2034,7 +2043,7 @@ CHECKSUMS
nokogiri (1.19.1-x86_64-darwin) sha256=7093896778cc03efb74b85f915a775862730e887f2e58d6921e3fa3d981e68bf
nokogiri (1.19.1-x86_64-linux-gnu) sha256=1a4902842a186b4f901078e692d12257678e6133858d0566152fe29cdb98456a
nokogiri (1.19.1-x86_64-linux-musl) sha256=4267f38ad4fc7e52a2e7ee28ed494e8f9d8eb4f4b3320901d55981c7b995fc23
oj (3.16.15) sha256=4d3324cac3e8fef54c0fa250b2af26a16dadd9f9788a1d6b1b2098b793a1b2cd
oj (3.16.16) sha256=3635b36128991796434f55da8decc0de236a323535adcb36fc04e6d0253c013d
okcomputer (1.19.1) sha256=7df770e768434816d228407f0786563827cbf34cb379933578829720cb4f1e77
omniauth (1.9.2)
omniauth-openid-connect (0.5.0)
@@ -2060,33 +2069,34 @@ CHECKSUMS
openproject-octicons (19.32.1) sha256=32253f3256ad4e1aec36442558ce140623c01e5241d9b90f6eb6d317f462781e
openproject-octicons_helper (19.32.1) sha256=7676059927ae940170fb13d62f88b885985a3f0d483e1bb246475afcffd90f8f
openproject-openid_connect (1.0.0)
openproject-primer_view_components (0.82.0) sha256=c3d61578d26e6fa6e4bfaf520345de01cc09b487c9841de78494017aadd65ae2
openproject-primer_view_components (0.83.0) sha256=5e0b89f6467f5c69f017dd14723be28dcc34c842312999c954184ef5b82b83b9
openproject-recaptcha (1.0.0)
openproject-reporting (1.0.0)
openproject-storages (1.0.0)
openproject-team_planner (1.0.0)
openproject-token (8.8.0) sha256=832a493e05dcce806134faf63ae8011cc5a48422fbed9ebb552f8028912954d4
openproject-token (8.8.2) sha256=081cbff7269d92a82fa1d63e9e09c87b70d47d7aefadcbb80d1e7368bc2cf096
openproject-two_factor_authentication (1.0.0)
openproject-webhooks (1.0.0)
openproject-wikis (1.0.0)
openproject-xls_export (1.0.0)
openssl (4.0.1) sha256=e27974136b7b02894a1bce46c5397ee889afafe704a839446b54dc81cb9c5f7d
openssl-signature_algorithm (1.3.0) sha256=a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80
opentelemetry-api (1.7.0) sha256=ccfd264ea6f2db5bf4185e3c07a1297977b44a944e2ce65457c4fe63a697214f
opentelemetry-api (1.8.0) sha256=3af51183daf0f56a164bc1579782245be70a40678566b9a393cbe5af28ea87c6
opentelemetry-common (0.23.0) sha256=da721190479d57bae0ad2207468f47f3e2c3b9a91024b5bc32c9d280183eb32c
opentelemetry-exporter-otlp (0.31.1) sha256=5358be17d7849cbcc4f49e1fc24105edc780a6f96c8e57b64192ab9a8e47474a
opentelemetry-helpers-mysql (0.4.0) sha256=d309c0b20825bdd14d4dbc75e0d3b381ffdad37d16424ceca3cb8453d9cb5a4f
opentelemetry-exporter-otlp (0.32.0) sha256=fd4c77a07bb96919e8ff8bbd19ed96d07cac1e368d8e920af2bf2ab02bfb1ec3
opentelemetry-helpers-mysql (0.5.0) sha256=8c2a5d5428aec271a7d2e25c158d06d4d8a914143b5004305964d1fcbc176eca
opentelemetry-helpers-sql (0.3.0) sha256=4bb08017d6a16dd41c4d1c53c7fd30f9c5bb691195d8b458933724627b3f37f9
opentelemetry-helpers-sql-processor (0.4.0) sha256=ec238d7a2887219bd247dc31d0eb8a1a03d414a899963b68e14bb9f4d18b23f4
opentelemetry-instrumentation-action_mailer (0.6.1) sha256=8384866bdb066ae14b9a1fe686ffaf1f23468326a35af64390c0395fcd471057
opentelemetry-instrumentation-action_pack (0.15.1) sha256=84fade740783caeebf260aaefcbf8f1a7a4c49f946944ff520a2fb1d6b07f273
opentelemetry-instrumentation-action_pack (0.16.0) sha256=f4d54806b96dff89af31fb971fe5b1f79dd41fcc46489ed7c5340a47ee12a7f9
opentelemetry-instrumentation-action_view (0.11.2) sha256=e6a099015d672dabc19993d6fca99ef1e7210361ef21549a6e2076a67719fafc
opentelemetry-instrumentation-active_job (0.10.1) sha256=aea1311224c20d064a8f218a44299171152dc36eeb531b9eba84bed8b3942a89
opentelemetry-instrumentation-active_model_serializers (0.24.0) sha256=8fe81e44167d17e45d9acfa588d20140c7640c323e58aca99e266de1bb3fce15
opentelemetry-instrumentation-active_record (0.11.1) sha256=1b083f34eea0449f8d6f4370b3fb4b935757fac6e4e538e67bb98211809e7c92
opentelemetry-instrumentation-active_storage (0.3.1) sha256=f89b0fef54921f17c0c4c38a6e0926d29afabd0ac98436fcdbb8bde85dfde89e
opentelemetry-instrumentation-active_support (0.10.1) sha256=82ea98367158797e33c6de96581f10aa4fe8adf0ebec832dcff5fd04c59bc57d
opentelemetry-instrumentation-all (0.90.1) sha256=7c6a1cb321fbf320618e644a2a0572881eccb7f26d27b61b7079f2dc64fc88e4
opentelemetry-instrumentation-anthropic (0.3.0) sha256=09bd9b4ba6189389a6c0f7ba49f1d11f387d93b411ab585137a48b59925a48de
opentelemetry-instrumentation-all (0.91.0) sha256=b077ce47da94e70e167157206034405f37ed0a4641d12ca8180a4b655c5727e2
opentelemetry-instrumentation-anthropic (0.4.0) sha256=0040e0d97e9a66ef32cc35612ff28d7310d4ec1cd2f949805a2017f00f4d2de0
opentelemetry-instrumentation-aws_lambda (0.6.0) sha256=1a3161393cfe9bc9eddd81a0668d076c38a0a2c3d5df40e95d02f5a8fcd3334c
opentelemetry-instrumentation-aws_sdk (0.11.0) sha256=67a21e754ddf51e2bb8c3e46e116aa9158d8db800f34c2a9b1e0da5a6ca911e3
opentelemetry-instrumentation-base (0.25.0) sha256=642a3a7f08354e6e969423327a4fa67ed2cca7ac6fe5ee09e55b17d1c576da27
@@ -2094,35 +2104,35 @@ CHECKSUMS
opentelemetry-instrumentation-concurrent_ruby (0.24.0) sha256=229bd8b72000c59de693609bb637b8a9114992f5e0ab03730d7fd7ef91f7d1d2
opentelemetry-instrumentation-dalli (0.29.2) sha256=21b82772ced1529288c7f08285d44d5690de11f3d275e24558a062f39a270f4f
opentelemetry-instrumentation-delayed_job (0.25.1) sha256=47f35b10d2bfd9ac7c2bbbe10dea095a2e25db2a84f5351860ead969d180c3ec
opentelemetry-instrumentation-ethon (0.27.0) sha256=bfd2e34a5f34c7114727b0e0c9d441a1c6c7a4cceb8374d90ae9332f009f3968
opentelemetry-instrumentation-excon (0.27.0) sha256=3d7e6e160f0328e1136646aabb23efefdb125854637d5bd57b849720f783b5bb
opentelemetry-instrumentation-faraday (0.31.0) sha256=1c00dc96d4c18890a34a20eef27eae536bf6558965e03e254bb7b84a4f09840b
opentelemetry-instrumentation-grape (0.5.1) sha256=a623608ef10e96c413f4d50b840082bf1ab9700126185d89ddbc8a29b49ec0ef
opentelemetry-instrumentation-ethon (0.28.0) sha256=5ab5eb0733fec27300047f1f0906453171732c663d0484968ce0582026256b2d
opentelemetry-instrumentation-excon (0.28.0) sha256=00bfd0bce489d5f924ab81c440098e99b6e4234f8968f942ce0753e2a326b99b
opentelemetry-instrumentation-faraday (0.32.0) sha256=21f78858c4d8986a9b89a330bc1f6ef03007d6893d009865b4539269f686cdfd
opentelemetry-instrumentation-grape (0.6.0) sha256=bc6f0ac3416b42bf096032ab79193326d6b50b12e8ccbcf028a78a4df492d057
opentelemetry-instrumentation-graphql (0.31.2) sha256=a4455f225427f8f9058247c8c0b351b8932567913c35ef049f7958801d401b1f
opentelemetry-instrumentation-grpc (0.4.1) sha256=5ffa2bb1d5ec69bcd1fe23e1d8c1a563a00351ce052fe9d76885cc43f21ebc87
opentelemetry-instrumentation-gruf (0.5.0) sha256=ee21be36e312e71b847c9a87168225625890121140a364b68d3668e0df58dacd
opentelemetry-instrumentation-http (0.28.0) sha256=0946a9593d64740780d16e041f9441945c4970b17566ce98e1a63bd3b276cacd
opentelemetry-instrumentation-http_client (0.27.0) sha256=6a639a21296d3b6d3934b2d5465bee5d5f9180208d0329a5bbe34b374030690e
opentelemetry-instrumentation-httpx (0.6.1) sha256=9050801826bb0f148f603b39b119c4f49591955ee53d3d0b2a8fb1eb8f558022
opentelemetry-instrumentation-http (0.29.0) sha256=c2981f22dac791f1768595c08b5338d29ad57bd98e23e9a2c0df7a1dc54122f1
opentelemetry-instrumentation-http_client (0.28.0) sha256=f6dadfed166d75d5632ae0b3521ed6a491080972923031489b85711e6d58fcb8
opentelemetry-instrumentation-httpx (0.7.0) sha256=3928185b62066cf6d8fe3b011dc5587ba53b09a5c7b573e36481b8d713d6aa03
opentelemetry-instrumentation-koala (0.23.0) sha256=8f324b50a2a64fd4994bb2b105a4cb0c80b64ec05cf5487d2daa906c650bc6f9
opentelemetry-instrumentation-lmdb (0.25.0) sha256=1e4d66d583ea242d4f72051062971f5af1ea353484d224abbd0aabdd1ce5f5cb
opentelemetry-instrumentation-mongo (0.25.0) sha256=d04585669f928ea82e7c469f996061d39d8ff184278d57cf4fc77a6d607f9c7a
opentelemetry-instrumentation-mysql2 (0.33.0) sha256=b49b7957d5eef59e046e73be3ca370518965d61495745b4cb7ece3ef5470bcf9
opentelemetry-instrumentation-net_http (0.27.0) sha256=f7ad7c5887646a09043b5f4e73717d9a897272f8032b883b0fadffbcc27f8990
opentelemetry-instrumentation-net_http (0.28.0) sha256=63b00c1c8fcfba15cd293ece8383d19bbc35e9b5cc04056b3e95799be11026f5
opentelemetry-instrumentation-pg (0.35.0) sha256=65a6e78bd45282b56021f1ee1b88b9fd318abf6812c32bd740465e6b9997aad4
opentelemetry-instrumentation-que (0.12.0) sha256=3b7a84341f6af5a04f8c57860aeba4033f87c855d40c611a2fc40dde849944fb
opentelemetry-instrumentation-racecar (0.6.1) sha256=833f6611906fb661f577e841d4ec52549474d32b4e8edea8048162348d35b845
opentelemetry-instrumentation-rack (0.29.0) sha256=9e2cbb8336087064cbe33b502d917d85b174162bc717efda1cfdbd182342f377
opentelemetry-instrumentation-rails (0.39.1) sha256=7959df7895543040fbb5cd3877c37bc9f95d79ff9d7749334314c50b871ac96f
opentelemetry-instrumentation-rack (0.30.0) sha256=30a54f7b44d4b91839622a20eb0b25a7c47084b37c2b03cfc149bfc4ef62303c
opentelemetry-instrumentation-rails (0.40.0) sha256=f794d477e8b48d9167ac1dbaf71dfc88e2a5647f76394cab7d1dfc6d5217b983
opentelemetry-instrumentation-rake (0.5.0) sha256=fa6bd019078975ac8a67eaea06294e4fe6707e6770d8ced88d74dc573b0a01ef
opentelemetry-instrumentation-rdkafka (0.9.0) sha256=f3beb56828c584d7d91a2c46f6e5a2ef82289b1d4445b1eb5bc13b80ab6aca89
opentelemetry-instrumentation-redis (0.28.0) sha256=8721957d1c527dd22bd564d17f3a8db252081abb302be189511282d023693900
opentelemetry-instrumentation-resque (0.8.0) sha256=559edde9d6273dd757ae5149ed36e26d147b63028d084121203f51c8cff805e5
opentelemetry-instrumentation-restclient (0.26.0) sha256=5d4e9d93ef51564a1023c076e17b4ac3b42fe81003321d9fb66e44886538bdce
opentelemetry-instrumentation-restclient (0.27.0) sha256=1abe208f5f43eff8648fa3ec3393c021bcbf30512f0fd69e4edbe8345ac3f899
opentelemetry-instrumentation-ruby_kafka (0.24.0) sha256=257e891f4ce630ba3e0669408d497b44afcc493cd49aed09343d5a51fa8952c2
opentelemetry-instrumentation-sidekiq (0.28.1) sha256=abc85d62996a5362e7a9fd7af9f6c709d01ce04795514d12fee5126335ae97ae
opentelemetry-instrumentation-sinatra (0.28.0) sha256=9f11d68c580a421cadd633aca1f8f92707d6b6995d48fffa045a48c187347f26
opentelemetry-instrumentation-trilogy (0.66.0) sha256=f09dead2fc09b84e5f84a9e39bdd351486549724f47471766230774831ecbe70
opentelemetry-instrumentation-sinatra (0.29.0) sha256=08595fec08d198df581d96aceb4b27998b84431e44a679950af7d00ab6559bdb
opentelemetry-instrumentation-trilogy (0.67.0) sha256=40394d3071d92aa418ef5aedab8e74f7683c0566c285a5418f75ca0586fd025f
opentelemetry-registry (0.4.0) sha256=903fa6bfaa29eac1c1d73a4fdd29b850977b5353b84b8cdff11222c00ad2968f
opentelemetry-sdk (1.10.0) sha256=43719949be8df24dcaeb86ebbf75636cda87d51a01af2729499b92a48b80521a
opentelemetry-semantic_conventions (1.36.0) sha256=c1b1607dbc7853aac7f9e23f6e8b76969c45b07f2b812a4aa4383c19a3b0f617
@@ -2131,7 +2141,7 @@ CHECKSUMS
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
overviews (1.0.0)
ox (2.14.23) sha256=4a9aedb4d6c78c5ebac1d7287dc7cc6808e14a8831d7adb727438f6a1b461b66
pagy (43.3.2) sha256=105683fccb86c1011a96c76da610c35c64aaf4cf412165606aadcb39e733e830
pagy (43.4.0) sha256=5962b476bab4b96aa6029be76d537dd9597aa00454508aecee001517d9744a9b
paper_trail (17.0.0) sha256=1c2842061d3874ca7015908e821e2aa14f9b982af2acb2a7974713bf79021c85
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70
@@ -2165,7 +2175,7 @@ CHECKSUMS
pry-rescue (1.6.0) sha256=985bfd506d9866b587fd86790cf8445266a41b7f92c627fc5b21ec7d92aba6db
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
puffing-billy (4.0.3) sha256=376fe2e2cc3ff9d48814a15153db80970cf0539ba026ac5108c971c2e160883c
puffing-billy (4.0.4) sha256=87015b0c41e0722b2171a0c5aa8130fd3f58aa1c016a1dc6dc569b2028aa846f
puma (7.2.0) sha256=bf8ef4ab514a4e6d4554cb4326b2004eba5036ae05cf765cfe51aba9706a72a8
puma-plugin-statsd (2.7.0) sha256=04f243a7233f4d06ec0e26f1a3522bce18a5910ae711763fabff22681bdad08b
raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882
@@ -2181,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) sha256=5069061b23dfa8706b9f0159ae8b9d35727359103178a26962b868a680ba7d95
rails (8.1.2.1) sha256=93ebf1efc792c9bc47e9795259c920312d3920008dad3ae634b7a0457ffe0af8
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) sha256=1289ece76b4f7668fc46d07e55cc992b5b8751f2ad85548b7da351b8c59f8055
railties (8.1.2.1) sha256=f4d902869541af4e5b5552d726062fa59ec0fd9078f7ab87720dbd93f22c43ee
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
rake-compiler-dock (1.11.0) sha256=eab51f2cd533eb35cea6b624a75281f047123e70a64c58b607471bb49428f8c2
@@ -2199,13 +2209,13 @@ CHECKSUMS
recaptcha (5.21.1) sha256=e003e9ceba9b993a9f0c6a828c192f2d46693cd2aa0b0beae94f936649507adb
redcarpet (3.6.1) sha256=d444910e6aa55480c6bcdc0cdb057626e8a32c054c29e793fa642ba2f155f445
redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae
redis-client (0.26.4) sha256=3ad70beff5da2653e02dfeae996e7d8d7147a558da12b16b2282ad345e4c7120
redis-client (0.28.0) sha256=888892f9cd8787a41c0ece00bdf5f556dfff7770326ce40bb2bc11f1bfec824b
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace
request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb
responders (3.2.0) sha256=89c2d6ac0ae16f6458a11524cae4a8efdceba1a3baea164d28ee9046bd3df55a
retriable (3.2.1) sha256=26e87a33391fae4c382d4750f1e135e4dda7e5aa32b6b71f1992265981f9b991
retriable (3.4.1) sha256=fb3f114b7d492121c158c01f3d5152b5a615c5b70d5877d0bc08c7ec3725c3bc
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
rinku (2.0.6) sha256=8b60670e3143f3db2b37efa262971ce3619ec23092045498ef9f077d82828d7d
roar (1.2.0) sha256=8db4d1ca79c57a5fb746c16c0d5661d7c3e0de3d9553dc016a88d2dba2929d08
@@ -2220,7 +2230,7 @@ CHECKSUMS
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
rspec-wait (1.0.2) sha256=865f921239325d3d26fc10ded4bdd485d8b58bcaaad1a28dd85ed15266b5a912
rubocop (1.85.1) sha256=3dbcf9e961baa4c376eeeb2a03913dca5e3987033b04d38fa538aa1e7406cc77
rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c
rubocop-factory_bot (2.28.0) sha256=4b17fc02124444173317e131759d195b0d762844a71a29fe8139c1105d92f0cb
rubocop-openproject (0.3.0) sha256=9554496e7ef0a2cf65dc2b32bee1bfa223b4f9ae058a5c603489d34e9001a828
@@ -2241,9 +2251,9 @@ CHECKSUMS
rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615
safety_net_attestation (0.5.0) sha256=c8cd01dd550dbe8553862918af6355a04672db11d218ec96104ce3955293f2aa
sanitize (7.0.0) sha256=269d1b9d7326e69307723af5643ec032ff86ad616e72a3b36d301ac75a273984
scimitar (2.14.0) sha256=93b29132ebd50d78e61d56c8a17406b1c01857448f2d075560fd6c7660ac9aa9
scimitar (2.15.0) sha256=700901afab9303b705a8f37644cd733ee3c3819b168d24a14a48dec060b16d63
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
selenium-devtools (0.143.0) sha256=23e8b364e1074a93a56ea0365ff739022a23a72e9033ad69832400c884417dc4
selenium-devtools (0.145.0) sha256=629c4964ebeb140f0e08cd6e88e241b07a995416a0e45d8416ba25cf4a7f0513
selenium-webdriver (4.41.0) sha256=cdc1173cd55cf186022cea83156cc2d0bec06d337e039b02ad25d94e41bedd22
semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163
shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34
@@ -2299,7 +2309,7 @@ CHECKSUMS
warden-basic_auth (0.2.1) sha256=bfc752e0109c0182c3e69e930284c5e1e81e7b4a354aeb2b5914ead1391f3c6e
webauthn (3.4.3) sha256=9be6f5f838f3405b0226e560aa40b67cc8c15ec9154509b997caa7ec9a05e1fc
webfinger (2.1.3) sha256=567a52bde77fb38ca6b67e55db755f988766ec4651c1d24916a65aa70540695c
webmock (3.26.1) sha256=4f696fb57c90a827c20aadb2d4f9058bbff10f7f043bd0d4c3f58791143b1cd7
webmock (3.26.2) sha256=774556f2ea6371846cca68c01769b2eac0d134492d21f6d0ab5dd643965a4c90
webrick (1.9.2) sha256=beb4a15fc474defed24a3bda4ffd88a490d517c9e4e6118c3edce59e45864131
websocket (1.2.11) sha256=b7e7a74e2410b5e85c25858b26b3322f29161e300935f70a0e0d3c35e0462737
websocket-driver (0.8.0) sha256=ed0dba4b943c22f17f9a734817e808bc84cdce6a7e22045f5315aa57676d4962
@@ -2307,7 +2317,7 @@ CHECKSUMS
will_paginate (4.0.1) sha256=107b226ebe1d393d274575956a7c472e1eefdd97d8828e01b72d425d15a875b9
with_advisory_lock (7.5.0) sha256=dadb2f1ed35a10ed7b9649a6769e6848bc64f735a85eb8a6e162a81d383a15bf
xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e
yabeda (0.15.0) sha256=81ce61c6e89d42ac3c5855aa2dcddfc2347c33d1595372d59d79e7dda4e72238
yabeda (0.16.0) sha256=7f6e51acd7d9a51d850ea8c3844f72a24882f1312b3fc3836052bcd63d384cba
yabeda-activerecord (0.1.2) sha256=1dd281a64e5742445a6718aa05e799ea08a397e9ab9c0d254ece447635a3e0e2
yabeda-prometheus-mmap (0.4.0) sha256=1a66120756d6f931f03a7784e08e79060d71681ff83a9f5287df2ff756e9e2c9
yabeda-puma-plugin (0.9.0) sha256=b78673ecc7ee30bc50691ddc41b7022c1c1801843900d5101418f4a14b550bc8
+1
View File
@@ -40,6 +40,7 @@ group :opf_plugins do
gem 'openproject-gantt', path: 'modules/gantt'
gem 'openproject-calendar', path: 'modules/calendar'
gem 'openproject-storages', path: 'modules/storages'
gem 'openproject-wikis', path: 'modules/wikis'
gem 'openproject-documents', path: 'modules/documents'
gem 'openproject-bim', path: 'modules/bim'
+1
View File
@@ -19,6 +19,7 @@
@import "step_wizard/footer_component"
@import "step_wizard/page_layout_component"
@import "users/hover_card_component"
@import "users/non_working_times/calendar_component"
@import "work_package_relations_tab/index_component"
@import "work_package_relations_tab/relation_component"
@import "work_package_types/pattern_input"
@@ -34,7 +34,10 @@ See COPYRIGHT and LICENSE files for more details.
<% else %>
<%= @event.event_title %>
<% end %>
<%= project_suffix %>
<% suffix = project_suffix %>
<% if suffix.present? %>
(<%= suffix %>)
<% end %>
</div>
<%=
render(
+1 -2
View File
@@ -45,8 +45,7 @@ class Activities::ItemComponent < ViewComponent::Base
return if activity_is_from_current_project?
kind = activity_is_from_subproject? ? "subproject" : "project"
suffix = I18n.t("events.title.#{kind}", name: link_to(@event.project.name, @event.project))
"(#{suffix})".html_safe # rubocop:disable Rails/OutputSafety
helpers.t("events.title.#{kind}_html", name: link_to(@event.project.name, @event.project))
end
def display_user?
@@ -29,10 +29,11 @@ See COPYRIGHT and LICENSE files for more details.
<div class="op-activity-list--item-subtitle">
<%=
# OG: html_safe usage has been double-checked and would otherwise require a lot of i18n key change
I18n.t(
i18n_key,
user: user_html,
datetime: datetime_html
).html_safe
user: h(user_html),
datetime: h(datetime_html)
).html_safe # rubocop:disable Rails/OutputSafety
%>
</div>
@@ -42,10 +42,12 @@ class Activities::ItemSubtitleComponent < ViewComponent::Base
def user_html
return unless @user
[
parts = [
helpers.avatar(@user, size: "mini"),
helpers.content_tag("span", helpers.link_to_user(@user), class: %w[spot-caption spot-caption_bold])
].join(" ")
]
safe_join(parts, " ")
end
def datetime_html
@@ -48,7 +48,7 @@ module EnterpriseEdition
def description
@description || begin
if I18n.exists?(:description_html, scope: i18n_scope)
I18n.t(:description_html, scope: i18n_scope).html_safe
helpers.t(:description_html, scope: i18n_scope)
else
I18n.t(:description, scope: i18n_scope)
end
@@ -85,7 +85,7 @@ module EnterpriseEdition
I18n.t("ee.upsell.plan_name", plan: plan.capitalize)
end
I18n.t("ee.upsell.plan_text_html", plan_name:).html_safe
helpers.t("ee.upsell.plan_text_html", plan_name:)
end
end
end
@@ -65,7 +65,7 @@ module EnterpriseEdition
end
def description
I18n.t("ee.teaser.description", trial_plan: plan_name).html_safe
helpers.t("ee.teaser.description_html", trial_plan: plan_name)
end
def plan_name
+8 -1
View File
@@ -31,7 +31,14 @@
module Groups
class RowComponent < OpPrimer::BorderBoxRowComponent
def name
render(Primer::Beta::Link.new(href: edit_group_path(model), font_weight: :bold)) { model.name }
depth = model.hierarchy_depth || 0
link = render(Primer::Beta::Link.new(href: edit_group_path(model), font_weight: :bold)) { model.name }
if depth > 0
tag.span(style: "margin-left: #{depth * 20}px") { link }
else
link
end
end
def user_count
@@ -113,6 +113,12 @@ module Members
end
end
def filter_group(query, group_id)
if group_id.present?
query.where(:group_hierarchy, "=", group_id)
end
end
protected
def filter_shares(query, role_id)
@@ -0,0 +1,60 @@
# 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
class WorkingTimesHeaderComponent < ApplicationComponent
def call
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t(:label_schedule_and_availability) }
header.with_breadcrumbs(
[{ href: my_account_path, text: t(:label_my_account) },
t(:label_schedule_and_availability)]
)
helpers.render_tab_header_nav(header, tabs)
end
end
def tabs
[
{
name: "working_hours",
path: my_working_hours_path,
label: t(:label_working_hours)
},
{
name: "non_working_times",
path: my_non_working_times_path(year: Date.current.year),
label: t(:label_non_working_days)
}
]
end
end
end
@@ -78,24 +78,20 @@ module OpenProject
private
def first_paragraph_content
return unless first_paragraph_ast
first_paragraph_ast
.inner_html
.html_safe # rubocop:disable Rails/OutputSafety
end
# rubocop:disable Rails/OutputSafety
# OG: html_safe double-checked and expected here,
# output is coming from format_text which we output elsewhere, too.
def first_paragraph
@first_paragraph ||= if body_children.any?
body_children
.first
.inner_html
.html_safe # rubocop:disable Rails/OutputSafety
.html_safe
else
""
end
end
# rubocop:enable Rails/OutputSafety
def first_paragraph_ast
@first_paragraph_ast ||= text_ast
@@ -1,20 +1,25 @@
<%= component_wrapper(tag: :div, class: "op-inplace-edit", data: { test_selector: wrapper_test_selector }) do %>
<% if display_field_component.present? && !enforce_edit_mode %>
<%= component_wrapper(
tag: :div,
class: "op-inplace-edit",
uniq_by: wrapper_uniq_by,
data: {
test_selector: wrapper_test_selector,
turbo_stream_target: wrapper_id,
inplace_edit_stable_key: wrapper_uniq_by,
inplace_edit_system_arguments: @system_arguments.to_json
}
) do %>
<% if display_field_component.present? && (!enforce_edit_mode || !writable?) %>
<%= render display_field_component %>
<% else %>
<%= primer_form_with(
model:,
url: inplace_edit_field_update_path(model: model.class.name, id: model.id, attribute:),
method: :patch,
data: { turbo_stream: true }
) do |form|
render_field_component = ->(f) { render edit_field_component(f) } # The render_inline_form method looses context and thus does not know about the `field_component` method
system_arguments = @system_arguments
<%= primer_form_with(**form_options) do |form|
render_field_component = ->(f) { render edit_field_component(f) } # The render_inline_form method looses context and thus does not know about the `field_component` method
system_arguments = @system_arguments
render_inline_form(form) do |f|
f.hidden name: "system_arguments_json", value: system_arguments.to_json
render_field_component.call(f)
end
render_inline_form(form) do |f|
f.hidden name: "system_arguments_json", value: system_arguments.to_json
render_field_component.call(f)
end
end %>
<% end %>
<% end %>
@@ -33,21 +33,38 @@ module OpenProject
class InplaceEditFieldComponent < ViewComponent::Base
include OpTurbo::Streamable
attr_reader :model, :attribute, :enforce_edit_mode
attr_reader :model, :attribute, :enforce_edit_mode, :open_in_dialog, :show_action_buttons, :truncated
def initialize(model:, attribute:, enforce_edit_mode: false,
update_registry: OpenProject::InplaceEdit::UpdateRegistry.default, **system_arguments)
def initialize(model:,
attribute:,
enforce_edit_mode: false,
open_in_dialog: false,
show_action_buttons: true,
truncated: false,
update_registry: OpenProject::InplaceEdit::UpdateRegistry.default,
**system_arguments)
super()
@model = model
@attribute = attribute
@enforce_edit_mode = enforce_edit_mode
@open_in_dialog = open_in_dialog
@show_action_buttons = show_action_buttons
@truncated = truncated
@update_registry = update_registry
@system_arguments = system_arguments
@system_arguments[:id] = system_arguments[:id] || SecureRandom.uuid
@system_arguments[:required] ||= required?
@system_arguments[:label] ||= field_label
@system_arguments[:truncated] = truncated
end
def field_class
OpenProject::InplaceEdit::FieldRegistry.fetch(attribute)
if custom_field?
OpenProject::InplaceEdit::FieldRegistry.fetch_for_custom_field_format(custom_field&.field_format)
else
OpenProject::InplaceEdit::FieldRegistry.fetch(attribute)
end
end
def edit_field_component(form)
@@ -55,6 +72,7 @@ module OpenProject
form:,
attribute:,
model:,
show_action_buttons:,
**@system_arguments
)
end
@@ -70,20 +88,102 @@ module OpenProject
def display_field_component
return nil if display_field_class.nil?
display_field_class.new(model:, attribute:, writable: writable?, **@system_arguments)
@display_field_component ||= build_display_field_component
end
def wrapper_key
model_class = @model.class.name.parameterize(separator: "_")
"op-inplace-edit-field--#{model_class}-#{model.id}--#{attribute.name}--#{@system_arguments[:id]}"
end
def wrapper_test_selector
"op-inplace-edit-field"
"op-inplace-edit-field--#{model_class}-#{model.id}--#{attribute.name}"
end
def wrapper_uniq_by
"#{model_class}_#{@model.id}_#{@attribute}"
end
def form_id
@system_arguments[:form_id]
end
def wrapper_id
@system_arguments[:wrapper_id]
end
def form_options
options = {
model: @model,
url: inplace_edit_field_update_path(
model: @model.class.name,
id: @model.id,
attribute: @attribute
),
method: :patch,
data: { turbo_stream: true,
test_selector: "op-inplace-edit-field--form" }
}
options[:id] = form_id if form_id.present?
options
end
def open_in_dialog?
@open_in_dialog || field_class.open_in_dialog? || (custom_field? && custom_field&.has_comment?)
end
def dialog_edit_url
return unless open_in_dialog?
inplace_edit_field_dialog_path(
model: model.class.name,
id: model.id,
attribute:,
system_arguments_json: @system_arguments
.except(:id)
.merge(page_component_id: @system_arguments[:id], writable: writable?)
.to_json
)
end
def model_class
@model_class ||= @model.class.name.parameterize(separator: "_")
end
private
def build_display_field_component
has_comment = custom_field? && custom_field&.has_comment?
additional_args = open_in_dialog? ? dialog_display_arguments : {}
display_field_class.new(
model:,
attribute:,
writable: writable?,
truncated:,
has_comment:,
# Show comment as read-only text when a non-writable user opens the dialog.
# enforce_edit_mode identifies the dialog context.
show_comment: enforce_edit_mode && !writable? && has_comment,
**@system_arguments.merge(additional_args)
)
end
def dialog_trigger_arguments
{
dialog_controller_name: "inplace-edit",
dialog_url: dialog_edit_url
}
end
# When inside a dialog and the field is not writable, strip dialog trigger args
# to prevent opening a nested dialog from the display component.
def dialog_display_arguments
return {} if enforce_edit_mode && !writable?
dialog_trigger_arguments
end
def writable?
return @writable if defined?(@writable)
@@ -95,6 +195,40 @@ module OpenProject
false
end
end
def field_label
# Check if this is a custom field attribute
if custom_field? && custom_field
return custom_field.name
end
label = model.class.human_attribute_name(attribute)
label = label.titleize if attribute.to_s.include?("_")
label
end
def required?
return @required if instance_variable_defined?(:@required)
@required = if @system_arguments.key?(:required)
@system_arguments[:required]
elsif custom_field?
# For custom fields, check the is_required attribute
custom_field&.is_required || false
else
false
end
end
def custom_field?
attribute.to_s.start_with?("custom_field_")
end
def custom_field
return @custom_field if defined?(@custom_field)
@custom_field = CustomField.find_by(id: attribute.to_s.sub("custom_field_", "").to_i)
end
end
end
end
@@ -0,0 +1,38 @@
<%=
render(
Primer::Alpha::Dialog.new(
title: dialog_title,
classes: "Overlay--size-large-portrait op-inplace-edit--dialog",
size: :large,
id: dialog_id
)
) do |d|
d.with_header(variant: :large)
d.with_body(classes: "Overlay-body_autocomplete_height",
test_selector: "async-dialog-content") do
render(edit_component)
end
d.with_footer do
component_collection do |footer_collection|
footer_collection.with_component(
Primer::Beta::Button.new(data: { "close-dialog-id": dialog_id })
) do
writable? ? t("button_cancel") : t("button_close")
end
if writable?
footer_collection.with_component(
Primer::Beta::Button.new(scheme: :primary,
type: :submit,
form: form_id,
data: {
test_selector: "save-inplace-edit-field-button",
turbo: true
})
) do
t("button_save")
end
end
end
end
end
%>
@@ -0,0 +1,81 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
class InplaceEditFieldDialogComponent < ViewComponent::Base
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
def initialize(model:, attribute:, system_arguments: {})
super()
@model = model
@attribute = attribute
@system_arguments = system_arguments
end
private
def writable?
@system_arguments[:writable] == true
end
def dialog_title
@system_arguments[:label] || @model.class.human_attribute_name(@attribute)
end
def dialog_id
model_class = @model.class.name.parameterize(separator: "_")
"inplace-edit-field-dialog--#{model_class}-#{@model.id}--#{@attribute}"
end
def wrapper_id
"##{dialog_id}"
end
def form_id
"inplace-edit-field-form-#{dialog_id}"
end
def edit_component
OpenProject::Common::InplaceEditFieldComponent.new(
model: @model,
attribute: @attribute,
enforce_edit_mode: true,
**@system_arguments.merge(
wrapper_id:,
form_id:,
show_action_buttons: false
)
)
end
end
end
end
@@ -0,0 +1,88 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
class BaseFieldComponent < ViewComponent::Base
include Primer::AttributesHelper
attr_reader :form, :attribute, :model, :show_action_buttons
def self.display_class
DisplayFields::DisplayFieldComponent
end
def self.open_in_dialog?
false
end
def initialize(form:, attribute:, model:, show_action_buttons: true, **system_arguments)
super()
@form = form
@attribute = attribute
@model = model
@show_action_buttons = show_action_buttons
@system_arguments = system_arguments
end
def comment_field_if_enabled(form)
return unless show_comment_field?
form.text_area(name: "#{model.class.model_name.param_key}[custom_comments][#{custom_field.id}]",
scope_name_to_model: false,
label: I18n.t("attributes.comment"),
value: model.custom_comment_for(custom_field)&.text,
rows: 5)
end
def show_comment_field?
custom_field? && custom_field&.has_comment?
end
def custom_field?
attribute.to_s.start_with?("custom_field_")
end
def custom_field
return @custom_field if defined?(@custom_field)
@custom_field = CustomField.find_by(id: attribute.to_s.sub("custom_field_", "").to_i)
end
def test_selector
if custom_field?
"custom-field-#{custom_field.id}"
end
end
end
end
end
end
@@ -0,0 +1,73 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
class BooleanInputComponent < BaseFieldComponent
def call
@system_arguments[:data] = merge_data(
@system_arguments,
**additional_arguments
)
form.check_box name: attribute,
**@system_arguments
comment_field_if_enabled(form)
end
private
def reset_url
inplace_edit_field_reset_path(
model: model.class.name,
id: model.id,
attribute:,
system_arguments_json: @system_arguments.to_json
)
end
def additional_arguments
if show_action_buttons
{
data: { controller: "inplace-edit",
inplace_edit_url_value: reset_url,
action: "click->inplace-edit#submitForm keydown.esc->inplace-edit#request",
test_selector: }
}
else
{ data: { test_selector: } }
end
end
end
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.
#++
module OpenProject
module Common
module InplaceEditFields
class CalculatedValueInputComponent < InplaceEditFields::TextInputComponent
def self.display_class
DisplayFields::CalculatedValueInputComponent
end
def initialize(form:, attribute:, model:, **system_arguments)
system_arguments ||= {}
system_arguments[:readonly] = true
super(form:, attribute:, model:, show_action_buttons: false, **system_arguments)
end
end
end
end
end
@@ -0,0 +1,71 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
class DateInputComponent < InplaceEditFields::TextInputComponent
def initialize(form:, attribute:, model:, **system_arguments)
system_arguments[:type] = :date
super
end
def call
@system_arguments[:data] = merge_data(
@system_arguments,
**additional_arguments
)
form.text_field name: attribute,
**@system_arguments
comment_field_if_enabled(form)
end
private
def additional_arguments
if show_action_buttons
{
data: { controller: "inplace-edit",
inplace_edit_url_value: reset_url,
action: "keydown.esc->inplace-edit#request " \
"keydown.enter->inplace-edit#submitForm " \
"change->inplace-edit#submitForm",
test_selector: }
}
else
{ data: { test_selector: } }
end
end
end
end
end
end
@@ -0,0 +1,81 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
module DisplayFields
class CalculatedValueInputComponent < DisplayFieldComponent
include OpPrimer::ComponentHelpers
include CalculatedValues::ErrorsHelper
attr_reader :model, :attribute
# If the writable attribute is not explicitly listed as an argument,
# it will be interpreted as one of the system_arguments and thus overwrite the `writable: false`
# rubocop:disable Lint/UnusedMethodArgument
def initialize(model:, attribute:, writable: nil, truncated: false, has_comment: false, show_comment: false,
**system_arguments)
super(model:, attribute:, writable: false, truncated:, has_comment:, show_comment:, **system_arguments)
end
# rubocop:enable Lint/UnusedMethodArgument
def render_calculation_error
error = custom_field&.first_calculation_error(model)
return unless error
render(Primer::OpenProject::FlexLayout.new(
align_items: :flex_start,
data: { test_selector: "error--custom_field_#{custom_field.id}" }
)) do |container|
container.with_column do
render Primer::Beta::Octicon.new(icon: :"alert-fill", color: :danger)
end
container.with_column(ml: 2) do
render Primer::Beta::Text.new(color: :danger) do
calculated_value_error_msg(error)
end
end
end
end
def render_tooltip
render Primer::Alpha::Tooltip.new(
for_id: @system_arguments[:id],
type: :description,
text: I18n.t("custom_fields.calculated_field_not_editable"),
direction: :s
)
end
end
end
end
end
end
@@ -0,0 +1,52 @@
<%=
flex_layout(
align_items: :flex_start,
justify_content: :space_between,
test_selector: @system_arguments[:test_selector],
classes: @system_arguments[:display_classes]
) do |flex|
flex.with_row(mb: 1) do
render OpenProject::Common::AttributeLabelComponent.new(
attribute:,
model:,
required: @system_arguments[:required],
hidden: @system_arguments[:visually_hide_label]
) do
render(Primer::Beta::Text.new(font_weight: :bold)) { @system_arguments[:label] }
end
end
flex.with_row(w: :full) do
input_specific_call
end
flex.with_row(w: :full) do
render OpenProject::Common::AttributeHelpTextCaptionComponent.new(
help_text: helpers.help_text_for(
model,
attribute,
current_user: helpers.current_user
)
)
end
if show_comment?
flex.with_row(w: :full, mt: 2) do
flex_layout do |inner_flex|
inner_flex.with_row do
render(Primer::Beta::Text.new(tag: :label, font_weight: :bold)) { t("attributes.comment") }
end
inner_flex.with_row(mt: 1) do
render(Primer::Beta::Text.new) { comment_text }
end
end
end
end
if (error_html = render_calculation_error).present?
flex.with_row(w: :full) { error_html }
end
end
%>
<%= render_tooltip %>
@@ -33,47 +33,136 @@ module OpenProject
module InplaceEditFields
module DisplayFields
class DisplayFieldComponent < ViewComponent::Base
include OpenProject::TextFormatting
include OpPrimer::ComponentHelpers
attr_reader :model, :attribute, :writable
attr_reader :model, :attribute, :writable, :truncated
def initialize(model:, attribute:, writable:, **system_arguments)
def initialize(model:, attribute:, writable:, truncated:, has_comment: false, show_comment: false, **system_arguments)
super()
@model = model
@attribute = attribute
@writable = writable
@truncated = truncated
@has_comment = has_comment
@show_comment = show_comment
@system_arguments = system_arguments
end
def render_display_value
value = model.public_send(attribute)
if value.present?
format_text(value)
if value.is_a?(TrueClass) || value.is_a?(FalseClass)
boolean_display_value(value)
elsif value.is_a?(Date) || value.is_a?(Time)
helpers.format_date(value)
elsif value.present? && value != [nil]
format_present_value(value)
else
""
t("placeholders.default")
end
end
def display_field_arguments
@display_field_arguments ||= {
classes: "op-inplace-edit--display-field #{'op-inplace-edit--display-field_editable' if writable}",
@display_field_arguments ||= if open_in_dialog?
base_arguments.merge(dialog_field_arguments)
else
base_arguments.merge(inline_edit_field_arguments)
end
end
def open_in_dialog?
@system_arguments[:dialog_controller_name].present?
end
def base_arguments
{
classes: display_field_classes,
id: @system_arguments[:id],
role: "button",
tabindex: 0
}
end
def dialog_field_arguments
return {} unless writable? || @has_comment
{
data: {
controller: "inplace-edit",
inplace_edit_url_value: edit_url,
action: writable ? "click->inplace-edit#request" : ""
controller: "inplace-edit async-dialog",
inplace_edit_dialog_url_value: @system_arguments[:dialog_url],
action: dialog_controller_actions,
test_selector: "inplace-edit-dialog-button-#{model.id}"
},
aria: {
label: [
I18n.t(:label_edit_x, x: @system_arguments[:label]),
I18n.t(:label_value_x, x: render_display_value)
].join(", ")
}
}
end
def call
def inline_edit_field_arguments
return {} unless writable?
{
data: {
controller: "inplace-edit",
inplace_edit_url_value: edit_url,
action: inline_controller_actions,
test_selector: "inplace-edit-field-button-#{model.id}"
}
}
end
def render_calculation_error
# no-op — subclasses may override to render a calculation error row
end
def show_comment?
@show_comment
end
def input_specific_call
render(Primer::BaseComponent.new(tag: :div, **display_field_arguments)) do
render_display_value
end
end
def render_tooltip
nil
end
def custom_field?
attribute.to_s.start_with?("custom_field_")
end
def custom_field
return @custom_field if defined?(@custom_field)
@custom_field = CustomField.find_by(id: attribute.to_s.sub("custom_field_", "").to_i)
end
private
def display_field_classes
# The later check catches non-editable users which should still see the comment in a dialog
clickable = writable? || open_in_dialog?
"op-inplace-edit--display-field#{' op-inplace-edit--display-field_clickable' if clickable}"
end
def format_present_value(value)
if custom_field?
helpers.format_value(value, custom_field)
else
value.to_s
end
end
def comment_text
model.custom_comment_for(custom_field)&.text.presence || t("placeholders.default")
end
def edit_url
inplace_edit_field_edit_path(
model: model.class.name,
@@ -82,6 +171,45 @@ module OpenProject
system_arguments_json: @system_arguments.to_json
)
end
def boolean_display_value(value)
I18n.t("general_text_#{value ? 'Yes' : 'No'}")
end
def writable?
writable && (@system_arguments[:readonly].nil? || @system_arguments[:readonly] == false)
end
def custom_field_values
CustomValue
.includes(custom_field: :custom_options)
.where(
custom_field_id: custom_field&.id,
customized_id: model.id
)
.to_a
end
def dialog_controller_actions
return "" unless writable? || @has_comment
[
"click->inplace-edit#openDialog",
"keydown.enter->inplace-edit#openDialog",
"keydown.space->inplace-edit#openDialog",
"inplace-edit:open-dialog->async-dialog#handleOpenDialog"
].join(" ")
end
def inline_controller_actions
return "" unless writable?
[
"click->inplace-edit#request",
"keydown.enter->inplace-edit#request",
"keydown.space->inplace-edit#request"
].join(" ")
end
end
end
end
@@ -1,16 +1,22 @@
.op-inplace-edit
&--display-field
&_editable
margin-left: -9px !important // cancel out 8px padding + 1px border
margin-right: -9px !important // cancel out 8px padding + 1px border
padding: var(--base-size-8)
width: calc(100% + 18px) !important
border: 1px solid transparent
border-radius: var(--borderRadius-medium)
padding: var(--base-size-4) var(--base-size-8)
border: 1px solid transparent
border-radius: var(--borderRadius-medium)
@include text-shortener(false)
&:hover, &:focus
border-color: var(--borderColor-default)
box-shadow: var(--shadow-inset)
&:hover, &:focus
border-color: var(--borderColor-default)
box-shadow: var(--shadow-inset)
&:not(&_editable)
&:not(&_clickable)
cursor: not-allowed
&:hover, &:focus
background-color: var(--bgColor-muted)
&--dialog
.op-inplace-edit--display-field
&:not(&_clickable)
&:hover, &:focus
border-color: transparent
box-shadow: none
@@ -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 OpenProject
module Common
module InplaceEditFields
module DisplayFields
class HierarchyListComponent < DisplayFieldComponent
def render_display_value
items = hierarchy_items
if items.empty?
t("placeholders.default")
elsif custom_field.multi_value?
items.join(", ")
else
items.first.to_s
end
end
private
def hierarchy_items
custom_field_values.filter_map do |cv|
CustomField::Hierarchy::Item.find_by(id: cv.value&.to_i)
end
end
end
end
end
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 OpenProject
module Common
module InplaceEditFields
module DisplayFields
class LinkInputComponent < DisplayFieldComponent
include OpenProject::TextFormatting
attr_reader :model, :attribute, :writable
def render_display_value
value = model.public_send(attribute)
if value.present?
render_link(value)
else
t("placeholders.default")
end
end
def render_link(href)
link = Addressable::URI.parse(href)
return href unless link
render(Primer::Beta::Link.new(href:, rel: "noopener noreferrer")) do
href
end
end
end
end
end
end
end
@@ -33,16 +33,35 @@ module OpenProject
module InplaceEditFields
module DisplayFields
class RichTextAreaComponent < DisplayFieldComponent
include OpenProject::TextFormatting
attr_reader :model, :attribute, :writable
def call
def input_specific_call
render(Primer::BaseComponent.new(tag: :div, **display_field_arguments)) do
render(Primer::BaseComponent.new(tag: :div,
classes: "op-uc-container op-uc-container_reduced-headings -multiline")) do
render_display_value
if field_value.present?
if truncated
render OpenProject::Common::AttributeComponent.new("#{attribute}-truncated-display-field",
attribute,
field_value,
lines: 3)
else
format_text(field_value)
end
else
t("placeholders.default")
end
end
end
end
private
def field_value
model.public_send(attribute)
end
end
end
end
@@ -0,0 +1,71 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
module DisplayFields
class SelectListComponent < DisplayFieldComponent
include CustomFieldsHelper
attr_reader :model, :attribute, :writable
def render_display_value
value = model.public_send(attribute)
if value.present? && value != [nil]
render_value(value)
else
t("placeholders.default")
end
end
private
def render_value(value)
if custom_field?
formatted_custom_field_values.presence || t("placeholders.default")
else
value.is_a?(Array) ? value.join(", ") : value.to_s
end
end
def formatted_custom_field_values
return @formatted_custom_field_values if defined?(@formatted_custom_field_values)
values = custom_field_values.map { |v| format_value(v.value, custom_field) }
@formatted_custom_field_values = custom_field&.multi_value? ? values.join(", ") : values.first
end
end
end
end
end
end
@@ -0,0 +1,71 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
module DisplayFields
class UserSelectListComponent < SelectListComponent
include CustomFieldsHelper
attr_reader :model, :attribute, :writable
def formatted_custom_field_values
return @formatted_custom_field_values if defined?(@formatted_custom_field_values)
cf_values = custom_field_values
users = cf_values.filter_map(&:typed_value)
@formatted_custom_field_values = if custom_field.multi_value?
flex_layout do |avatar_container|
users.each do |user|
avatar_container.with_row do
render_avatar(user)
end
end
end
else
render_avatar(users.first)
end
end
private
def render_avatar(user)
return unless user
render(::Users::AvatarComponent.new(user:, size: :mini))
end
end
end
end
end
end
@@ -0,0 +1,43 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
class FloatInputComponent < InplaceEditFields::TextInputComponent
def initialize(form:, attribute:, model:, **system_arguments)
system_arguments[:type] = :number
system_arguments[:step] = "any"
super
end
end
end
end
end
@@ -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 OpenProject
module Common
module InplaceEditFields
class HierarchyListComponent < BaseFieldComponent
include CustomFieldHierarchyTreeViewHelper
def self.display_class
DisplayFields::HierarchyListComponent
end
def self.open_in_dialog?
true
end
def initialize(form:, attribute:, model:, show_action_buttons: false, **system_arguments)
super
end
def call
form_field_name = "project[custom_field_values][]"
form.hidden(name: form_field_name, value: "", scope_name_to_model: false)
filterable_tree_view(form)
comment_field_if_enabled(form)
end
private
def filterable_tree_view(form)
form.html_content do
render(Primer::OpenProject::FilterableTreeView.new(
form_arguments: { builder: rails_builder, name: "custom_field_values" },
include_sub_items_check_box_arguments: { hidden: true },
filter_mode_control_arguments: { hidden: true }
)) do |tree_view|
item_options = {
expanded_fn: ->(*) { true },
label_fn:,
checked_fn:,
select_variant: custom_field.multi_value? ? :multiple : :single
}
populate_tree_view(tree_view, custom_field, item_options:)
end
end
end
# Primer's FormObject stores the underlying ActionView/Primer form builder
# as @builder. FilterableTreeView requires an ActionView::FormBuilder to
# generate its hidden form inputs via hidden_field.
def rails_builder
form.instance_variable_get(:@builder)
end
def checked_fn
current_values = Array(model.custom_value_for(custom_field)).map(&:value)
lambda { |item| current_values.include?(item.id.to_s) }
end
def label_fn
item_formatter = standard_tree_view_item_formatter
lambda { |item| item_formatter.format(item:) }
end
end
end
end
end
@@ -0,0 +1,42 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
class IntegerInputComponent < InplaceEditFields::TextInputComponent
def initialize(form:, attribute:, model:, **system_arguments)
system_arguments[:type] = :number
super
end
end
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.
#++
module OpenProject
module Common
module InplaceEditFields
class LinkInputComponent < TextInputComponent
def self.display_class
DisplayFields::LinkInputComponent
end
def initialize(form:, attribute:, model:, **system_arguments)
system_arguments[:type] = :url
super
end
end
end
end
end
@@ -31,38 +31,55 @@
module OpenProject
module Common
module InplaceEditFields
class RichTextAreaComponent < ViewComponent::Base
attr_reader :form, :attribute, :model
class RichTextAreaComponent < BaseFieldComponent
def self.display_class
DisplayFields::RichTextAreaComponent
end
def initialize(form:, attribute:, model:, **system_arguments)
super()
@form = form
@attribute = attribute
@model = model
@system_arguments = system_arguments
def initialize(form:, attribute:, model:, show_action_buttons: true, **system_arguments)
super
@system_arguments[:classes] = class_names(
@system_arguments[:classes],
"op-inplace-edit-field--text-area"
)
@system_arguments[:label] ||= model.class.human_attribute_name(attribute)
@system_arguments[:rich_text_options] ||= {}
@system_arguments[:rich_text_options][:primerized] = true
@system_arguments[:data] = merge_data(
@system_arguments,
data: { test_selector: }
)
end
def call
form.rich_text_area(name: attribute,
wrapper_data_attributes: {
controller: "ckeditor-focus",
ckeditor_focus_target: "editor",
ckeditor_focus_autofocus_value: true
},
wrapper_data_attributes: ckeditor_wrapper_data,
**@system_arguments)
comment_field_if_enabled(form)
render_action_buttons if show_action_buttons
end
def test_selector
if custom_field?
"custom-field-#{custom_field.id}"
else
"augmented-text-area-#{attribute.to_s.parameterize(separator: '_')}"
end
end
private
def ckeditor_wrapper_data
{
controller: "ckeditor-focus",
ckeditor_focus_target: "editor",
ckeditor_focus_autofocus_value: true
}
end
def render_action_buttons
form.group(layout: :horizontal, justify_content: :flex_end) do |button_group|
button_group.submit(name: :reset,
type: :submit,
@@ -0,0 +1,101 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
class SelectListComponent < BaseFieldComponent
def self.display_class
DisplayFields::SelectListComponent
end
def initialize(form:, attribute:, model:, show_action_buttons: true, **system_arguments)
super
@system_arguments[:autocomplete_options] ||= {}
set_autocomplete_defaults(model, attribute)
end
def call
if custom_field?
render_custom_field_input
else
render_autocompleter
end
comment_field_if_enabled(form)
render_action_buttons if show_action_buttons
end
private
def set_autocomplete_defaults(model, attribute)
opts = @system_arguments[:autocomplete_options]
opts[:model] ||= { id: model.id, name: model.name }
opts[:inputName] ||= attribute
opts[:wrapper_id] ||= @system_arguments[:wrapper_id]
opts[:focusDirectly] = true if opts[:focusDirectly].nil?
opts[:closeOnSelect] = false if opts[:closeOnSelect].nil?
end
def render_action_buttons
form.group(layout: :horizontal, justify_content: :flex_end) do |button_group|
button_group.submit(name: :reset,
type: :submit,
label: I18n.t(:button_cancel),
scheme: :default,
formaction: inplace_edit_field_reset_path(model: model.class.name, id: model.id, attribute:),
formmethod: :get)
button_group.submit(name: :submit,
label: I18n.t(:button_save),
scheme: :primary)
end
end
def render_custom_field_input
input_class = if custom_field.multi_value?
CustomFields::Inputs::MultiSelectList
else
CustomFields::Inputs::SingleSelectList
end
# Use fields_for to create the proper context for custom field inputs
form.fields_for(:custom_field_values) do |builder|
input_class.new(builder, custom_field:, object: model, **@system_arguments[:autocomplete_options])
end
end
def render_autocompleter
form.autocompleter(name: attribute, **@system_arguments)
end
end
end
end
end
@@ -31,29 +31,18 @@
module OpenProject
module Common
module InplaceEditFields
class TextInputComponent < ViewComponent::Base
attr_reader :form, :attribute, :model
def self.display_class
DisplayFields::DisplayFieldComponent
end
def initialize(form:, attribute:, model:, **system_arguments)
super()
@form = form
@attribute = attribute
@model = model
@system_arguments = system_arguments
@system_arguments[:label] ||= model.class.human_attribute_name(attribute)
end
class TextInputComponent < BaseFieldComponent
def call
@system_arguments[:data] = merge_data(
@system_arguments,
**additional_arguments
)
form.text_field name: attribute,
autofocus: true,
data: { controller: "inplace-edit",
inplace_edit_url_value: reset_url,
action: "keydown.esc->inplace-edit#request" },
**@system_arguments
comment_field_if_enabled(form)
end
private
@@ -66,6 +55,19 @@ module OpenProject
system_arguments_json: @system_arguments.to_json
)
end
def additional_arguments
if show_action_buttons
{
data: { controller: "inplace-edit",
inplace_edit_url_value: reset_url,
action: "keydown.esc->inplace-edit#request",
test_selector: }
}
else
{ data: { test_selector: } }
end
end
end
end
end
@@ -0,0 +1,56 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
class UserSelectListComponent < SelectListComponent
def self.display_class
DisplayFields::UserSelectListComponent
end
private
def render_custom_field_input
input_class = if custom_field.multi_value?
CustomFields::Inputs::MultiUserSelectList
else
CustomFields::Inputs::SingleUserSelectList
end
# Use fields_for to create the proper context for custom field inputs
form.fields_for(:custom_field_values) do |builder|
input_class.new(builder, custom_field:, object: model, **@system_arguments[:autocomplete_options])
end
end
end
end
end
end
@@ -0,0 +1,90 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpenProject
module Common
module InplaceEditFields
class VersionSelectListComponent < SelectListComponent
def initialize(form:, attribute:, model:, **system_arguments)
super
unless custom_field?
assign_defaults!
end
end
private
def render_custom_field_input
input_class = if custom_field.multi_value?
CustomFields::Inputs::MultiVersionSelectList
else
CustomFields::Inputs::SingleVersionSelectList
end
# Use fields_for to create the proper context for custom field inputs
form.fields_for(:custom_field_values) do |builder|
input_class.new(builder, custom_field:, object: model, **@system_arguments[:autocomplete_options])
end
end
def render_autocompleter
form.autocompleter(name: attribute, **@system_arguments) do |list|
model.assignable_versions.each do |version|
list.option(
label: version.name,
value: version.id,
selected: version.id == model.version&.id
)
end
end
end
def assign_defaults!
version = model.version
@system_arguments[:autocomplete_options][:inputValue] = version&.id
@system_arguments[:autocomplete_options][:model] = version_model
@system_arguments[:autocomplete_options][:decorated] = true
@system_arguments[:autocomplete_options][:closeOnSelect] = true
# Override inputName to use Rails form builder naming convention
@system_arguments[:autocomplete_options][:inputName] = input_name
end
def version_model
version ? { id: version.id, name: version.name } : nil
end
def input_name
"#{model.class.model_name.param_key}[#{attribute}]"
end
end
end
end
end
@@ -28,7 +28,11 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { "#{avatar @placeholder_user} #{h(@placeholder_user.name)}".html_safe }
header.with_title do
concat avatar(@placeholder_user)
concat @placeholder_user.name
end
header.with_breadcrumbs(breadcrumb_items)
if @current_user.allowed_globally?(:manage_placeholder_user)
@@ -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.
# ++
module Projects
module Concerns
module IdentifierSuggestion
def identifier_suggestion_data
suggestion_mode = Setting::WorkPackageIdentifier.alphanumeric? ? "semantic" : "legacy"
{
controller: "projects--identifier-suggestion",
"projects--identifier-suggestion-mode-value": suggestion_mode,
"projects--identifier-suggestion-url-value": projects_identifier_suggestion_path,
"projects--identifier-suggestion-set-name-first-value": I18n.t("js.projects.identifier_suggestion.set_name_first")
}
end
end
end
end
@@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
component_wrapper do
component_wrapper(data: identifier_suggestion_data) do
settings_primer_form_with(model: target_project, url: copy_project_path(source_project), method: :post) do |f|
flex_layout do |container|
container.with_row(mb: 3) do
@@ -41,6 +41,7 @@ See COPYRIGHT and LICENSE files for more details.
render(
Primer::Forms::FormList.new(
Projects::Settings::NameForm.new(f),
Projects::Settings::EditableIdentifierForm.new(f),
Projects::Settings::RelationsForm.new(f, invisible: params[:parent_id].present?),
Projects::Settings::CustomFieldsForm.new(f)
)
@@ -33,6 +33,7 @@ module Projects
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
include Projects::Concerns::IdentifierSuggestion
options :source_project, :target_project, :copy_options
end
@@ -28,7 +28,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
component_wrapper(target: "_top") do
component_wrapper(target: "_top", data: identifier_suggestion_data) do
settings_primer_form_with(
model: project,
url: workspaces_path,
@@ -39,6 +39,7 @@ See COPYRIGHT and LICENSE files for more details.
render(
Primer::Forms::FormList.new(
Projects::Settings::NameForm.new(f),
Projects::Settings::EditableIdentifierForm.new(f),
Projects::Settings::DescriptionForm.new(f),
Projects::Settings::RelationsForm.new(f, invisible: params[:parent_id].present?),
Projects::Settings::TypeForm.new(f)
+1
View File
@@ -33,6 +33,7 @@ module Projects
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
include Projects::Concerns::IdentifierSuggestion
options :project, :template, :step
@@ -27,9 +27,10 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<tr
<%= "id=\"#{row_css_id}\"".html_safe if row_css_id %>
<%= "class=\"#{row_css_class}\"".html_safe if row_css_class %>>
<%= content_tag(:tr,
id: row_css_id,
class: row_css_class
) do %>
<% columns.each do |column| %>
<td class="<%= column_css_class(column) %>">
<%= column_value(column) %>
@@ -40,4 +41,4 @@ See COPYRIGHT and LICENSE files for more details.
<%= link %>
<% end %>
</td>
</tr>
<% end %>
+11 -13
View File
@@ -211,17 +211,15 @@ module Projects
def project_status
return nil unless user_can_view_project_attributes?
content = "".html_safe
status_code = project.status_code
if status_code
classes = helpers.project_status_css_class(status_code)
content << content_tag(:span, "", class: "project-status--bulb -inline #{classes}")
content << content_tag(:span, helpers.project_status_name(status_code), class: "project-status--name #{classes}")
end
content
capture do
concat content_tag(:span, "", class: "project-status--bulb -inline #{classes}")
concat content_tag(:span, helpers.project_status_name(status_code), class: "project-status--name #{classes}")
end
end
end
def status_explanation
@@ -250,7 +248,7 @@ module Projects
def row_css_class
classes = %w[basics context-menu--reveal op-project-row-component]
classes << project_css_classes
classes += project_css_classes
classes << row_css_level_classes
classes.join(" ")
@@ -269,13 +267,13 @@ module Projects
end
def project_css_classes
s = " project ".html_safe
output = ["project"]
s << " root" if project.root?
s << " child" if project.child?
s << (project.leaf? ? " leaf" : " parent")
output << "root" if project.root?
output << "child" if project.child?
output << (project.leaf? ? "leaf" : "parent")
s
output
end
def column_css_class(column)
@@ -0,0 +1,55 @@
<%#-- 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: "change-identifier-dialog",
title: t("projects.settings.change_identifier_dialog_title")
)) do |dialog| %>
<% dialog.with_body do %>
<%= render(Primer::Alpha::Banner.new(scheme: :warning, mb: 3)) do %>
<%= t("projects.settings.change_identifier_warning") %>
<% end %>
<%= settings_primer_form_with(model: project,
url: project_identifier_path(project),
id: "change-identifier-form",
mt: 4) do |f| %>
<%= render(Primer::Forms::FormList.new(Projects::Settings::EditableIdentifierForm.new(f))) %>
<% end %>
<% end %>
<% dialog.with_footer do %>
<%= render(Primer::Beta::Button.new(data: { "close-dialog-id": "change-identifier-dialog" })) do %>
<%= t(:button_cancel) %>
<% end %>
<%= render(Primer::Beta::Button.new(scheme: :primary, type: :submit, form: "change-identifier-form")) do %>
<%= t("projects.settings.change_identifier") %>
<% end %>
<% end %>
<% end %>
@@ -28,25 +28,18 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Overviews
module ProjectCustomFields
class EditComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
module Projects
module Settings
class ChangeIdentifierDialogComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include CustomFieldHierarchyTreeViewHelper
include OpPrimer::FormHelpers
include OpTurbo::Streamable
attr_reader :wrapper_id
attr_reader :project
def initialize(project:, project_custom_field:, wrapper_id: nil)
def initialize(project:)
super
@project = project
@project_custom_field = project_custom_field
@wrapper_id = wrapper_id
end
def wrapper_uniq_by
@project_custom_field.id
end
end
end
@@ -46,6 +46,29 @@ See COPYRIGHT and LICENSE files for more details.
%>
<% end %>
<%= render(Primer::BaseComponent.new(tag: :section, mb: 4)) do %>
<%=
render(Primer::Beta::Subhead.new) do |component|
component.with_heading(tag: :h3, size: :medium) { t(:label_identifier) }
end
%>
<%=
settings_primer_form_with(model: project, url: project_settings_general_path(project)) do |f|
render(Primer::Forms::FormList.new(Projects::Settings::IdentifierForm.new(f)))
end
%>
<%=
render(
Primer::Beta::Button.new(
tag: :a,
href: identifier_update_dialog_project_identifier_path(project_id: project),
data: { turbo_stream: true },
mt: 2
)
) { t("projects.settings.change_identifier") }
%>
<% end %>
<%= render(Primer::BaseComponent.new(tag: :section, mb: 4)) do %>
<%=
render(Primer::Beta::Subhead.new) do |component|
@@ -24,19 +24,6 @@
end
end
header.with_action_button(
tag: :a,
mobile_icon: :pencil,
mobile_label: t("projects.settings.change_identifier"),
size: :medium,
href: project_identifier_path(@project),
aria: { label: t("projects.settings.change_identifier") },
title: t("projects.settings.change_identifier")
) do |button|
button.with_leading_visual_icon(icon: :pencil)
t("projects.settings.change_identifier")
end
header.with_action_menu(
menu_arguments: {
anchor_align: :end
@@ -32,7 +32,7 @@
end
end
unless hide_help_text?
unless hide_help_text_caption?
container.with_row do
render(
OpenProject::Common::AttributeHelpTextCaptionComponent.new(
@@ -34,16 +34,18 @@ class Projects::StatusButtonComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include ProjectStatusHelper
attr_reader :project, :user, :hide_help_text
attr_reader :project, :user, :hide_help_text, :hide_help_text_caption
alias :hide_help_text? :hide_help_text
alias :hide_help_text_caption? :hide_help_text_caption
def initialize(project:, user:, size: :medium, hide_help_text: false)
def initialize(project:, user:, size: :medium, hide_help_text: false, hide_help_text_caption: false)
super
@project = project
@user = user
@size = size
@hide_help_text = hide_help_text
@hide_help_text_caption = hide_help_text_caption
@status = find_status(project.status_code)
end
@@ -31,7 +31,10 @@ See COPYRIGHT and LICENSE files for more details.
<div class="generic-table--flex-container">
<div class="generic-table--container <%= container_class %>">
<div class="generic-table--results-container">
<table class="generic-table" data-controller="table-highlighting" <%= table_id ? "id=\"#{table_id}\"".html_safe : "" %>>
<%= content_tag :table,
id: table_id,
class: "generic-table",
data: { controller: "table-highlighting" } do %>
<colgroup>
<% columns.each do |column| %>
<col <%= "opHighlightCol" unless column.attribute == :hierarchy %>>
@@ -84,7 +87,7 @@ See COPYRIGHT and LICENSE files for more details.
<% end %>
<%= render_collection rows %>
</tbody>
</table>
<% end %>
<% if inline_create_link && show_inline_create %>
<div class="wp-inline-create-button">
<%= inline_create_link %>
+5 -4
View File
@@ -27,9 +27,10 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<tr
<%= "id=\"#{row_css_id}\"".html_safe if row_css_id %>
<%= "class=\"#{row_css_class}\"".html_safe if row_css_class %>>
<%= content_tag(:tr,
id: row_css_id,
class: row_css_class
) do %>
<% columns.each do |column| %>
<td class="<%= column_css_class(column) %>">
<%= column_value(column) %>
@@ -40,4 +41,4 @@ See COPYRIGHT and LICENSE files for more details.
<%= link %>
<% end %>
</td>
</tr>
<% end %>
@@ -104,7 +104,7 @@ module Settings
private
def internal_comments_translation = t("ee.features.internal_comments").html_safe
def internal_comments_translation = t("ee.features.internal_comments")
end
end
end
@@ -39,11 +39,11 @@
user_limit_row.with_column do
render(Primer::Beta::Text.new(color: :danger)) do
I18n.t(
helpers.link_translate(
"sharing.warning_user_limit_reached#{'_admin' if User.current.admin?}",
upgrade_url: OpenProject::Enterprise.upgrade_url,
entity: entity.model_name.human
).html_safe
i18n_args: { entity: entity.model_name.human },
links: { upgrade_url: OpenProject::Enterprise.upgrade_url }
)
end
end
end
+2 -2
View File
@@ -90,9 +90,9 @@ class Users::HoverCardComponent < ApplicationComponent
remaining_count_link = link_to(t("users.groups.more", count: remaining_count), user_path(@user))
if remaining_count > 0
t("users.groups.summary_with_more", names: summary_links, count_link: remaining_count_link).html_safe
t("users.groups.summary_with_more_html", names: summary_links, count_link: remaining_count_link)
else
t("users.groups.summary", names: summary_links).html_safe
t("users.groups.summary_html", names: summary_links)
end
end
end
@@ -10,7 +10,8 @@
data: {
turbo_method: :post
}
)) do
)
) do
I18n.t(:button_back)
end
end
@@ -20,6 +21,7 @@
buttons.with_component(
Primer::Beta::Button.new(
data: { "close-dialog-id": Users::Invitation::DialogComponent::DIALOG_ID },
mr: 1
)
) do
I18n.t(:button_cancel)
@@ -44,7 +44,8 @@ module Users::Invitation::ProjectStep
component_collection do |modal_footer|
modal_footer.with_component(
Primer::Beta::Button.new(
data: { "close-dialog-id": Users::Invitation::DialogComponent::DIALOG_ID }
data: { "close-dialog-id": Users::Invitation::DialogComponent::DIALOG_ID },
mr: 1
)
) do
I18n.t(:button_cancel)
@@ -0,0 +1,3 @@
<%= flex_layout(data: wrapper_data, classes: "users-non-working-times-calendar-view") do |calendar_page| %>
<% calendar_page.with_row(classes: "op-fc-wrapper", data: { "users--non-working-times-target" => "calendar" }) %>
<% end %>
@@ -0,0 +1,126 @@
# 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 Users
module NonWorkingTimes
class CalendarComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
options non_working_times: [],
year: Date.current.year,
user: nil
private
def can_update?
user.present? && UserNonWorkingTimes::UpdateContract.can_update?(user: User.current, target_user: user)
end
def can_create?
user.present? && UserNonWorkingTimes::CreateContract.can_create?(user: User.current, target_user: user)
end
def wrapper_data
data = {
"controller" => "users--non-working-times",
"users--non-working-times-events-value" => events_json,
"users--non-working-times-year-value" => year,
"users--non-working-times-locale-value" => I18n.locale,
"users--non-working-times-start-of-week-value" => first_day_of_week,
"users--non-working-times-working-days-value" => working_days.to_json
}
if can_create?
data["users--non-working-times-new-url-value"] = new_user_non_working_time_path(user)
end
data
end
def working_days
# Setting.working_days is mo=1, tu=2, we=3, th=4, fr=5, sa=6, su=7
# businessHours in fullcalendar is su=0, mo=1, tu=2, we=3, th=4, fr=5, sa=6
Setting.working_days.map { |day| day % 7 }.sort
end
def events_json
(global_events + user_events).to_json
end
def global_events
non_working_times
.grep(NonWorkingDay)
.map do |day|
{ date: day.date.iso8601, title: day.name, type: "global" }
end
end
def user_events
system_dates = non_working_times.grep(NonWorkingDay).to_set(&:date)
non_working_times
.grep(UserNonWorkingTime)
.map { |nwt| user_event_for(nwt, system_dates) }
end
def user_event_for(nwt, system_dates)
clipped = nwt.clip_to_year(year, system_non_working_dates: system_dates)
{
start: clipped.start_date.iso8601,
end: (clipped.end_date + 1.day).iso8601,
title: event_title(clipped),
working_days: clipped.working_days_count,
type: "user",
edit_url: can_update? ? edit_user_non_working_time_path(user, nwt.id) : nil
}.compact
end
def event_title(clipped)
base = I18n.t("label_x_working_days_time_off", count: clipped.working_days_count)
if clipped.continues_from_previous_year
"#{base} (#{I18n.t('label_continued_from_previous_year')})"
elsif clipped.continues_into_next_year
"#{base} (#{I18n.t('label_continues_into_next_year')})"
else
base
end
end
# Maps Setting.start_of_week to FullCalendar's firstDay convention.
# Setting: nil=locale default, 1=Monday, 6=Saturday, 7=Sunday
# FullCalendar firstDay: 0=Sunday, 1=Monday, ..., 6=Saturday
# Nil defaults to 1 (Monday) to match Rails/OpenProject convention.
def first_day_of_week
(Setting.start_of_week || 1) % 7
end
end
end
end
@@ -0,0 +1,45 @@
.users-non-working-times-year-overview
// 100% - page header height - subheader height
height: calc(100% - 180px)
@media (max-width: $breakpoint-sm)
height: 100%
// Set the display of calendar container
#tab-content-non_working_times
display: inline
.users-non-working-times-calendar-view
height: 100%
.op-fc-wrapper
flex-grow: 1
.users-non-working-times-calendar-view
.op-fc-wrapper
// Rounded corners for the entire calendar
.fc-multiMonthYear-view
border-radius: var(--borderRadius-medium) !important
// Global non-working days rendered as FullCalendar background events
.fc-bg-event.non-working-day--global
background-color: var(--bgColor-done-muted) !important
color: var(--fgColor-sponsors) !important
opacity: 0.8 !important
border: 1px solid var(--display-pink-scale-5) !important
// User non-working day ranges rendered as foreground all-day bars
.fc-event.non-working-day--user
background-color: var(--bgColor-accent-emphasis) !important
border-color: var(--bgColor-accent-emphasis) !important
color: var(--fgColor-onEmphasis) !important
border-radius: var(--borderRadius-medium) !important
cursor: pointer !important
.fc-multimonth-daygrid-table
.fc-day
background-color: var(--body-background) !important
&.fc-day-today
background-color: var(--bgColor-accent-muted) !important
color: var(--fgColor-accent) !important
@@ -0,0 +1,25 @@
<%= component_wrapper do %>
<%= render(Primer::Alpha::Dialog.new(id: DIALOG_ID, title:)) do |dialog| %>
<% dialog.with_body do %>
<%= render(Users::NonWorkingTimes::FormComponent.new(user:, non_working_time:)) %>
<% end %>
<% dialog.with_footer do %>
<% if non_working_time.persisted? && can_delete? %>
<%= render(
Primer::Beta::Button.new(
scheme: :danger,
tag: :a,
href: destroy_url,
mr: :auto,
data: { turbo_method: :delete, turbo_confirm: t(:text_are_you_sure) }
)
) do %>
<%= t(:button_delete) %>
<% end %>
<% end %>
<%= render(Primer::Beta::Button.new(data: { "close-dialog-id": DIALOG_ID })) { t(:button_cancel) } %>
<%= render(Primer::Beta::Button.new(scheme: :primary, form: Users::NonWorkingTimes::FormComponent::FORM_ID, type: :submit)) { t(:button_confirm) } %>
<% end %>
<% end %>
<% end %>
@@ -28,43 +28,32 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Overviews
module ProjectCustomFields
module Users
module NonWorkingTimes
class DialogComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
def initialize(project:, project_custom_field:)
super
@project = project
@project_custom_field = project_custom_field
DIALOG_ID = "non-working-time-dialog"
attr_reader :user, :non_working_time
def initialize(user:, non_working_time:, **)
super(nil, **)
@user = user
@non_working_time = non_working_time
end
private
def dialog_title
@project_custom_field.project_custom_field_section.name
def title
non_working_time.persisted? ? t(:button_edit_non_working_time) : t(:button_add_non_working_time)
end
def dialog_id
"project-custom-field-dialog-#{@project_custom_field.id}"
def can_delete?
UserNonWorkingTimes::DeleteContract.can_delete?(user: User.current, target_user: user)
end
def wrapper_id
"##{dialog_id}"
end
def body_component
fail NoMethodError, "Must be overridden in subclass"
end
def close_button_title
fail NoMethodError, "Must be overridden in subclass"
end
def footer_buttons(footer_collection)
# noop
def destroy_url
user_non_working_time_path(user, non_working_time)
end
end
end
@@ -0,0 +1,51 @@
<%= component_wrapper do %>
<%= primer_form_with(
model: non_working_time,
url: form_url,
method: form_method,
id: FORM_ID,
data: {
controller: "users--non-working-times-form",
"users--non-working-times-form-preview-url-value" => working_days_preview_url
}
) do |form|
render_inline_form(form) do |f|
f.group(layout: :horizontal) do |g|
g.single_date_picker(
name: :start_date,
label: I18n.t(:label_start_date),
required: true,
value: model.start_date&.iso8601,
datepicker_options: {
inDialog: Users::NonWorkingTimes::DialogComponent::DIALOG_ID,
data: {
action: "change->users--non-working-times-form#previewWorkingDays"
}
}
)
g.single_date_picker(
name: :end_date,
label: I18n.t(:label_end_date),
required: true,
value: model.end_date&.iso8601,
datepicker_options: {
inDialog: Users::NonWorkingTimes::DialogComponent::DIALOG_ID,
data: {
action: "change->users--non-working-times-form#previewWorkingDays"
}
}
)
g.text_field(
name: :working_days_display,
label: I18n.t(:label_working_days),
readonly: true,
value: model.working_days_count,
datepicker_options: { inDialog: Users::NonWorkingTimes::DialogComponent::DIALOG_ID },
data: { "users--non-working-times-form-target": "workingDaysInput" }
)
end
end
end %>
<% end %>
@@ -28,38 +28,37 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Overviews
module ProjectCustomFields
class EditDialogComponent < DialogComponent
private
module Users
module NonWorkingTimes
class FormComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
def body_component
Overviews::ProjectCustomFields::EditComponent.new(
project_custom_field: @project_custom_field,
project: @project,
wrapper_id:
)
FORM_ID = "non-working-time-form"
attr_reader :user, :non_working_time
def initialize(user:, non_working_time:, **)
super(nil, **)
@user = user
@non_working_time = non_working_time
end
def close_button_title
t("button_cancel")
end
def footer_buttons(footer_collection)
footer_collection.with_component(
Primer::Beta::Button.new(
scheme: :primary,
type: :submit,
form: "project-custom-field-edit-form",
data: {
test_selector: "save-project-attributes-button",
turbo: true
}
)
) do
t("button_save")
def form_url
if non_working_time.persisted?
user_non_working_time_path(user, non_working_time)
else
user_non_working_times_path(user)
end
end
def form_method
non_working_time.persisted? ? :patch : :post
end
def working_days_preview_url
working_days_preview_user_non_working_times_path(user)
end
end
end
end
@@ -0,0 +1,40 @@
<%= render(Primer::Box.new(border: true, border_radius: 3, p: 3, mb: 3)) do %>
<%= render(Primer::Beta::Heading.new(tag: :h3, mb: 2)) do %>
<%= t(:label_non_working_times_with_count, year:, count: total_user_days) %>
<% end %>
<% user_non_working_times.each do |nwt| %>
<% if can_update? %>
<%= link_to edit_href(nwt),
class: "color-bg-accent-emphasis color-fg-on-emphasis rounded-2 p-2 mb-1 d-block",
data: { controller: "async-dialog" } do %>
<%= range_label(nwt) %>
<% end %>
<% else %>
<%= render(Primer::Box.new(bg: :accent_emphasis, color: :on_emphasis, border_radius: 2, p: 2, mb: 1)) do %>
<%= range_label(nwt) %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= render(Primer::Box.new(border: true, border_radius: 3, p: 3)) do %>
<%= render(Primer::Beta::Heading.new(tag: :h3, mb: 2)) do %>
<%= t(:label_non_working_times_summary, year:) %>
<% end %>
<%= render(Primer::OpenProject::FlexLayout.new(justify_content: :space_between, mb: 1)) do |row| %>
<% row.with_column { t(:label_total_user_non_working_times) } %>
<% row.with_column { render(Primer::Beta::Text.new(font_weight: :bold, color: :accent)) { total_user_days.to_s } } %>
<% end %>
<%= render(Primer::OpenProject::FlexLayout.new(justify_content: :space_between, mb: 2)) do |row| %>
<% row.with_column { t(:label_total_global_non_working_days) } %>
<% row.with_column { render(Primer::Beta::Text.new(font_weight: :bold, color: :sponsors)) { global_day_count.to_s } } %>
<% end %>
<%= render(Primer::OpenProject::FlexLayout.new(justify_content: :space_between)) do |row| %>
<% row.with_column { render(Primer::Beta::Text.new(font_weight: :bold)) { t(:label_total_days_off) } } %>
<% row.with_column { render(Primer::Beta::Text.new(font_weight: :bold)) { total_days.to_s } } %>
<% end %>
<% end %>
@@ -0,0 +1,88 @@
# 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 Users
module NonWorkingTimes
class SidebarComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
options non_working_times: [],
year: Date.current.year,
user: nil
private
def user_non_working_times
system_dates = non_working_times.grep(NonWorkingDay).to_set(&:date)
non_working_times
.grep(UserNonWorkingTime)
.sort_by(&:start_date)
.map { |nwt| nwt.clip_to_year(year, system_non_working_dates: system_dates) }
end
def global_day_count
non_working_times.count { |d| d.is_a?(NonWorkingDay) }
end
def total_user_days
user_non_working_times.sum(&:working_days_count)
end
def total_days
total_user_days + global_day_count
end
def can_update?
user.present? && UserNonWorkingTimes::UpdateContract.can_update?(user: User.current, target_user: user)
end
def can_delete?
user.present? && UserNonWorkingTimes::DeleteContract.can_delete?(user: User.current, target_user: user)
end
def edit_href(clipped)
edit_user_non_working_time_path(user, clipped.id)
end
def range_label(clipped)
date_range = format_date_range(clipped.start_date, clipped.end_date)
"#{date_range}: #{I18n.t('label_x_working_days', count: clipped.working_days_count)}"
end
def format_date_range(first, last)
if first.year == last.year
"#{I18n.l(first, format: :short)} - #{I18n.l(last, format: :short)}, #{first.year}"
else
"#{I18n.l(first, format: :long)} - #{I18n.l(last, format: :long)}"
end
end
end
end
end
@@ -0,0 +1,30 @@
<%= render(Primer::OpenProject::SubHeader.new) do |component|
component.with_text { year.to_s }
component.with_action_button_group do |group|
group.with_button(icon: "arrow-left", tag: :a, **previous_year_attrs)
group.with_button(icon: "arrow-right", tag: :a, **next_year_attrs)
end
component.with_action_button(
tag: :a,
href: today_href,
leading_icon: :"op-calendar-day",
label: I18n.t(:label_today)
) do
I18n.t(:label_today_capitalized)
end
if can_create?
component.with_action_button(
scheme: :primary,
leading_icon: :plus,
label: I18n.t(:button_add_non_working_time),
data: { controller: "async-dialog" },
tag: :a,
href: new_non_working_time_href
) do
t(:button_add_non_working_time)
end
end
end %>
@@ -0,0 +1,69 @@
# 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 Users
module NonWorkingTimes
class SubHeaderComponent < ApplicationComponent
options :year, :user
def can_create?
UserNonWorkingTimes::CreateContract.can_create?(user: User.current, target_user: user)
end
def new_non_working_time_href
new_user_non_working_time_path(user)
end
def previous_year_attrs
{
href: path_for(year: year - 1),
aria: { label: I18n.t(:label_previous_year) }
}
end
def next_year_attrs
{
href: path_for(year: year + 1),
aria: { label: I18n.t(:label_next_year) }
}
end
def today_href
path_for(year: Date.current.year)
end
private
def path_for(year:)
url_for(controller: params[:controller], action: params[:action], user_id: params[:user_id], year:, tab: params[:tab])
end
end
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 Users
module NonWorkingTimes
class YearOverviewComponent < ApplicationComponent
attr_reader :non_working_times, :year, :user
def initialize(year:, non_working_times:, user:, **)
super(**)
@year = year
@non_working_times = non_working_times
@user = user
end
def call
render(Users::NonWorkingTimes::SubHeaderComponent.new(year:, user:)) +
render(Primer::Alpha::Layout.new(classes: "users-non-working-times-year-overview")) do |layout|
layout.with_main { render_calendar }
layout.with_sidebar(col_placement: :end, width: :wide) { render_sidebar }
end
end
private
def render_calendar
render(Users::NonWorkingTimes::CalendarComponent.new(non_working_times:, year:, user:))
end
def render_sidebar
render(Users::NonWorkingTimes::SidebarComponent.new(non_working_times:, year:, user:))
end
end
end
end
@@ -29,7 +29,8 @@ See COPYRIGHT and LICENSE files for more details.
<%=
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title do
"#{avatar(@user, hover_card: { active: false })} #{h(@user.name)}".html_safe
concat avatar(@user, hover_card: { active: false })
concat @user.name
end
header.with_breadcrumbs(breadcrumb_items)
@@ -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.
#++
class Users::WorkingHours::AvailabilityFactorForm < ApplicationForm
form do |form|
form.html_content do
render(Primer::Beta::Subhead.new(spacious: true)) do |component|
component.with_heading(tag: :div) do
I18n.t("users.working_hours.form.title_availability_factor")
end
component.with_description do
I18n.t("users.working_hours.form.availability_description")
end
end
end
form.text_field name: :availability_factor,
label: UserWorkingHours.human_attribute_name(:availability_factor),
caption: I18n.t("users.working_hours.form.availability_factor_caption"),
input_width: :large,
inputmode: "numeric",
value: model.availability_factor,
data: {
"users--working-hours-form-target": "availabilityFactorInput",
action: "input->users--working-hours-form#availabilityChanged"
},
trailing_visual: { text: { text: "%" } }
form.text_field name: :total_factored_hours,
label: I18n.t("users.working_hours.form.total_available_hours"),
input_width: :large,
readonly: true,
data: { "users--working-hours-form-target": "totalAvailableHoursDisplay" },
trailing_visual: { text: { text: I18n.t("users.working_hours.form.per_week") } }
end
end
@@ -0,0 +1,81 @@
<%#-- 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::Box.new(border: true, border_radius: 3, bg: :accent, p: 3)) do %>
<%= render(Primer::Box.new(display: :flex, justify_content: :space_between, align_items: :center, mb: 3)) do %>
<%= render(Primer::Beta::Heading.new(tag: :h3)) do %>
<%= t("users.working_hours.current_schedule.title") %>
<% end %>
<% if editable? %>
<%= render(
Primer::Beta::IconButton.new(
icon: :pencil,
tag: :a,
href: create_or_edit_path,
"aria-label": t("button_edit"),
data: { controller: "async-dialog" }
)
) %>
<% end %>
<% end %>
<%= render(Primer::Box.new(display: :flex, classes: "gap-3")) do %>
<%= render(
Users::WorkingHours::StatCardComponent.new(
label: t("users.working_hours.current_schedule.work_days"),
value: work_days_value,
subtitle: work_days_subtitle
)
) %>
<%= render(
Users::WorkingHours::StatCardComponent.new(
label: t("users.working_hours.current_schedule.work_hours"),
value: weekly_hours_value,
subtitle: weekly_hours_subtitle
)
) %>
<%= render(
Users::WorkingHours::StatCardComponent.new(
label: t("users.working_hours.current_schedule.availability_factor"),
value: availability_value,
subtitle: t("users.working_hours.current_schedule.availability_subtitle")
)
) %>
<%= render(
Users::WorkingHours::StatCardComponent.new(
label: t("users.working_hours.current_schedule.effective_hours"),
value: effective_hours_value,
subtitle: t("users.working_hours.current_schedule.effective_subtitle")
)
) %>
<% end %>
<% end %>
@@ -0,0 +1,105 @@
# 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 Users
module WorkingHours
class CurrentScheduleComponent < ApplicationComponent
attr_reader :working_hours, :user
def initialize(working_hours:, user:, **)
super(working_hours, **)
@working_hours = working_hours
@user = user
end
def editable?
if working_hours && working_hours.valid_from == Date.current
UserWorkingHours::UpdateContract.can_update?(user: User.current, working_hours:)
else
UserWorkingHours::CreateContract.can_create?(user: User.current, target_user: user)
end
end
def work_days_value
return "" unless working_hours
UserWorkingHours::DAYS.count { |day| working_hours.public_send(day) > 0 }.to_s
end
def work_days_subtitle
return t("users.working_hours.current_schedule.not_set") unless working_hours
working_hours.working_day_ranges
end
def weekly_hours_value
return "" unless working_hours
format_hours(working_hours.weekly_working_hours)
end
def weekly_hours_subtitle
return t("users.working_hours.current_schedule.not_set") unless working_hours
working_hours.working_days_summary
end
def availability_value
return "" unless working_hours
"#{working_hours.availability_factor}%"
end
def effective_hours_value
return "" unless working_hours
format_hours(working_hours.effective_weekly_working_hours)
end
private
def format_hours(hours)
formatted = helpers.number_with_precision(hours,
precision: 2,
strip_insignificant_zeros: true,
separator: I18n.t("number.format.separator"))
"#{formatted}h"
end
def create_or_edit_path
if working_hours && working_hours.valid_from == Date.current
edit_user_working_hour_path(user, working_hours, current: true)
else
new_user_working_hour_path(user, current: true)
end
end
end
end
end
@@ -0,0 +1,161 @@
# 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 Users::WorkingHours::DaysAndHoursForm < ApplicationForm
def initialize(is_first_form:)
super()
@is_first_form = is_first_form
end
form do |form|
form.html_content do
render(Primer::Beta::Subhead.new(spacious: !@is_first_form)) do |component|
component.with_heading(tag: :div, size: :medium) do
I18n.t("users.working_hours.form.title_days_and_hours")
end
end
if model.errors[:days].present?
render(Primer::Alpha::Banner.new(mb: 3, icon: :stop, scheme: :danger)) { model.errors[:days].join("\n") }
end
end
form.group(layout: :horizontal, mb: 2) do |group|
ordered_days.each do |day|
group.hidden name: "#{day}_hours", value: 0
group.check_box name: "day_enabled_#{day}",
data: {
"users--working-hours-form-target": "dayCheckbox",
day: day,
action: "users--working-hours-form#dayToggled"
},
checked: day_enabled?(day),
label: full_day_name(day),
label_arguments: { mr: 3 }
end
end
form.radio_button_group(name: "hours_mode", label: I18n.t("users.working_hours.form.hours_mode_label"),
mb: 2) do |group|
group.radio_button(
label: I18n.t("users.working_hours.form.same_hours_mode"),
value: "same",
checked: all_same_hours?,
data: { action: "users--working-hours-form#hoursModeChanged" }
)
group.radio_button(
label: I18n.t("users.working_hours.form.individual_hours_mode"),
value: "individual",
checked: !all_same_hours?,
data: { action: "users--working-hours-form#hoursModeChanged" }
)
end
copy_day_errors_to_shared_hours
form.group(data: { "users--working-hours-form-target": "sameHoursSection" }) do |group|
group.text_field name: :shared_hours,
label: UserWorkingHours.human_attribute_name(:shared_hours),
input_width: :large,
value: shared_hours,
data: {
"users--working-hours-form-target": "sharedHoursInput",
action: "input->users--working-hours-form#hoursChanged blur->users--working-hours-form#hoursFormatted"
},
trailing_visual: { text: { text: I18n.t("users.working_hours.form.per_day") } }
end
form.group(data: { "users--working-hours-form-target": "individualSection" }) do |group|
ordered_days.each do |day|
group.text_field name: "#{day}_hours",
label: UserWorkingHours.human_attribute_name("#{day}_hours"),
value: day_hours(day),
input_width: :large,
data: {
"users--working-hours-form-target": "dayHoursInput",
day: day,
action: "input->users--working-hours-form#hoursChanged blur->users--working-hours-form#hoursFormatted"
},
disabled: !day_enabled?(day)
end
end
form.text_field name: :total_work_hours,
label: I18n.t("users.working_hours.form.total_work_hours"),
input_width: :large,
readonly: true,
data: { "users--working-hours-form-target": "totalWorkHoursDisplay" },
trailing_visual: { text: { text: I18n.t("users.working_hours.form.per_week") } }
end
private
def ordered_days
# DAYS = [monday(0), tuesday(1), ..., saturday(5), sunday(6)]
# Setting.start_of_week: 1=Monday, 6=Saturday, 7=Sunday, nil=locale default (treat as Monday)
start_index = case Setting.start_of_week
when 6 then UserWorkingHours::DAYS.index(:saturday)
when 7 then UserWorkingHours::DAYS.index(:sunday)
else 0 # Monday
end
UserWorkingHours::DAYS.rotate(start_index)
end
def day_enabled?(day)
model.public_send(day) > 0
end
def day_hours(day)
"#{model.public_send("#{day}_hours").round(2)}h"
end
def all_same_hours?
enabled = UserWorkingHours::DAYS.select { |d| day_enabled?(d) }
return true if enabled.empty?
enabled.map { |d| day_hours(d) }.uniq.one?
end
def shared_hours
first_enabled = UserWorkingHours::DAYS.find { |d| day_enabled?(d) }
first_enabled ? day_hours(first_enabled) : "#{Setting.hours_per_day.round(2)}h"
end
def copy_day_errors_to_shared_hours
UserWorkingHours::DAYS
.flat_map { |day| model.errors[:"#{day}_hours"] }
.uniq
.each { |message| model.errors.add(:shared_hours, message) }
end
def full_day_name(day)
I18n.t("date.day_names")[UserWorkingHours::DAY_ABBR_INDEX[day]]
end
end
@@ -0,0 +1,45 @@
<%#-- 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:, position: :right, size: :xlarge)) do |dialog| %>
<% dialog.with_header(variant: :large) %>
<% dialog.with_body do %>
<%= render(Users::WorkingHours::FormComponent.new(user:, working_hours:, show_valid_from:)) %>
<% end %>
<% dialog.with_footer do %>
<%= component_collection do |footer| %>
<% footer.with_component(Primer::Beta::Button.new(data: { "close-dialog-id": DIALOG_ID })) do %>
<%= t(:button_cancel) %>
<% end %>
<% footer.with_component(Primer::Beta::Button.new(scheme: :primary, form: Users::WorkingHours::FormComponent::FORM_ID, type: :submit)) do %>
<%= working_hours.persisted? ? t(:button_save) : t(:button_create) %>
<% end %>
<% end %>
<% end %>
<% end %>
@@ -0,0 +1,57 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Users
module WorkingHours
class DialogComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
DIALOG_ID = "working-hours-dialog"
attr_reader :user, :working_hours, :show_valid_from
def initialize(user:, working_hours:, show_valid_from: true, **)
super(nil, **)
@user = user
@working_hours = working_hours
@show_valid_from = show_valid_from
end
def title
if show_valid_from
t("users.working_hours.form.title")
else
t("users.working_hours.form.title_current")
end
end
end
end
end
@@ -27,11 +27,12 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= attachments.select { |a| a.readable? and a.visible? }
.map do |a|
{ content_type: CGI::escape_html(a.content_type),
filename: CGI::escape_html(a.filename),
description: CGI::escape_html(a.description),
url: url_for(controller: "/attachments", action: "show", id: a, filename: a.filename),
is_image: !!a.image? } # doing the !! as image? for whatever reason returns null or a number
end.to_json.html_safe %>
<%= component_wrapper do %>
<%= primer_form_with(**form_options) do |f| %>
<% if show_valid_from %>
<%= render(Users::WorkingHours::ValidFromForm.new(f)) %>
<% end %>
<%= render(Users::WorkingHours::DaysAndHoursForm.new(f, is_first_form: !show_valid_from)) %>
<%= render(Users::WorkingHours::AvailabilityFactorForm.new(f)) %>
<% end %>
<% end %>
@@ -0,0 +1,79 @@
# 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 Users
module WorkingHours
class FormComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
FORM_ID = "working-hours-form"
attr_reader :user, :working_hours, :show_valid_from
def initialize(user:, working_hours:, show_valid_from: true, **)
super(nil, **)
@user = user
@working_hours = working_hours
@show_valid_from = show_valid_from
end
def form_url
url_params = show_valid_from ? {} : { current: true }
if working_hours.persisted?
user_working_hour_path(user, working_hours, **url_params)
else
user_working_hours_path(user, **url_params)
end
end
def form_options
{
model: working_hours,
url: form_url,
method: form_method,
html: {
id: FORM_ID,
data: {
turbo: true,
controller: "users--working-hours-form"
}
}
}
end
def form_method
working_hours.persisted? ? :patch : :post
end
end
end
end
@@ -0,0 +1,110 @@
# 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 Users
module WorkingHours
class ScheduleRowComponent < OpPrimer::BorderBoxRowComponent
def working_hours
model
end
def start_date
helpers.format_date(working_hours.valid_from)
end
def work_days
working_hours.working_days_summary
end
def work_hours
formatted = helpers.number_with_precision(working_hours.weekly_working_hours,
precision: 2,
strip_insignificant_zeros: true,
separator: I18n.t("number.format.separator"))
"#{formatted}h"
end
def availability_factor
"#{working_hours.availability_factor}%"
end
def effective_work_hours
"#{working_hours.effective_weekly_working_hours}h"
end
def button_links
[action_menu]
end
def action_menu
render(Primer::Alpha::ActionMenu.new) do |menu|
menu.with_show_button(icon: "kebab-horizontal", "aria-label": t(:label_more), scheme: :invisible)
add_edit_item(menu) if can_update?
add_delete_item(menu) if can_delete?
end
end
private
def can_update?
UserWorkingHours::UpdateContract.can_update?(user: User.current, working_hours:)
end
def can_delete?
UserWorkingHours::DeleteContract.can_delete?(user: User.current, target_user: table.user)
end
def add_edit_item(menu)
menu.with_item(label: t(:button_edit),
href: edit_user_working_hour_path(table.user, working_hours),
tag: :a,
content_arguments: { data: { controller: "async-dialog" } }) do |item|
item.with_leading_visual_icon(icon: :pencil)
end
end
def add_delete_item(menu)
menu.with_item(label: t(:button_delete),
href: user_working_hour_path(table.user, working_hours),
tag: :a,
scheme: :danger,
content_arguments: {
data: {
"turbo-method": :delete,
"turbo-stream": true,
"turbo-confirm": t("users.working_hours.destroy.confirm")
}
}) do |item|
item.with_leading_visual_icon(icon: :trash)
end
end
end
end
end
@@ -0,0 +1,109 @@
# 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 Users
module WorkingHours
class ScheduleTableComponent < OpPrimer::BorderBoxTableComponent
columns :start_date, :work_days, :work_hours, :availability_factor, :effective_work_hours
main_column :work_days
attr_reader :variant, :user
def initialize(rows:, variant:, user:, **)
super(rows:, **)
@variant = variant
@user = user
end
def has_actions?
true
end
def blank_icon
:calendar
end
def row_class
Users::WorkingHours::ScheduleRowComponent
end
def mobile_title
I18n.t("users.working_hours.table.mobile_title")
end
def headers
[
[:start_date, { caption: I18n.t("users.working_hours.table.start_date") }],
[:work_days, { caption: I18n.t("users.working_hours.table.work_days") }],
[:work_hours, { caption: I18n.t("users.working_hours.table.work_hours") }],
[:availability_factor, { caption: I18n.t("users.working_hours.table.availability_factor") }],
[:effective_work_hours, { caption: I18n.t("users.working_hours.table.effective_work_hours") }]
]
end
def action_row_header_content
return unless variant == :future
return unless UserWorkingHours::CreateContract.can_create?(user: User.current, target_user: user)
render(Primer::Beta::IconButton.new(
icon: :plus,
scheme: :invisible,
tag: :a,
href: new_user_working_hour_path(user),
data: { turbo: true, controller: "async-dialog" },
"aria-label": I18n.t("users.working_hours.future.add_button")
))
end
def blank_title
I18n.t("users.working_hours.#{@variant}.blank_title")
end
def blank_description
I18n.t("users.working_hours.#{@variant}.blank_description")
end
def render_blank_slate
render(Primer::Beta::Blankslate.new(border: false)) do |component|
component.with_visual_icon(icon: :calendar, size: :medium)
component.with_heading(tag: :h2) { blank_title }
component.with_description { blank_description }
if variant == :future && UserWorkingHours::CreateContract.can_create?(user: User.current, target_user: user)
component.with_primary_action(href: new_user_working_hour_path(user),
data: { turbo: true,
controller: "async-dialog" }) do
I18n.t("users.working_hours.future.add_button")
end
end
end
end
end
end
end
@@ -0,0 +1,34 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%= render(Primer::Box.new(border: true, border_radius: 3, bg: :default, p: 4, flex: 1)) do %>
<%= render(Primer::Beta::Text.new(color: :subtle, font_size: 4, tag: :p)) { label } %>
<%= render(Primer::Beta::Text.new(font_weight: :normal, font_size: 1, mb: 2)) { value } %>
<%= render(Primer::Beta::Text.new(color: :subtle, font_size: :small, tag: :p)) { subtitle } %>
<% 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.
#++
module Users
module WorkingHours
class StatCardComponent < ApplicationComponent
attr_reader :label, :value, :subtitle
def initialize(label:, value:, subtitle:, **)
super(nil, **)
@label = label.to_s
@value = value.to_s
@subtitle = subtitle.to_s
end
end
end
end
@@ -0,0 +1,50 @@
# 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 Users::WorkingHours::ValidFromForm < ApplicationForm
form do |f|
f.html_content do
render(Primer::Beta::Subhead.new) do |component|
component.with_heading(tag: :div, size: :medium) do
I18n.t("users.working_hours.form.title_future_dates")
end
end
end
f.single_date_picker name: :valid_from,
type: "date",
required: true,
input_width: :large,
datepicker_options: { inDialog: Users::WorkingHours::DialogComponent::DIALOG_ID },
value: model.valid_from&.iso8601,
caption: I18n.t("users.working_hours.form.start_date_caption"),
label: I18n.t("users.working_hours.form.start_date")
end
end
@@ -42,13 +42,13 @@
target: "_blank"
)
) do
I18n.t("js.label_committed_link", revision_identifier: short_revision)
I18n.t(:label_committed_link, revision_identifier: short_revision)
end
I18n.t(
"js.label_committed_at",
committed_revision_link: committed_text.html_safe,
:label_committed_at_html,
committed_revision_link: committed_text,
date: format_time(changeset.committed_on)
).html_safe
)
end
end
end
@@ -69,13 +69,13 @@
target: "_blank"
)
) do
I18n.t("js.label_committed_link", revision_identifier: short_revision)
I18n.t(:label_committed_link, revision_identifier: short_revision)
end
I18n.t(
"js.label_committed_at",
committed_revision_link: committed_text.html_safe,
t(
:label_committed_at_html,
committed_revision_link: committed_text,
date: format_time(changeset.committed_on)
).html_safe
)
end
end
end
@@ -0,0 +1,64 @@
<%#
-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#
%>
<%=
render(
Primer::OpenProject::DangerDialog.new(
id: "change-identifiers-dialog",
title: I18n.t("admin.settings.work_packages_identifier.dialog.title"),
confirm_button_text: I18n.t("admin.settings.work_packages_identifier.dialog.confirm_button"),
cancel_button_text: I18n.t("button_close"),
size: :large,
form_arguments: {
action: admin_settings_work_packages_identifier_path,
method: :patch
}
)
) do |dialog|
dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) do
I18n.t("admin.settings.work_packages_identifier.dialog.heading")
end
message.with_description_content(
I18n.t("admin.settings.work_packages_identifier.dialog.description")
)
end
dialog.with_confirmation_check_box_content(
I18n.t("admin.settings.work_packages_identifier.dialog.checkbox_label")
)
dialog.with_additional_details(display: :none) do
hidden_field_tag("settings[work_packages_identifier]", Setting::WorkPackageIdentifier::ALPHANUMERIC)
end
end
%>
@@ -0,0 +1,40 @@
# 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 WorkPackages
module Admin
module Settings
class ChangeIdentifiersDialogComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
end
end
end
end
@@ -0,0 +1,109 @@
<%#
-- 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::Banner.new(scheme: :warning, my: 3)) do
I18n.t(
"admin.settings.work_packages_identifier.banner.existing_identifiers_notice",
project_count: total_count
)
end
%>
<%=
render(border_box_container(mb: 3)) do |component|
component.with_header(font_weight: :bold) do
flex_layout do |header|
header.with_column(flex: 1) do
render(Primer::Beta::Text.new(font_weight: :semibold)) do
I18n.t("admin.settings.work_packages_identifier.box_header.label_project")
end
end
header.with_column(flex: 1) do
render(Primer::Beta::Text.new(font_weight: :semibold)) do
I18n.t("admin.settings.work_packages_identifier.box_header.label_previous_identifier")
end
end
header.with_column(flex: 1) do
render(Primer::Beta::Text.new(font_weight: :semibold)) do
I18n.t("admin.settings.work_packages_identifier.box_header.label_autofixed_suggestion")
end
end
header.with_column(flex: 1) do
render(Primer::Beta::Text.new(font_weight: :semibold)) do
I18n.t("admin.settings.work_packages_identifier.box_header.label_example_work_package_id")
end
end
end
end
displayed.each do |entry|
component.with_row do
flex_layout(align_items: :center) do |row|
row.with_column(flex: 1) do
render(Primer::Beta::Link.new(href: project_path(entry[:project]))) do
entry[:project].name
end
end
row.with_column(flex: 1) do
flex_layout(direction: :column) do |col|
col.with_row do
render(Primer::Beta::Text.new) { entry[:current_identifier] }
end
col.with_row do
render(Primer::Beta::Text.new(color: :danger, font_size: :small)) do
error_label(entry[:error_reason])
end
end
end
end
row.with_column(flex: 1) do
render(Primer::Beta::Text.new) { entry[:suggested_identifier] }
end
row.with_column(flex: 1) do
render(Primer::Beta::Text.new) { sample_wp_id(entry[:suggested_identifier]) }
end
end
end
end
if remaining_count.positive?
component.with_row do
render(Primer::Beta::Text.new(color: :muted)) do
I18n.t(
"admin.settings.work_packages_identifier.autofix_preview.remaining_projects",
count: remaining_count
)
end
end
end
end
%>
@@ -0,0 +1,65 @@
# 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 WorkPackages
module Admin
module Settings
class IdentifierAutofixSectionComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
DISPLAY_COUNT = WorkPackages::IdentifierAutofix::PreviewQuery::DISPLAY_COUNT
def initialize(projects_data:, total_count: projects_data.size)
super()
@total_count = total_count
@displayed = projects_data.first(DISPLAY_COUNT)
@remaining_count = [total_count - @displayed.size, 0].max
end
private
attr_reader :total_count, :displayed, :remaining_count
def error_label(error_reason)
I18n.t("admin.settings.work_packages_identifier.autofix_preview.error_#{error_reason}",
default: "")
end
# Produces a realistic-looking example work package ID for the preview table.
# The sequence number is derived deterministically from the identifier so it looks
# varied across projects but is stable across renders. Range: 1500.
def sample_wp_id(identifier)
n = (identifier.bytes.sum % 500) + 1
"#{identifier}-#{n}"
end
end
end
end
end
@@ -0,0 +1,99 @@
<%#
-- 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.
++#
%>
<%= component_wrapper(**wrapper_data_attrs) do %>
<%=
settings_primer_form_with(
scope: :settings, action: :update, method: :patch,
html: change_in_progress? ? {} : { id: form_id }
) do |f|
render_inline_settings_form(f) do |form|
form.radio_button_group(
name: :work_packages_identifier,
label: I18n.t("settings.work_packages.work_package_identifier"),
required: true,
**radio_button_options
)
form.html_content do
if change_in_progress?
render(Primer::Beta::Text.new(my: 3)) do
render(Primer::Beta::Spinner.new(size: :small, mr: 2)).to_s +
I18n.t("admin.settings.work_packages_identifier.in_progress.banner_message")
end
elsif completed?
render(Primer::Alpha::Banner.new(scheme: :success, dismiss_scheme: :remove, mb: 3)) do
I18n.t("admin.settings.work_packages_identifier.success_banner")
end
end
end
end
end
%>
<% unless change_in_progress? %>
<%= tag.div(
hidden: !show_autofix_section?,
data: { admin__work_packages_identifier_target: "autofixSection" }
) do %>
<%= render(
WorkPackages::Admin::Settings::IdentifierAutofixSectionComponent.new(
projects_data:,
total_count:
)
) %>
<% end %>
<div class="op-admin-settings-form-wrapper">
<%= render(
Primer::Beta::Button.new(
scheme: :primary,
type: :submit,
form: form_id,
hidden: show_autofix_section?,
data: { admin__work_packages_identifier_target: "saveButton" }
)
) { t("button_save") } %>
<%= render(
Primer::Beta::Button.new(
tag: :a,
href: confirm_dialog_admin_settings_work_packages_identifier_path,
scheme: :primary,
hidden: !show_autofix_section?,
data: {
turbo_stream: true,
admin__work_packages_identifier_target: "autofixButton"
}
)
) { t("admin.settings.work_packages_identifier.button_autofix") } %>
</div>
<% end %>
<% end %>
@@ -0,0 +1,110 @@
# 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 WorkPackages
module Admin
module Settings
class IdentifierSettingsFormComponent < ApplicationComponent
include OpPrimer::FormHelpers
include OpTurbo::Streamable
STATES = %i[edit change_in_progress completed].freeze
attr_reader :projects_data, :total_count, :state
def initialize(state: :edit)
raise ArgumentError, "Unknown state: #{state}" unless STATES.include?(state)
super()
@state = state
if state == :edit
result = WorkPackages::IdentifierAutofix::PreviewQuery.new.call
@projects_data = result.projects_data
@total_count = result.total_count
else
@projects_data = []
@total_count = 0
end
end
def has_problematic_projects?
total_count > 0
end
private
def form_id = "wp-identifier-settings-form"
def show_autofix_section?
state == :edit && Setting::WorkPackageIdentifier.alphanumeric? && has_problematic_projects?
end
def change_in_progress? = state == :change_in_progress
def completed? = state == :completed
def wrapper_data_attrs
if change_in_progress?
poll_for_changes_controller_attrs
else
work_package_identifier_controller_attrs
end
end
def poll_for_changes_controller_attrs
{
data: {
controller: "poll-for-changes",
poll_for_changes_url_value: url_helpers.status_admin_settings_work_packages_identifier_path,
poll_for_changes_interval_value: 5000
}
}
end
def work_package_identifier_controller_attrs
{
data: {
controller: "admin--work-packages-identifier",
admin__work_packages_identifier_has_problematic_projects_value: has_problematic_projects?
}
}
end
def radio_button_options
if change_in_progress?
{ button_options: { disabled: true } }
else
{ button_options: { data: { action: "change->admin--work-packages-identifier#handleChange" } } }
end
end
end
end
end
end
@@ -96,15 +96,17 @@ module WorkPackages
end
def mobile_description
text = if @manually_scheduled
I18n.t("work_packages.datepicker_modal.banner.description.manual_mobile")
else
I18n.t("work_packages.datepicker_modal.banner.description.automatic_mobile")
end
text =
if @manually_scheduled
I18n.t("work_packages.datepicker_modal.banner.description.manual_mobile")
else
I18n.t("work_packages.datepicker_modal.banner.description.automatic_mobile")
end
"#{text} #{render(Primer::Beta::Link.new(tag: :a, href: link, target: '_blank', underline: true)) do
I18n.t('work_packages.datepicker_modal.show_relations')
end}".html_safe
capture do
concat text
concat render(Primer::Beta::Link.new(tag: :a, href: link, target: "_blank", underline: true)) { I18n.t("work_packages.datepicker_modal.show_relations") }
end
end
def overlapping_predecessor?
@@ -122,7 +124,7 @@ module WorkPackages
predecessor_work_packages.filter_map(&:due_date)
.max
&.before?(@work_package.start_date - 2)
&.before?(@work_package.start_date - 2)
end
def predecessor_relations
@@ -131,8 +133,8 @@ module WorkPackages
def predecessor_work_packages
@predecessor_work_packages ||= predecessor_relations
.includes(:to)
.map(&:to)
.includes(:to)
.map(&:to)
end
def children
@@ -1,7 +1,9 @@
<%= render(
Primer::Alpha::Dialog.new(
title: I18n.t("export.dialog.title"),
size: :xlarge,
size: :large,
position: :right,
position_narrow: :fullscreen,
id: MODAL_ID,
data: {
controller: "work-packages--export--dialog"

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