diff --git a/.env.example b/.env.example index 1d40114a28a..22069e05a6c 100644 --- a/.env.example +++ b/.env.example @@ -48,10 +48,15 @@ FE_PORT=4200 OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL=ws://localhost:1234 OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET=secret12345 +# Hocuspocus (collaborative editing) - docker dev development +# OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL=wss://hocuspocus.local +# OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET=secret12345 + # Default TLD for docker dev stack (e.g. when set to "local", services will be openproject.local, nextcloud.local, etc.) OPENPROJECT_DOCKER_DEV_TLD=local # Use this variables to configure hostnames for frontend and backend, e.g. to enable HTTPS in docker development setup +# For docker development: openproject.local and localhost for "local" development OPENPROJECT_DEV_HOST=localhost OPENPROJECT_DEV_URL=http://${OPENPROJECT_DEV_HOST}:${FE_PORT} diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index b1920ad916c..dfce7ca123d 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -41,6 +41,7 @@ jobs: NobodysNightmare, RobinWagner, akabiru, + anna-mohn, as-op, astock2, ba1ash, @@ -53,10 +54,12 @@ jobs: corinnaguenther, crohr, dependabot[bot], + dfriquet, dombesz, dominic-braeunlein, dsteiner, dtohmucu, + fereshtehnm, gleone-art, ihor-khomenko, judithroth, @@ -80,8 +83,8 @@ jobs: vspielau, wielinde, yanzubrytskyi, - ehassan01 - + ehassan01, + thykel # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken remote-organization-name: opf remote-repository-name: legal diff --git a/.github/workflows/create-merge-release-into-dev-pr.yml b/.github/workflows/create-merge-release-into-dev-pr.yml index c2bd973bf78..853e71493be 100644 --- a/.github/workflows/create-merge-release-into-dev-pr.yml +++ b/.github/workflows/create-merge-release-into-dev-pr.yml @@ -57,8 +57,14 @@ jobs: - name: Resolve bug in git https://stackoverflow.com/a/63879454/96823 run: git repack -d - - name: Get one commit more to connect the history of dev and release branches - run: git fetch --deepen 1 origin "$BASE_BRANCH" "$RELEASE_BRANCH" + - name: Get more commits to connect the history of dev and release branches + run: | + for i in $(seq 1 10); do + git fetch --deepen 10 origin "$BASE_BRANCH" "$RELEASE_BRANCH" + git merge-base --all HEAD origin/"$RELEASE_BRANCH" && exit 0 + done + echo "Failed to fetch merge base" >&2 + exit 1 - name: Create branch without checkout just to have shorter automatic commit message run: git branch "$RELEASE_BRANCH" origin/"$RELEASE_BRANCH" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6fb968d0a98..10919e0228b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -113,7 +113,7 @@ jobs: ./docker/prod/setup/precompile-assets.sh # public/assets will be saved as artifact, so temporarily copying config file there as well cp config/frontend_assets.manifest.json public/assets/frontend_assets.manifest.json - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: path: public/ name: public-assets-${{ inputs.tag }}-${{ github.sha }} @@ -173,7 +173,7 @@ jobs: run: | cp ./docker/prod/Dockerfile ./Dockerfile - name: Download precompiled public assets - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: public-assets-${{ inputs.tag }}-${{ github.sha }} path: public/ @@ -192,18 +192,18 @@ jobs: echo "https://github.com/opf/openproject/commits/${{ inputs.branch }}" > PRODUCT_URL date -u +"%Y-%m-%dT%H:%M:%SZ" > RELEASE_DATE - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: context: git labels: | @@ -228,7 +228,7 @@ jobs: sed -i 's/vendor\/bundle//g' .dockerignore - name: Build image id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: ${{ matrix.platform }} @@ -273,7 +273,7 @@ jobs: --platform "${{ matrix.platform }}" - name: Push image id: push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . platforms: ${{ matrix.platform }} @@ -291,7 +291,7 @@ jobs: digest="${{ steps.push.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: digests-${{ inputs.tag }}-${{ matrix.target }}--${{ matrix.digest }} path: /tmp/digests/* @@ -307,13 +307,13 @@ jobs: - build steps: - name: Merge digests - uses: actions/upload-artifact/merge@v6 + uses: actions/upload-artifact/merge@v7 with: pattern: "digests-${{ inputs.tag }}-${{ matrix.target }}--*" overwrite: true name: "merged-digests-${{ inputs.tag }}-${{ matrix.target }}-${{ github.run_number }}-${{ github.run_attempt }}" - name: Download digests - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: "merged-digests-${{ inputs.tag }}-${{ matrix.target }}-${{ github.run_number }}-${{ github.run_attempt }}" path: /tmp/digests @@ -324,10 +324,10 @@ jobs: if [ "$suffix" = "-all-in-one" ]; then suffix="" ; fi echo "suffix=$suffix" >> "$GITHUB_OUTPUT" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ needs.setup.outputs.registry_image }} labels: | @@ -342,7 +342,7 @@ jobs: tags: | ${{ needs.setup.outputs.docker_tags }} - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/email-notification.yml b/.github/workflows/email-notification.yml index dda751a5c28..0f2d3cb0b9c 100644 --- a/.github/workflows/email-notification.yml +++ b/.github/workflows/email-notification.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Send mail - uses: dawidd6/action-send-mail@v7 + uses: dawidd6/action-send-mail@v11 with: subject: ${{ inputs.subject }} body: ${{ inputs.body }} diff --git a/.github/workflows/hocuspocus-docker.yml b/.github/workflows/hocuspocus-docker.yml index 680f1ae61c1..2ac162061b1 100644 --- a/.github/workflows/hocuspocus-docker.yml +++ b/.github/workflows/hocuspocus-docker.yml @@ -83,20 +83,20 @@ jobs: echo $REGISTRY:$LATEST_TAG >> $GITHUB_OUTPUT echo 'EOF' >> $GITHUB_OUTPUT - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Build and push id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: extensions/op-blocknote-hocuspocus push: true diff --git a/.github/workflows/hocuspocus-test.yml b/.github/workflows/hocuspocus-test.yml index 0839e51f825..17af080cb10 100644 --- a/.github/workflows/hocuspocus-test.yml +++ b/.github/workflows/hocuspocus-test.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/sync-internal-fork.yml b/.github/workflows/sync-internal-fork.yml index a3e21021e18..2d91bd10284 100644 --- a/.github/workflows/sync-internal-fork.yml +++ b/.github/workflows/sync-internal-fork.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.rubocop.yml b/.rubocop.yml index 47896294dfe..1815a2c11d1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -226,6 +226,10 @@ Rails/SkipsModelValidations: RSpecRails/HttpStatus: Enabled: false +# The block form of `travel_to` is often the tighter and safer option in our Rails specs. +RSpecRails/TravelAround: + Enabled: false + # expect not_to change is not working as expected # if you chain it with multiple expected changes RSpec/ChangeByZero: diff --git a/.ruby-version b/.ruby-version index 2aa51319921..1454f6ed4b7 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.4.7 +4.0.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b0484f3ae6f..cb9ad130e7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,6 +139,7 @@ version 1.0.0, available at ## Contributors license agreement -Contributors have to sign a CLA before contributing to OpenProject. -The [CLA can be found here](https://www.openproject.org/legal/contributor-license-agreement/) -and has to be filled out and sent to info@openproject.org. +If you want to contribute to OpenProject, please make sure to accept our Contributor License Agreement (CLA). The CLA documents the rights granted by contributors to OpenProject. +The [CLA can be found here](https://www.openproject.org/legal/contributor-license-agreement/). + +A GitHub action will enforce the CLA has been read and accepted by every new contributor. You will only be asked once, the document is short, and signing is easy. diff --git a/Gemfile b/Gemfile index a698eb91537..14b8a209569 100644 --- a/Gemfile +++ b/Gemfile @@ -50,7 +50,7 @@ gem "connection_pool", "~> 3.0.2" gem "rdoc", ">= 2.4.2" -gem "doorkeeper", "~> 5.8.0" +gem "doorkeeper", "~> 5.9.0" # Maintain our own omniauth due to relative URL root issues # see upstream PR: https://github.com/omniauth/omniauth/pull/903 gem "omniauth", git: "https://github.com/opf/omniauth", ref: "7eb21563ba047ef86d71f099975587b5ec88f9c9" @@ -69,16 +69,16 @@ gem "scimitar", "~> 2.13" gem "acts_as_list", "~> 1.2.6" gem "acts_as_tree", "~> 2.9.0" gem "awesome_nested_set", "~> 3.9.0" -gem "closure_tree", "~> 9.5.0" +gem "closure_tree", "~> 9.6.0" gem "rubytree", "~> 2.2.0" -gem "addressable", "~> 2.8.0" +gem "addressable", "~> 2.8.9" # Remove whitespace from model input gem "auto_strip_attributes", "~> 2.5" # Provide timezone info for TZInfo used by AR -gem "tzinfo-data", "~> 1.2025.1" +gem "tzinfo-data", "~> 1.2026.1" # to generate html-diffs (e.g. for wiki comparison) gem "htmldiff" @@ -127,7 +127,7 @@ gem "multi_json", "~> 1.19.0" gem "oj", "~> 3.16.12" gem "daemons" -gem "good_job", "~> 4.12.0" # update should be done manually in sync with saas-openproject version. +gem "good_job", "~> 4.13.3" # update should be done manually in sync with saas-openproject version. gem "rack-protection", "~> 3.2.0" @@ -161,7 +161,7 @@ gem "ttfunk", "~> 1.7.0" # remove after https://github.com/prawnpdf/prawn/issues # prawn implicitly depends on matrix gem no longer in ruby core with 3.1 gem "matrix", "~> 0.4.3" -gem "mcp", "~> 0.7.0" +gem "mcp", "~> 0.8.0" gem "meta-tags", "~> 2.22.3" @@ -199,8 +199,9 @@ gem "rack-timeout", "~> 0.7.0", require: "rack/timeout/base" gem "nokogiri", "~> 1.19.1" -gem "carrierwave", "~> 1.3.4" -gem "carrierwave_direct", "~> 2.1.0" +gem "carrierwave", "~> 2.2.6" +gem "carrierwave_direct", "~> 3.0.0" +gem "ssrf_filter", "~> 1.3" gem "fog-aws" gem "aws-sdk-core", "~> 3.241" @@ -239,7 +240,7 @@ gem "opentelemetry-exporter-otlp", "~> 0.31.0", require: false gem "opentelemetry-instrumentation-all", "~> 0.90.0", require: false gem "opentelemetry-sdk", "~> 1.10", require: false -gem "view_component", "~> 4.4.0" +gem "view_component", "~> 4.5.0" # Lookbook gem "lookbook", "2.3.14" @@ -255,7 +256,7 @@ 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.6.3" +gem "httpx", "~> 1.7.3" # Brings actual deep-freezing to most ruby objects gem "ice_nine" @@ -276,7 +277,7 @@ group :test do gem "rspec-rails", "~> 8.0.3", group: :development # Retry failures within the same environment - gem "retriable", "~> 3.1.1" + gem "retriable", "~> 3.2.1" gem "rspec-retry", "~> 0.6.1" # Accessibility tests @@ -388,7 +389,7 @@ end gem "bootsnap", "~> 1.23.0", require: false # API gems -gem "grape", "~> 2.4.0" +gem "grape", "~> 3.1.1" gem "grape_logging", "~> 3.0.0" gem "roar", "~> 1.2.0" diff --git a/Gemfile.lock b/Gemfile.lock index a5b0d3ce8bf..e86c2226f17 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -321,7 +321,7 @@ GEM activesupport (>= 6.1) acts_as_tree (2.9.1) activerecord (>= 3.0.0) - addressable (2.8.8) + addressable (2.8.9) public_suffix (>= 2.0.2, < 8.0) aes_key_wrap (1.1.0) afm (1.0.0) @@ -332,7 +332,7 @@ GEM android_key_attestation (0.3.0) anyway_config (2.8.0) ruby-next-core (~> 1.0) - appsignal (4.8.2) + appsignal (4.8.3) logger rack (>= 2.0.0) ast (2.4.3) @@ -342,7 +342,7 @@ GEM awesome_nested_set (3.9.0) activerecord (>= 4.0.0, < 8.2) aws-eventstream (1.4.0) - aws-partitions (1.1213.0) + aws-partitions (1.1221.0) aws-sdk-core (3.242.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -351,10 +351,10 @@ GEM bigdecimal jmespath (~> 1, >= 1.6.1) logger - aws-sdk-kms (1.121.0) + aws-sdk-kms (1.122.0) aws-sdk-core (~> 3, >= 3.241.4) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.213.0) + aws-sdk-s3 (1.214.0) aws-sdk-core (~> 3, >= 3.241.4) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -390,7 +390,7 @@ GEM bindata (2.5.1) bootsnap (1.23.0) msgpack (~> 1.2) - brakeman (8.0.2) + brakeman (8.0.4) racc browser (6.2.0) builder (3.3.0) @@ -405,23 +405,26 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - capybara-screenshot (1.0.26) + capybara-screenshot (1.0.27) capybara (>= 1.0, < 4) launchy - carrierwave (1.3.4) - activemodel (>= 4.0.0) - activesupport (>= 4.0.0) - mime-types (>= 1.16) - ssrf_filter (~> 1.0, < 1.1.0) - carrierwave_direct (2.1.0) - carrierwave (>= 1.0.0) + carrierwave (2.2.6) + activemodel (>= 5.0.0) + activesupport (>= 5.0.0) + addressable (~> 2.6) + image_processing (~> 1.1) + marcel (~> 1.0.0) + mini_mime (>= 0.1.3) + ssrf_filter (~> 1.0) + carrierwave_direct (3.0.0) + carrierwave (>= 2.2.0) fog-aws cbor (0.5.10.1) cgi (0.5.1) childprocess (5.1.0) logger (~> 1.5) climate_control (1.2.0) - closure_tree (9.5.0) + closure_tree (9.6.0) activerecord (>= 7.2.0) with_advisory_lock (>= 7.5.0) zeitwerk (~> 2.7) @@ -444,21 +447,21 @@ GEM cose (1.3.1) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) - counter_culture (3.12.1) + counter_culture (3.12.2) activerecord (>= 4.2) activesupport (>= 4.2) crack (1.0.1) bigdecimal rexml crass (1.0.6) - css_parser (1.21.1) + css_parser (2.0.0) addressable csv (3.3.5) cuprite (0.17) capybara (~> 3.0) ferrum (~> 0.17.0) daemons (1.4.1) - dalli (5.0.0) + dalli (5.0.2) logger date (3.5.1) date_validator (0.12.0) @@ -476,7 +479,7 @@ GEM disposable (0.6.3) declarative (>= 0.0.9, < 1.0.0) representable (>= 3.1.1, < 4) - doorkeeper (5.8.2) + doorkeeper (5.9.0) railties (>= 5) dotenv (3.2.0) dotenv-rails (3.2.0) @@ -503,13 +506,13 @@ GEM concurrent-ruby (~> 1.0) dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-schema (1.15.0) + dry-schema (1.16.0) concurrent-ruby (~> 1.0) dry-configurable (~> 1.0, >= 1.0.1) dry-core (~> 1.1) dry-initializer (~> 3.2) dry-logic (~> 1.6) - dry-types (~> 1.8) + dry-types (~> 1.9, >= 1.9.1) zeitwerk (~> 2.6) dry-types (1.9.1) bigdecimal (>= 3.0) @@ -555,7 +558,7 @@ GEM tzinfo eventmachine (1.2.7) eventmachine_httpserver (0.2.1) - excon (1.3.2) + excon (1.4.0) logger factory_bot (6.5.6) activesupport (>= 6.1.0) @@ -616,7 +619,7 @@ GEM glob (0.4.0) globalid (1.3.0) activesupport (>= 6.1) - good_job (4.12.1) + good_job (4.13.3) activejob (>= 6.1.0) activerecord (>= 6.1.0) concurrent-ruby (>= 1.3.1) @@ -637,30 +640,30 @@ GEM base64 (~> 0.2) faraday (>= 1.0, < 3.a) google-logging-utils (0.2.0) - google-protobuf (4.33.5) + google-protobuf (4.34.0) bigdecimal - rake (>= 13) - google-protobuf (4.33.5-aarch64-linux-gnu) + rake (~> 13.3) + google-protobuf (4.34.0-aarch64-linux-gnu) bigdecimal - rake (>= 13) - google-protobuf (4.33.5-aarch64-linux-musl) + rake (~> 13.3) + google-protobuf (4.34.0-aarch64-linux-musl) bigdecimal - rake (>= 13) - google-protobuf (4.33.5-arm64-darwin) + rake (~> 13.3) + google-protobuf (4.34.0-arm64-darwin) bigdecimal - rake (>= 13) - google-protobuf (4.33.5-x86_64-darwin) + rake (~> 13.3) + google-protobuf (4.34.0-x86_64-darwin) bigdecimal - rake (>= 13) - google-protobuf (4.33.5-x86_64-linux-gnu) + rake (~> 13.3) + google-protobuf (4.34.0-x86_64-linux-gnu) bigdecimal - rake (>= 13) - google-protobuf (4.33.5-x86_64-linux-musl) + rake (~> 13.3) + google-protobuf (4.34.0-x86_64-linux-musl) bigdecimal - rake (>= 13) + rake (~> 13.3) googleapis-common-protos-types (1.22.0) google-protobuf (~> 4.26) - googleauth (1.16.1) + googleauth (1.16.2) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.2) google-logging-utils (~> 0.1) @@ -668,8 +671,9 @@ GEM multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) - grape (2.4.0) - activesupport (>= 6.1) + grape (3.1.1) + activesupport (>= 7.1) + dry-configurable dry-types (>= 1.1) mustermann-grape (~> 1.1.0) rack (>= 2) @@ -691,10 +695,10 @@ GEM htmlbeautifier (1.4.3) htmldiff (0.0.1) htmlentities (4.3.4) - http-2 (1.1.2) + http-2 (1.1.3) http_parser.rb (0.8.1) - httpx (1.6.3) - http-2 (>= 1.0.0) + httpx (1.7.3) + http-2 (>= 1.1.3) i18n (1.14.8) concurrent-ruby (~> 1.0) i18n-js (4.2.4) @@ -719,6 +723,9 @@ GEM ostruct ice_cube (0.17.0) ice_nine (0.11.2) + image_processing (1.14.0) + mini_magick (>= 4.9.5, < 6) + ruby-vips (>= 2.0.17, < 3) inline_svg (1.10.0) activesupport (>= 3.0) nokogiri (>= 1.6) @@ -760,7 +767,7 @@ GEM addressable (~> 2.8) childprocess (~> 5.0) logger (~> 1.6) - lefthook (2.1.1) + lefthook (2.1.2) letter_opener (1.10.0) launchy (>= 2.2, < 4) letter_opener_web (3.0.0) @@ -804,10 +811,10 @@ GEM net-imap net-pop net-smtp - marcel (1.1.0) + marcel (1.0.4) markly (0.15.2) matrix (0.4.3) - mcp (0.7.1) + mcp (0.8.0) json-schema (>= 4.1) messagebird-rest (5.0.0) jwt (< 4) @@ -817,7 +824,7 @@ GEM mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) - mime-types-data (3.2026.0203) + mime-types-data (3.2026.0303) mini_magick (5.3.1) logger mini_mime (1.1.5) @@ -895,7 +902,7 @@ GEM view_component (>= 3.1, < 5.0) openproject-token (8.8.0) activemodel - openssl (4.0.0) + openssl (4.0.1) openssl-signature_algorithm (1.3.0) openssl (> 2.0) opentelemetry-api (1.7.0) @@ -984,7 +991,7 @@ GEM opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-concurrent_ruby (0.24.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-dalli (0.29.0) + opentelemetry-instrumentation-dalli (0.29.2) opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-delayed_job (0.25.1) opentelemetry-instrumentation-base (~> 0.25) @@ -994,7 +1001,7 @@ GEM opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-faraday (0.31.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-grape (0.5.0) + opentelemetry-instrumentation-grape (0.5.1) opentelemetry-instrumentation-rack (~> 0.29) opentelemetry-instrumentation-graphql (0.31.2) opentelemetry-instrumentation-base (~> 0.25) @@ -1006,7 +1013,7 @@ GEM opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-http_client (0.27.0) opentelemetry-instrumentation-base (~> 0.25) - opentelemetry-instrumentation-httpx (0.6.0) + opentelemetry-instrumentation-httpx (0.6.1) opentelemetry-instrumentation-base (~> 0.25) opentelemetry-instrumentation-koala (0.23.0) opentelemetry-instrumentation-base (~> 0.25) @@ -1076,8 +1083,9 @@ GEM ostruct (0.6.3) ox (2.14.23) bigdecimal (>= 3.0) - pagy (43.2.9) + pagy (43.3.1) json + uri yaml paper_trail (17.0.0) activerecord (>= 7.1) @@ -1085,7 +1093,7 @@ GEM parallel (1.27.0) parallel_tests (4.10.1) parallel - parser (3.3.10.1) + parser (3.3.10.2) ast (~> 2.4.1) racc pdf-core (0.9.0) @@ -1167,7 +1175,7 @@ GEM psych (5.3.1) date stringio - public_suffix (7.0.2) + public_suffix (7.0.5) puffing-billy (4.0.3) addressable (~> 2.5) cgi @@ -1283,7 +1291,7 @@ GEM responders (3.2.0) actionpack (>= 7.0) railties (>= 7.0) - retriable (3.1.2) + retriable (3.2.1) rexml (3.4.4) rinku (2.0.6) roar (1.2.0) @@ -1299,7 +1307,7 @@ GEM rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.7) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-rails (8.0.3) @@ -1315,10 +1323,11 @@ GEM rspec-support (3.13.7) rspec-wait (1.0.2) rspec (>= 3.4) - rubocop (1.84.2) + rubocop (1.85.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) + mcp (~> 0.6) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) @@ -1360,13 +1369,17 @@ GEM iso8601 ruby-next-core (1.2.0) ruby-ole (1.2.13.1) - ruby-prof (1.7.2) + ruby-prof (2.0.4) base64 + ostruct ruby-progressbar (1.13.0) ruby-rc4 (0.1.5) ruby-saml (1.18.1) nokogiri (>= 1.13.10) rexml + ruby-vips (2.3.0) + ffi (~> 1.12) + logger ruby2_keywords (0.0.5) rubytree (2.2.0) json (~> 2.0, > 2.9) @@ -1381,7 +1394,7 @@ GEM securerandom (0.4.1) selenium-devtools (0.143.0) selenium-webdriver (~> 4.2) - selenium-webdriver (4.40.0) + selenium-webdriver (4.41.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -1415,7 +1428,7 @@ GEM actionpack (>= 6.1) activesupport (>= 6.1) sprockets (>= 3.0.0) - ssrf_filter (1.0.8) + ssrf_filter (1.3.0) stackprof (0.2.28) statesman (13.1.0) store_attribute (2.1.1) @@ -1454,7 +1467,7 @@ GEM turbo-rails (>= 1.3.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - tzinfo-data (1.2025.3) + tzinfo-data (1.2026.1) tzinfo (>= 1.0.0) uber (0.1.0) unicode-display_width (3.2.0) @@ -1469,8 +1482,8 @@ GEM activemodel (>= 3.0.0) public_suffix vcr (6.4.0) - vernier (1.9.0) - view_component (4.4.0) + vernier (1.10.0) + view_component (4.5.0) actionview (>= 7.1.0) activesupport (>= 7.1.0) concurrent-ruby (~> 1) @@ -1510,7 +1523,7 @@ GEM zeitwerk (>= 2.7) xpath (3.2.0) nokogiri (~> 1.8) - yabeda (0.14.0) + yabeda (0.15.0) anyway_config (>= 1.0, < 3) concurrent-ruby dry-initializer @@ -1555,7 +1568,7 @@ DEPENDENCIES activerecord-session_store (~> 2.2.0) acts_as_list (~> 1.2.6) acts_as_tree (~> 2.9.0) - addressable (~> 2.8.0) + addressable (~> 2.8.9) airbrake (~> 13.0.0) appsignal (~> 4.7) auto_strip_attributes (~> 2.5) @@ -1572,10 +1585,10 @@ DEPENDENCIES capybara (~> 3.40.0) capybara-screenshot (~> 1.0.17) capybara_accessible_selectors! - carrierwave (~> 1.3.4) - carrierwave_direct (~> 2.1.0) + carrierwave (~> 2.2.6) + carrierwave_direct (~> 3.0.0) climate_control - closure_tree (~> 9.5.0) + closure_tree (~> 9.6.0) colored2 commonmarker (~> 2.6.0) compare-xml (~> 0.66) @@ -1590,7 +1603,7 @@ DEPENDENCIES deckar01-task_list (~> 2.3.1) dentaku (~> 3.5) disposable (~> 0.6.2) - doorkeeper (~> 5.8.0) + doorkeeper (~> 5.9.0) dotenv-rails dry-container dry-monads @@ -1608,15 +1621,15 @@ DEPENDENCIES friendly_id (~> 5.6.0) fuubar (~> 2.5.0) globalid (~> 1.3) - good_job (~> 4.12.0) + good_job (~> 4.13.3) google-apis-gmail_v1 googleauth - grape (~> 2.4.0) + grape (~> 3.1.1) grape_logging (~> 3.0.0) grids! html-pipeline (~> 2.14.0) htmldiff - httpx (~> 1.6.3) + httpx (~> 1.7.3) i18n-js (~> 4.2.4) i18n-tasks (~> 1.1.0) ice_cube (~> 0.17.0) @@ -1635,7 +1648,7 @@ DEPENDENCIES mail (= 2.9.0) markly (~> 0.15) matrix (~> 0.4.3) - mcp (~> 0.7.0) + mcp (~> 0.8.0) md_to_pdf! meta-tags (~> 2.22.3) mini_magick (~> 5.3.0) @@ -1708,7 +1721,7 @@ DEPENDENCIES redis (~> 5.4.0) request_store (~> 1.7.0) responders (~> 3.2) - retriable (~> 3.1.1) + retriable (~> 3.2.1) rinku (~> 2.0.4) roar (~> 1.2.0) rouge (~> 4.7.0) @@ -1740,6 +1753,7 @@ DEPENDENCIES spring-commands-rubocop sprockets (~> 3.7.2) sprockets-rails (~> 3.5.1) + ssrf_filter (~> 1.3) stackprof statesman (~> 13.1.0) store_attribute (~> 2.0) @@ -1754,11 +1768,11 @@ DEPENDENCIES turbo-rails (~> 2.0.20) turbo_power (~> 0.7.0) turbo_tests! - tzinfo-data (~> 1.2025.1) + tzinfo-data (~> 1.2026.1) validate_url vcr vernier - view_component (~> 4.4.0) + view_component (~> 4.5.0) warden (~> 1.2) warden-basic_auth (~> 0.2.1) webmock (~> 3.26) @@ -1791,23 +1805,23 @@ CHECKSUMS activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae acts_as_list (1.2.6) sha256=8345380900b7bee620c07ad00991ccee59af3d8c9e8574f426e321da2865fdc8 acts_as_tree (2.9.1) sha256=b869eb10a8de38616b64ffcf9e882d3d99c8e06909c4057078a76c3b89a9a2f3 - addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057 + addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485 aes_key_wrap (1.1.0) sha256=b935f4756b37375895db45669e79dfcdc0f7901e12d4e08974d5540c8e0776a5 afm (1.0.0) sha256=5bd4d6f6241e7014ef090985ec6f4c3e9745f6de0828ddd58bc1efdd138f4545 airbrake (13.0.5) sha256=901f5074c25d5ef77ed87f5bde7a28400a7324f5d7013a8a12d07e0099cc31b6 airbrake-ruby (6.2.2) sha256=293e34fb36e763e1b6d67ab584cce7c5b6fe9eea1a70c26d8c13c0f5d7de2fbc android_key_attestation (0.3.0) sha256=467eb01a99d2bb48ef9cf24cc13712669d7056cba5a52d009554ff037560570b anyway_config (2.8.0) sha256=f6797a7231f81202dcd3d0c07284e836e45713e761d320180348b13a5c7c9306 - appsignal (4.8.2) sha256=88cf3229a451a6501da55e221367991788f5621d319823b471f271e07cc92ca5 + appsignal (4.8.3) sha256=aa0ea5ffd39fe7530c56a6eb6efda60825ab061ef31376126cae93b009844dd7 ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 attr_required (1.0.2) sha256=f0ebfc56b35e874f4d0ae799066dbc1f81efefe2364ca3803dc9ea6a4de6cb99 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.1213.0) sha256=5ec132d91d44ef2702125b8f71f0e4fc2cd7de040e02c5d0aefb87219fd2e05e + aws-partitions (1.1221.0) sha256=f09304480191f5ff03f8994705067779bc8fe5b4731183ce45f092afb706e8eb aws-sdk-core (3.242.0) sha256=c17b3003acc78d80c1a8437b285a1cfc5e4d7749ce7821cf3071e847535a29a0 - aws-sdk-kms (1.121.0) sha256=d563c1cfb4b5754efbc671216c8eca875338748adad0f42518c28dfa0a2d01e0 - aws-sdk-s3 (1.213.0) sha256=af596ccf544582406db610e95cc9099276eaf03142f57a2f30f76940e598e50d + aws-sdk-kms (1.122.0) sha256=47ce3f51b26bd7d76f1270cfdfca17b40073ecd3219c8c9400788712abfb4eb8 + aws-sdk-s3 (1.214.0) sha256=923135327634c873ecedbd8f396ea9939874d524b14fa65eced766660c8dd62e aws-sdk-sns (1.112.0) sha256=aff1b1b5bbcb4229599221c558a41790c1cd1a1fed47ac3d27d27512ad24b254 aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 axe-core-api (4.11.1) sha256=a6460506449a692030620a0574fee7afa6cd38cfbbf6620d20bf4d53d33a80cc @@ -1820,21 +1834,21 @@ CHECKSUMS bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218 bindata (2.5.1) sha256=53186a1ec2da943d4cb413583d680644eb810aacbf8902497aac8f191fad9e58 bootsnap (1.23.0) sha256=c1254f458d58558b58be0f8eb8f6eec2821456785b7cdd1e16248e2020d3f214 - brakeman (8.0.2) sha256=7b02065ce8b1de93949cefd3f2ad78e8eb370e644b95c8556a32a912a782426a + brakeman (8.0.4) sha256=7bf921fa9638544835df9aa7b3e720a9a72c0267f34f92135955edd80d4dcf6f browser (6.2.0) sha256=281d5295788825c9396427c292c2d2be0a5c91875c93c390fde6e5d61a5ace2d budgets (1.0.0) builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f byebug (13.0.0) sha256=d2263efe751941ca520fa29744b71972d39cbc41839496706f5d9b22e92ae05d capybara (3.40.0) sha256=42dba720578ea1ca65fd7a41d163dd368502c191804558f6e0f71b391054aeef - capybara-screenshot (1.0.26) sha256=816b9370a07752097c82a05f568aaf5d3b7f45c3db5d3aab2014071e1b3c0c77 + capybara-screenshot (1.0.27) sha256=afa1896cc23df77be1774e8d3b3ce3953bf060aeaa04ff87607b5daf689174f2 capybara_accessible_selectors (0.15.0) - carrierwave (1.3.4) sha256=81772dabd1830edbd7f4526d2ae2c79f974f1d48900c3f03f7ecb7c657463a21 - carrierwave_direct (2.1.0) sha256=b0d5c19c1d17a05940e488cff644a3b2946422d6d494b2174b48f6a90c5dddbf + carrierwave (2.2.6) sha256=cd9b6108fc7544e97e7fbcc561bd319a09f23c96816fdd0df8f2f45ffdc0dac3 + carrierwave_direct (3.0.0) sha256=da4105ec7beea2687817b95ad95181be3d657248ec1cb5a0e5c35a45b176fc2f cbor (0.5.10.1) sha256=79cdf79f18dcd9ee97e0b849c6d573e5a2e3ddc1954d180f384d6ed2612b6df0 cgi (0.5.1) sha256=e93fcafc69b8a934fe1e6146121fa35430efa8b4a4047c4893764067036f18e9 childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec climate_control (1.2.0) sha256=36b21896193fa8c8536fa1cd843a07cf8ddbd03aaba43665e26c53ec1bd70aa5 - closure_tree (9.5.0) sha256=1ae7d32f1a03ac13dee152bd1185a0f509c26dca7f2e5a234867576de5a96b5b + closure_tree (9.6.0) sha256=75aac8f058597b0d96f2052f1419c912e8f4e92425e56273db122913cdae5d1e coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b coercible (1.0.0) sha256=5081ad24352cc8435ce5472bc2faa30260c7ea7f2102cc6a9f167c4d9bffaadc color_conversion (0.1.2) sha256=99bea5fa412e1527a11389975aa6ad445ff8528ebae202c11d08c45ea2b94c96 @@ -1851,14 +1865,14 @@ CHECKSUMS cookiejar (0.3.4) sha256=11b16acfc4baf7a0f463c21a6212005e04e25f5554d4d9f24d97f3492dfda0df cose (1.3.1) sha256=d5d4dbcd6b035d513edc4e1ab9bc10e9ce13b4011c96e3d1b8fe5e6413fd6de5 costs (1.0.0) - counter_culture (3.12.1) sha256=8a880092321941732bfa639c449a133ff08c5197c29e853371cc03c486e43980 + counter_culture (3.12.2) sha256=26358dbb15e2f1f7e60f2c11976e37517a74ffd34eb5ef8d7269862b9ff8aadd crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d - css_parser (1.21.1) sha256=6cfd3ffc0a97333b39d2b1b49c95397b05e0e3b684d68f77ec471ba4ec2ef7c7 + css_parser (2.0.0) sha256=af5c759a127b125b635006a6c6c2e05b96a1ebdeec21b3c415fd5f09ec714a0a csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f cuprite (0.17) sha256=b140d5dc70d08b97ad54bcf45cd95d0bd430e291e9dffe76fff851fddd57c12b daemons (1.4.1) sha256=8fc76d76faec669feb5e455d72f35bd4c46dc6735e28c420afb822fac1fa9a1d - dalli (5.0.0) sha256=7d5ae821da3ef98cad68e77f09ae0e563244dc0f3995f5c8999c5a379fa3d468 + dalli (5.0.2) sha256=818469227b9acdd9da3fc65ec5ae75d4020115545879e5e8f95634085e9a8749 date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 date_validator (0.12.0) sha256=68c9834da240347b9c17441c553a183572508617ebfbe8c020020f3192ce3058 deckar01-task_list (2.3.4) sha256=66abdc7e009ea759732bb53867e1ea42de550e2aa03ac30a015cbf42a04c1667 @@ -1867,7 +1881,7 @@ CHECKSUMS descendants_tracker (0.0.4) sha256=e9c41dd4cfbb85829a9301ea7e7c48c2a03b26f09319db230e6479ccdc780897 diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 disposable (0.6.3) sha256=7f2a3fb251bff6cd83f25b164043d4ec3531209b51b066ed476a9df9c2d384cc - doorkeeper (5.8.2) sha256=a73d07aeaf590b1e7e2a35390446f23131c9f37bc0561653e514d3973f4d50d3 + doorkeeper (5.9.0) sha256=edad053b3dcb6bf51e3181fc423333e7fba28863beb2122379643ce6463b6336 dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d dotenv-rails (3.2.0) sha256=657e25554ba622ffc95d8c4f1670286510f47f2edda9f68293c3f661b303beab drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 @@ -1878,7 +1892,7 @@ CHECKSUMS dry-initializer (3.2.0) sha256=37d59798f912dc0a1efe14a4db4a9306989007b302dcd5f25d0a2a20c166c4e3 dry-logic (1.6.0) sha256=da6fedbc0f90fc41f9b0cc7e6f05f5d529d1efaef6c8dcc8e0733f685745cea2 dry-monads (1.9.0) sha256=9348a67b5c862c7a876342dbd94737fdf3fb3c17978382cf6801a85b27215816 - dry-schema (1.15.0) sha256=0f2a34adba4206bd6d46ec1b6b7691b402e198eecaff1d8349a7d48a77d82cd2 + dry-schema (1.16.0) sha256=cd3aaeabc0f1af66ec82a29096d4c4fb92a0a58b9dae29a22b1bbceb78985727 dry-types (1.9.1) sha256=baebeecdb9f8395d6c9d227b62011279440943e3ef2468fe8ccc1ba11467f178 dry-validation (1.11.1) sha256=70900bb5a2d911c8aab566d3e360c6bff389b8bf92ea8e04885ce51c41ff8085 dumb_delegator (1.1.0) sha256=1ad255e5b095a2206a574c62b40c678f3d5c9151f1b3d0bae1b0463f7e40188e @@ -1895,7 +1909,7 @@ CHECKSUMS et-orbi (1.4.0) sha256=6c7e3c90779821f9e3b324c5e96fda9767f72995d6ae435b96678a4f3e2de8bc eventmachine (1.2.7) sha256=994016e42aa041477ba9cff45cbe50de2047f25dd418eba003e84f0d16560972 eventmachine_httpserver (0.2.1) sha256=5db5e8a23754204d43592e5fcc2160457c57c870babe6307c4e61fc95019b809 - excon (1.3.2) sha256=a089babe98638e58042a7d542b2bbd183304527e33d612b6dde22fa491a544a5 + excon (1.4.0) sha256=5d2bc9d2c79511a562e7fcac77cc7a40acd9cebcc55b80e537975ad8187f2924 factory_bot (6.5.6) sha256=12beb373214dccc086a7a63763d6718c49769d5606f0501e0a4442676917e077 factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68 faraday (2.14.1) sha256=a43cceedc1e39d188f4d2cdd360a8aaa6a11da0c407052e426ba8d3fb42ef61c @@ -1923,21 +1937,21 @@ CHECKSUMS fuubar (2.5.1) sha256=b272a7804b282661c7fab583a3764f92543cb482c365ae39c685cd218fdd4880 glob (0.4.0) sha256=893dc9e2d24abe13dda907ce0cda576f680ff382f2a6cf9e543f98ecbe29238c globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11 - good_job (4.12.1) sha256=a8a981baf2ac9c40a85412d92ce2bf7a5e6d35d9dadb0900db57780583eafebb + good_job (4.13.3) sha256=37478710dddd2630ed055f2159111ce6f7f14482d8ccb412142ee04674151e2e google-apis-core (1.0.2) sha256=ba4579aaadc902d6cc7bc8db88f566ab00f5e31ea87ab41e9f9a032c470f2629 google-apis-gmail_v1 (0.47.0) sha256=3064434b6da55b85e2828ce4bb0f4d04e8cfd187a4ab262ceb1dcb01f98e49ef google-cloud-env (2.3.1) sha256=0faac01eb27be78c2591d64433663b1a114f8f7af55a4f819755426cac9178e7 google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b - google-protobuf (4.33.5) sha256=1b64fb774c101b23ac3f6923eca24be04fd971635d235c4cd4cfe0d752620da0 - google-protobuf (4.33.5-aarch64-linux-gnu) sha256=f70ca066e37a7ac60b4f34a836bb48ca3fc41a9371310052e484d8c9f925ff39 - google-protobuf (4.33.5-aarch64-linux-musl) sha256=d9ae90025f05db642e5603de5dbb2390cd1215bac7507fa575cc20b0db7e11a1 - google-protobuf (4.33.5-arm64-darwin) sha256=996d4e93c4232cc42f0facd821a92b4f4a926c3c9c1a768e7d768b33d9ef72f9 - google-protobuf (4.33.5-x86_64-darwin) sha256=173d1d6c9f0de93fd9ee25fde172d6fb6376099dca8844e19bc5782bbc7b93b0 - google-protobuf (4.33.5-x86_64-linux-gnu) sha256=a782adf86bfba207740b49d7bb9ccdc25c4fb8f800fe222af62bce951149338a - google-protobuf (4.33.5-x86_64-linux-musl) sha256=d14feec9118f44cfdc3ee4a1d1baa4e6dd77fa418967ccf22ecbe76b8c1bacbf + google-protobuf (4.34.0) sha256=bffaea30fbe2807c80667a78953b15645b3bef62b25c10ca187e4418119be531 + google-protobuf (4.34.0-aarch64-linux-gnu) sha256=0ab8a8a97976a2265d647e69b3ff1980c89184abdaf06d36091856c5ab37cc55 + google-protobuf (4.34.0-aarch64-linux-musl) sha256=0632a86df6d320eac3b335bd779499d43ad8ee6d1f8c8494b773ed5d3d5c6ab4 + google-protobuf (4.34.0-arm64-darwin) sha256=f83967a8095a9da676b79ba372c58fef2ca3878428bd40febfce65b3752c90d1 + google-protobuf (4.34.0-x86_64-darwin) sha256=4a5b67281993345adca54bb32947f25a289597eafaa240e5b714d0a740f99321 + google-protobuf (4.34.0-x86_64-linux-gnu) sha256=bbb333fbe79c16f35a2e2154cf29f3ce26f60390dba286b339861206d5435ef9 + google-protobuf (4.34.0-x86_64-linux-musl) sha256=0b75858a388b17e73aa4176df2e722762dbc92551b7075fdc562d33c1c6de0b0 googleapis-common-protos-types (1.22.0) sha256=f97492b77bd6da0018c860d5004f512fe7cd165554d7019a8f4df6a56fbfc4c7 - googleauth (1.16.1) sha256=36776bce9d55d8c1a0c6638c939b000dcee5954ca5b728f06ec4c2df4a46709c - grape (2.4.0) sha256=3d59673e80f11d49ba86270b78344e5348dc057b318c2bbc1c01f3532f9b6aec + googleauth (1.16.2) sha256=15009502e2e38af71948cda918f230e27d327f6882a1e47967a5a4664930a638 + grape (3.1.1) sha256=774f16782d917a90e69de0499dfaab571e5ad967569ac066a2b0b918af12de69 grape_logging (3.0.0) sha256=7b62d984ce96df15d120508668debe307e6a59ac1c511f1d9b5f3b4bea793e13 gravatar_image_tag (1.2.0) sha256=eb5630fea846b711e713b934a0178fb9785f02f4eb9ced8d6faa4d537c40fdcf grids (1.0.0) @@ -1950,15 +1964,16 @@ CHECKSUMS htmlbeautifier (1.4.3) sha256=b43d08f7e2aa6ae1b5a6f0607b4ed8954c8d4a8e85fd2336f975dda1e4db385b htmldiff (0.0.1) sha256=a96d068c8b8ea96cca3154a61785365d83129f47f1df964c6b1666924ce113a1 htmlentities (4.3.4) sha256=125a73c6c9f2d1b62100b7c3c401e3624441b663762afa7fe428476435a673da - http-2 (1.1.2) sha256=8c1cb40203fc60b521c491509fee96c7b08092975eb30036465992433715ed40 + http-2 (1.1.3) sha256=1b2f379d35a11dbae94f8a1a52c053d8c161eb4a0c98b5d1605ff1b2bf171c9c http_parser.rb (0.8.1) sha256=9ae8df145b39aa5398b2f90090d651c67bd8e2ebfe4507c966579f641e11097a - httpx (1.6.3) sha256=1b4a11b9572b78839f649ad6ebbe09b5f467cc458e0ee408bddc902b273a2e8a + httpx (1.7.3) sha256=126914109a58350e5ad0c13786092f35e2419857dad15745f31fc81c371f93a8 i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 i18n-js (4.2.4) sha256=61390d372f8fa68c495c5907d577657e8cc3a7031f4945db1e91f935e1391355 i18n-tasks (1.1.2) sha256=4dcfba49e52a623f30661cb316cb80d84fbba5cb8c6d88ef5e02545fffa3637a icalendar (2.12.1) sha256=ecff56c550aed551f29ad1faad0da54bf62362dfaf22a428bd7ad782938fe764 ice_cube (0.17.0) sha256=32deb45dda4b4acc53505c2f581f6d32b5afc04d29b9004769944a0df5a5fcbe ice_nine (0.11.2) sha256=5d506a7d2723d5592dc121b9928e4931742730131f22a1a37649df1c1e2e63db + image_processing (1.14.0) sha256=754cc169c9c262980889bec6bfd325ed1dafad34f85242b5a07b60af004742fb inline_svg (1.10.0) sha256=5b652934236fd9f8adc61f3fd6e208b7ca3282698b19f28659971da84bf9a10f interception (0.5) sha256=a53818d636752a8df90d8c1bb2f7b6e13a7b828543cb02b50fbde98b849d7907 io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc @@ -1975,7 +1990,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.1) sha256=1b4ce49fbadb3f6584c07daaa9164e560e27bb5aba18446bb9de2378fcf3f5b6 + lefthook (2.1.2) sha256=da5a2484d68ee00d7fcc7e072e925c0246363eb20d77499d536bea209ae6ae06 letter_opener (1.10.0) sha256=2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2 letter_opener_web (3.0.0) sha256=3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860 lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 @@ -1986,16 +2001,16 @@ CHECKSUMS loofah (2.25.0) sha256=df5ed7ac3bac6a4ec802df3877ee5cc86d027299f8952e6243b3dac446b060e6 lookbook (2.3.14) sha256=c11a693bde9915b553c4463440ad5e750829f90bff08abdb6b8610373864cd7c mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941 - marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee + marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4 markly (0.15.2) sha256=65dae965d4dd4ecd997fba43b93acc0fe7dadfec6f07a748640c7a9299a8551e matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b - mcp (0.7.1) sha256=fa967895d6952bad0d981ea907731d8528d2c246d2079d56a9c8bae83d14f1c7 + 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 method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5 mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56 - mime-types-data (3.2026.0203) sha256=54353d693af028847391c28361c07d4b8b689cad78c3e1cc272fb1205c6d2a2f + mime-types-data (3.2026.0303) sha256=164af1de5824c5195d4b503b0a62062383b65c08671c792412450cd22d3bc224 mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4 mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d @@ -2054,7 +2069,7 @@ CHECKSUMS openproject-two_factor_authentication (1.0.0) openproject-webhooks (1.0.0) openproject-xls_export (1.0.0) - openssl (4.0.0) sha256=185711ed93d4e9c9a9db6efea7edb202dfe04f7d3692fbab988e3d84e498ee91 + openssl (4.0.1) sha256=e27974136b7b02894a1bce46c5397ee889afafe704a839446b54dc81cb9c5f7d openssl-signature_algorithm (1.3.0) sha256=a3b40b5e8276162d4a6e50c7c97cdaf1446f9b2c3946a6fa2c14628e0c957e80 opentelemetry-api (1.7.0) sha256=ccfd264ea6f2db5bf4185e3c07a1297977b44a944e2ce65457c4fe63a697214f opentelemetry-common (0.23.0) sha256=da721190479d57bae0ad2207468f47f3e2c3b9a91024b5bc32c9d280183eb32c @@ -2077,18 +2092,18 @@ CHECKSUMS opentelemetry-instrumentation-base (0.25.0) sha256=642a3a7f08354e6e969423327a4fa67ed2cca7ac6fe5ee09e55b17d1c576da27 opentelemetry-instrumentation-bunny (0.24.0) sha256=1ec484e48a5f42a1d0c33e8e6bc7e9e78dd80f3ed9d63520b8a22ba564aa2585 opentelemetry-instrumentation-concurrent_ruby (0.24.0) sha256=229bd8b72000c59de693609bb637b8a9114992f5e0ab03730d7fd7ef91f7d1d2 - opentelemetry-instrumentation-dalli (0.29.0) sha256=a2686650545609e8d7e281c9fd1aef529ab578ef2dcf9a6258737e4ba214bc2f + 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.0) sha256=b9fcbe13b015b663577b8bde5b419c297da2588d0a022f4ce40f9ffc49df7624 + opentelemetry-instrumentation-grape (0.5.1) sha256=a623608ef10e96c413f4d50b840082bf1ab9700126185d89ddbc8a29b49ec0ef 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.0) sha256=a4ee129cb7d5ea690565585b6a5c7a1d181a1e239c5360ec4554c15ce2b3fd6d + opentelemetry-instrumentation-httpx (0.6.1) sha256=9050801826bb0f148f603b39b119c4f49591955ee53d3d0b2a8fb1eb8f558022 opentelemetry-instrumentation-koala (0.23.0) sha256=8f324b50a2a64fd4994bb2b105a4cb0c80b64ec05cf5487d2daa906c650bc6f9 opentelemetry-instrumentation-lmdb (0.25.0) sha256=1e4d66d583ea242d4f72051062971f5af1ea353484d224abbd0aabdd1ce5f5cb opentelemetry-instrumentation-mongo (0.25.0) sha256=d04585669f928ea82e7c469f996061d39d8ff184278d57cf4fc77a6d607f9c7a @@ -2116,11 +2131,11 @@ CHECKSUMS ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 overviews (1.0.0) ox (2.14.23) sha256=4a9aedb4d6c78c5ebac1d7287dc7cc6808e14a8831d7adb727438f6a1b461b66 - pagy (43.2.9) sha256=6a167c8da72388883e8f4c8e29dc8f5577f9f934d8d0b704cb2f39d2cebd276e + pagy (43.3.1) sha256=78e6c3e5125463b817cbe48eb5ed7b552af062c7db90bef4810d1f442ec61744 paper_trail (17.0.0) sha256=1c2842061d3874ca7015908e821e2aa14f9b982af2acb2a7974713bf79021c85 parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70 - parser (3.3.10.1) sha256=06f6a725d2cd91e5e7f2b7c32ba143631e1f7c8ae2fb918fc4cebec187e6a688 + parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357 pdf-core (0.9.0) sha256=4f368b2f12b57ec979872d4bf4bd1a67e8648e0c81ab89801431d2fc89f4e0bb pdf-inspector (1.3.0) sha256=fc107579d6f29b636e2da3d6743479b2624d9e390bf2d84beef8fd4ebe1a05bd pdf-reader (2.15.1) sha256=18c6a986a84a3117fa49f4279fc2de51f5d2399b71833df5d2bccd595c7068ce @@ -2149,7 +2164,7 @@ CHECKSUMS pry-rails (0.3.11) sha256=a69e28e24a34d75d1f60bcf241192a54253f8f7ef8a62cba1e75750a9653593d pry-rescue (1.6.0) sha256=985bfd506d9866b587fd86790cf8445266a41b7f92c627fc5b21ec7d92aba6db psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974 - public_suffix (7.0.2) sha256=9114090c8e4e7135c1fd0e7acfea33afaab38101884320c65aaa0ffb8e26a857 + public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 puffing-billy (4.0.3) sha256=376fe2e2cc3ff9d48814a15153db80970cf0539ba026ac5108c971c2e160883c puma (7.2.0) sha256=bf8ef4ab514a4e6d4554cb4326b2004eba5036ae05cf765cfe51aba9706a72a8 puma-plugin-statsd (2.7.0) sha256=04f243a7233f4d06ec0e26f1a3522bce18a5910ae711763fabff22681bdad08b @@ -2190,7 +2205,7 @@ CHECKSUMS representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace request_store (1.7.0) sha256=e1b75d5346a315f452242a68c937ef8e48b215b9453a77a6c0acdca2934c88cb responders (3.2.0) sha256=89c2d6ac0ae16f6458a11524cae4a8efdceba1a3baea164d28ee9046bd3df55a - retriable (3.1.2) sha256=0a5a5d0ca4ba61a76fb31a17ab8f7f80281beb040c329d34dfc137a1398688e0 + retriable (3.2.1) sha256=26e87a33391fae4c382d4750f1e135e4dda7e5aa32b6b71f1992265981f9b991 rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 rinku (2.0.6) sha256=8b60670e3143f3db2b37efa262971ce3619ec23092045498ef9f077d82828d7d roar (1.2.0) sha256=8db4d1ca79c57a5fb746c16c0d5661d7c3e0de3d9553dc016a88d2dba2929d08 @@ -2199,12 +2214,12 @@ CHECKSUMS rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587 rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 - rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c + rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 rspec-rails (8.0.3) sha256=b0a440e7a10700317d898a014852e26660867298c4076dbc3baa99c768b79dc1 rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858 rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c rspec-wait (1.0.2) sha256=865f921239325d3d26fc10ded4bdd485d8b58bcaaad1a28dd85ed15266b5a912 - rubocop (1.84.2) sha256=5692cea54168f3dc8cb79a6fe95c5424b7ea893c707ad7a4307b0585e88dbf5f + rubocop (1.85.1) sha256=3dbcf9e961baa4c376eeeb2a03913dca5e3987033b04d38fa538aa1e7406cc77 rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c rubocop-factory_bot (2.28.0) sha256=4b17fc02124444173317e131759d195b0d762844a71a29fe8139c1105d92f0cb @@ -2216,10 +2231,11 @@ CHECKSUMS ruby-duration (3.2.3) sha256=eb3d13b1df85067a015a8fb2ed8f1eec842a3b721e47c9b6fd74d2f356069784 ruby-next-core (1.2.0) sha256=f6a7d00bb5186cecbb02f7f1845a0f3a2c9788d35b6ccff5c9be3f0d46799b86 ruby-ole (1.2.13.1) sha256=578d10dd2a797a2b35a1286c6fb2c9525f67c24791346fc8015d39f0ffa3cb72 - ruby-prof (1.7.2) sha256=270424fcac37e611f2d15a55226c4628e234f8434e1d7c25ca8a2155b9fc4340 + ruby-prof (2.0.4) sha256=42190ff6870e055a9d58b39df63e83442113ef7169c0e27b599942c3f15d1435 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-rc4 (0.1.5) sha256=00cc40a39d20b53f5459e7ea006a92cf584e9bc275e2a6f7aa1515510e896c03 ruby-saml (1.18.1) sha256=1b0e7a44aef150b4197955f5e015d593672e242cfdc5d06aa7554ec2350b9107 + ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374 ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef rubytree (2.2.0) sha256=e312dc1ed814153583b57d9662e6caac1fd60830886a571b48bb06daef906eb6 rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 @@ -2228,7 +2244,7 @@ CHECKSUMS scimitar (2.14.0) sha256=93b29132ebd50d78e61d56c8a17406b1c01857448f2d075560fd6c7660ac9aa9 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 selenium-devtools (0.143.0) sha256=23e8b364e1074a93a56ea0365ff739022a23a72e9033ad69832400c884417dc4 - selenium-webdriver (4.40.0) sha256=16ef7aa9853c1d4b9d52eac45aafa916e3934c5c83cb4facb03f250adfd15e5b + selenium-webdriver (4.41.0) sha256=cdc1173cd55cf186022cea83156cc2d0bec06d337e039b02ad25d94e41bedd22 semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163 shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34 shoulda-matchers (7.0.1) sha256=b4bfd8744c10e0a36c8ac1a687f921ee7e25ed529e50488d61b79a8688749c77 @@ -2241,7 +2257,7 @@ CHECKSUMS spring-commands-rubocop (0.4.0) sha256=3e677a2c8a27ae8a986f04bfb69e66d5d55b017541e8be93bf0dc48a7f5690c1 sprockets (3.7.5) sha256=72c20f256548f8a37fe7db41d96be86c3262fddaf4ebe9d69ec8317394fed383 sprockets-rails (3.5.2) sha256=a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e - ssrf_filter (1.0.8) sha256=03f49f54837e407d43ee93ec733a8a94dc1bcf8185647ac61606e63aaedaa0db + ssrf_filter (1.3.0) sha256=66882d7de7d09c019098d6d7372412950ae184ebbc7c51478002058307aba6f2 stackprof (0.2.28) sha256=4ec2ace02f386012b40ca20ef80c030ad711831f59511da12e83b34efb0f9a04 statesman (13.1.0) sha256=3ecb78466dfd2682e433f335a7722aa0e5b8c6853d72d83e460151b8af17a84e store_attribute (2.1.1) sha256=de53611c01d3fb4c91e0d7af4208861bd18d017c4cee682ed0473d0e4496f5b8 @@ -2267,7 +2283,7 @@ CHECKSUMS turbo_power (0.7.0) sha256=ad95d147e0fa761d0023ad9ca00528c7b7ddf6bba8ca2e23755d5b21b290d967 turbo_tests (2.2.0) tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b - tzinfo-data (1.2025.3) sha256=b546e2f1e5e5c40a0c619aafb24e30d3d6a128c2f689278f721b7286dd499562 + tzinfo-data (1.2026.1) sha256=4ea36519ae5ae2cf0fad471207a519be006daf42e3b2359ee9e9c53f113609fd uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42 unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f @@ -2276,8 +2292,8 @@ CHECKSUMS validate_email (0.1.6) sha256=9dfe9016d527b17a8d3a6e95e4dc50a125400eef899d13d4cc2a254393f82ee4 validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451 vcr (6.4.0) sha256=077ac92cc16efc5904eb90492a18153b5e6ca5398046d8a249a7c96a9ea24ae6 - vernier (1.9.0) sha256=3aa6169804144ef27744123525f8f884541185a2eeb53c77afb28ad33c4be3cd - view_component (4.4.0) sha256=ecccecb495ee4b4a5d9f93382992dc32c11a0708fde5ec261150a0f5223393d0 + vernier (1.10.0) sha256=5b1dc57012e08ed23e14f4d2943540140d454aa8434c7c35e7eb97befd4969bf + view_component (4.5.0) sha256=0d951360d830752da4d1daa5f6cb643f17c30f295fb779fd4de90a051537d9c1 virtus (2.0.0) sha256=8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2 warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0 warden-basic_auth (0.2.1) sha256=bfc752e0109c0182c3e69e930284c5e1e81e7b4a354aeb2b5914ead1391f3c6e @@ -2291,7 +2307,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.14.0) sha256=bc517bf22d692ebd80a29fc9fd2246c257aaf92d10b2735a775e2419351a43bf + yabeda (0.15.0) sha256=81ce61c6e89d42ac3c5855aa2dcddfc2347c33d1595372d59d79e7dda4e72238 yabeda-activerecord (0.1.2) sha256=1dd281a64e5742445a6718aa05e799ea08a397e9ab9c0d254ece447635a3e0e2 yabeda-prometheus-mmap (0.4.0) sha256=1a66120756d6f931f03a7784e08e79060d71681ff83a9f5287df2ff756e9e2c9 yabeda-puma-plugin (0.9.0) sha256=b78673ecc7ee30bc50691ddc41b7022c1c1801843900d5101418f4a14b550bc8 @@ -2301,7 +2317,7 @@ CHECKSUMS zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd RUBY VERSION - ruby 3.4.7p58 + ruby 4.0.1 BUNDLED WITH - 2.7.2 + 4.0.6 diff --git a/app/components/op_primer/copy_to_clipboard_component.rb b/app/components/op_primer/copy_to_clipboard_component.rb index fde52372f4c..18e0153b5f2 100644 --- a/app/components/op_primer/copy_to_clipboard_component.rb +++ b/app/components/op_primer/copy_to_clipboard_component.rb @@ -32,14 +32,25 @@ module OpPrimer class CopyToClipboardComponent < ApplicationComponent include OpPrimer::ComponentHelpers + SCHEME_OPTIONS = %i[value link].freeze + alias_method :value, :model def initialize(value = nil, scheme: :value, **system_arguments) super(value) - @scheme = scheme + @scheme = validate_scheme!(scheme) @system_arguments = system_arguments @id = SecureRandom.hex(8) end + + private + + def validate_scheme!(scheme) + scheme = scheme.to_sym + raise ArgumentError, "scheme must be one of #{SCHEME_OPTIONS}" unless SCHEME_OPTIONS.include?(scheme) + + scheme + end end end diff --git a/app/contracts/work_packages/base_contract.rb b/app/contracts/work_packages/base_contract.rb index b26b1d1c309..21c973d7a51 100644 --- a/app/contracts/work_packages/base_contract.rb +++ b/app/contracts/work_packages/base_contract.rb @@ -46,8 +46,9 @@ module WorkPackages attribute :type_id attribute :priority_id attribute :category_id + # TODO: manage_sprint_items can be removed once the sprint_id is in place. attribute :version_id, - permission: :assign_versions do + permission: %i(assign_versions manage_sprint_items) do validate_version_is_assignable end diff --git a/app/contracts/work_packages/update_contract.rb b/app/contracts/work_packages/update_contract.rb index 5e9a8777548..562bf9eba87 100644 --- a/app/contracts/work_packages/update_contract.rb +++ b/app/contracts/work_packages/update_contract.rb @@ -33,7 +33,8 @@ module WorkPackages include UnchangedProject attribute :lock_version, - permission: %i[edit_work_packages change_work_package_status assign_versions manage_subtasks move_work_packages] do + permission: %i[edit_work_packages change_work_package_status assign_versions manage_sprint_items manage_subtasks + move_work_packages] do if model.lock_version.nil? || model.lock_version_changed? errors.add :base, :error_conflict end @@ -60,6 +61,7 @@ module WorkPackages with_unchanged_project_id do next if allowed_in_work_package?(:edit_work_packages) || allowed_in_project?(:assign_versions) || + allowed_in_project?(:manage_sprint_items) || allowed_in_project?(:change_work_package_status) || allowed_in_project?(:manage_subtasks) || allowed_in_project?(:move_work_packages) diff --git a/app/controllers/custom_styles_controller.rb b/app/controllers/custom_styles_controller.rb index e35c1880fd0..301fbca287f 100644 --- a/app/controllers/custom_styles_controller.rb +++ b/app/controllers/custom_styles_controller.rb @@ -249,7 +249,7 @@ class CustomStylesController < ApplicationController def file_download(path_method) @custom_style = CustomStyle.current - if @custom_style && @custom_style.send(path_method) + if @custom_style&.send(path_method) expires_in 1.year, public: true, must_revalidate: false send_file(@custom_style.send(path_method)) else @@ -263,7 +263,7 @@ class CustomStylesController < ApplicationController return render_404 end - @custom_style.send(remove_method) + @custom_style.send("#{remove_method}!") redirect_to custom_style_path, status: :see_other end end diff --git a/app/forms/versions/form.rb b/app/forms/versions/form.rb index a1b17760550..4297fa02f8c 100644 --- a/app/forms/versions/form.rb +++ b/app/forms/versions/form.rb @@ -113,14 +113,44 @@ module Versions if backlogs_enabled? setting = version_setting_for_project - f.select_list( - name: "version[version_settings_attributes][][display]", - scope_name_to_model: false, - label: I18n.t(:label_column_in_backlog), - input_width: :small - ) do |list| - position_display_options.each do |label, value| - list.option(label:, value:, selected: setting.display == value) + if OpenProject::FeatureDecisions.scrum_projects_active? + # We originally planned to render a check_box here. But since this changes the way Rails will submit the parameters, + # this would require changing the controller or services, too. With a feature flag in place, this adds quite a + # lot of complexity. + # To circumvent this, we will use a select list with two options for now. This will not require any changes to + # controllers or services. We can fix this once the feature flag has been removed. + f.select_list( + name: "version[version_settings_attributes][][display]", + scope_name_to_model: false, + label: I18n.t(:label_used_as_backlog), + input_width: :small + ) do |list| + # Maintain the current setting for the sake of migrating sprints to versions later on + current_display_setting = setting.display + value_for_no = if current_display_setting == VersionSetting::DISPLAY_RIGHT || current_display_setting.nil? + VersionSetting::DISPLAY_NONE + else + current_display_setting + end + + list.option(label: I18n.t(:general_text_no), + value: value_for_no, + selected: setting.display != VersionSetting::DISPLAY_RIGHT) + + list.option(label: I18n.t(:general_text_yes), + value: VersionSetting::DISPLAY_RIGHT, + selected: setting.display == VersionSetting::DISPLAY_RIGHT) + end + else + f.select_list( + name: "version[version_settings_attributes][][display]", + scope_name_to_model: false, + label: I18n.t(:label_column_in_backlog), + input_width: :small + ) do |list| + position_display_options.each do |label, value| + list.option(label:, value:, selected: setting.display == value) + end end end diff --git a/app/models/custom_style.rb b/app/models/custom_style.rb index 58f4111f9fa..ac23d2c8bff 100644 --- a/app/models/custom_style.rb +++ b/app/models/custom_style.rb @@ -70,15 +70,9 @@ class CustomStyle < ApplicationRecord end end - define_method :"remove_#{name}" do - attachment = send(name) - attachment&.remove! - - if new_record? - send(:"#{name}=", nil) - else - update_columns(name => nil, updated_at: Time.zone.now) - end + define_method :"remove_#{name}!" do + super() + save! end end end diff --git a/app/models/permitted_params.rb b/app/models/permitted_params.rb index 6562883140e..51dec7954b7 100644 --- a/app/models/permitted_params.rb +++ b/app/models/permitted_params.rb @@ -557,6 +557,7 @@ class PermittedParams :priority_id, :remaining_hours, :responsible_id, + :sprint_id, :start_date, :status_id, :type_id, diff --git a/app/seeders/env_data/custom_design_seeder.rb b/app/seeders/env_data/custom_design_seeder.rb index 82bd3fa0720..d0d41302b6c 100644 --- a/app/seeders/env_data/custom_design_seeder.rb +++ b/app/seeders/env_data/custom_design_seeder.rb @@ -54,7 +54,7 @@ module EnvData data = Setting.seed_design[key.to_s] if data.blank? - custom_style.public_send(:"remove_#{key}") + custom_style.public_send(:"remove_#{key}!") elsif data.match?(/^https?:\/\//) seed_remote_url(custom_style, key, data) else diff --git a/app/services/project_custom_fields/load_service.rb b/app/services/project_custom_fields/load_service.rb index 50e3cbc4cdc..7f15d4c2c2b 100644 --- a/app/services/project_custom_fields/load_service.rb +++ b/app/services/project_custom_fields/load_service.rb @@ -31,26 +31,19 @@ module ProjectCustomFields class LoadService def initialize(project:, project_custom_fields:) - super() - @project = project - @project_custom_fields = project_custom_fields - eager_load_project_custom_field_values + @values_by_custom_field_id = + CustomValue + .includes(custom_field: :custom_options) + .where( + custom_field: project_custom_fields, + customized: project + ) + .order(:id) + .group_by(&:custom_field_id) end def get_eager_loaded_project_custom_field_values_for(custom_field_id) - @eager_loaded_project_custom_field_values.select { |pcfv| pcfv.custom_field_id == custom_field_id } - end - - private - - def eager_load_project_custom_field_values - @eager_loaded_project_custom_field_values = CustomValue - .includes(custom_field: :custom_options) - .where( - custom_field_id: @project_custom_fields.pluck(:id), - customized_id: @project.id - ) - .to_a + @values_by_custom_field_id[custom_field_id] || [] end end end diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index 78862f69417..e6e4b85f366 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -56,6 +56,12 @@ module CarrierWave end end +# CW 2.0 changed the default cache_storage from :file to nil. +# Restore :file to keep Attachment.clean_cached_files! working. +CarrierWave.configure do |config| + config.cache_storage = :file +end + unless OpenProject::Configuration.fog_credentials.empty? CarrierWave::Configuration.configure_fog! end diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index ad78036edd4..28b6fb2eb55 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -42,9 +42,10 @@ # These inflection rules are supported but not enabled by default: ActiveSupport::Inflector.inflections(:en) do |inflect| + inflect.acronym "AI" inflect.acronym "API" + inflect.acronym "ClamAV" + inflect.acronym "ICal" inflect.acronym "OAuth" inflect.acronym "OpenID" - inflect.acronym "ICal" - inflect.acronym "ClamAV" end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index a0b70d27cf2..4ecc5bc38c9 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -36,6 +36,7 @@ relative_url_root = config["rails_relative_url_root"].presence session_options = { key: config["session_cookie_name"], httponly: true, + same_site: :lax, secure: config.https?, path: relative_url_root } diff --git a/config/locales/crowdin/af.yml b/config/locales/crowdin/af.yml index 7560dcb3221..7d4ce48ac48 100644 --- a/config/locales/crowdin/af.yml +++ b/config/locales/crowdin/af.yml @@ -1375,9 +1375,6 @@ af: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ af: label_float: "Dryf" label_folder: "Folder" label_follows: "volg" - label_force_user_language_to_default: "Stel taal van gebruikers wat 'n nie-toegelate taal gebruik na verstektaal" label_form_configuration: "Vorm konfigurasie" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ af: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Skrap" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Wat wil jy doen?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/ar.yml b/config/locales/crowdin/ar.yml index dba656e844b..6e4aacc3d85 100644 --- a/config/locales/crowdin/ar.yml +++ b/config/locales/crowdin/ar.yml @@ -1435,9 +1435,6 @@ ar: dependencies: "الاعتماديات" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3739,7 +3736,6 @@ ar: label_float: "عدد عشري" label_folder: "مجلد" label_follows: "تتبع" - label_force_user_language_to_default: "إعطاء لغة للمستخدمين غير اللغة المسموح بها بشكل افتراضي" label_form_configuration: "تشكيل النموذج" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5241,7 +5237,7 @@ ar: text_default_administrator_account_changed: "تغيير حساب المسؤول الافتراضي" text_default_encoding: "الافتراضي: UTF-8" text_destroy: "احذف" - text_destroy_with_associated: "وهناك كائنات إضافية مرتبطة مع الحزمة (الحزم) العمل الموجودة المراد حذفها. هذه الكائنات من الأنواع التالية: " + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "ماذا تريد أن تفعل؟" text_diff_truncated: "... تم اقتطاعه لأنه يتجاوز الحد الأقصى للحجم التي يمكن عرضها." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/az.yml b/config/locales/crowdin/az.yml index 4fb6680e711..5f6e5b90f5f 100644 --- a/config/locales/crowdin/az.yml +++ b/config/locales/crowdin/az.yml @@ -1375,9 +1375,6 @@ az: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ az: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ az: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Sil" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/be.yml b/config/locales/crowdin/be.yml index e8f3812979b..e759195a542 100644 --- a/config/locales/crowdin/be.yml +++ b/config/locales/crowdin/be.yml @@ -1405,9 +1405,6 @@ be: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3627,7 +3624,6 @@ be: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5127,7 +5123,7 @@ be: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Выдаліць" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/bg.yml b/config/locales/crowdin/bg.yml index e797445f3f4..50c999cc691 100644 --- a/config/locales/crowdin/bg.yml +++ b/config/locales/crowdin/bg.yml @@ -1375,9 +1375,6 @@ bg: dependencies: "Зависимости" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ bg: label_float: "Плаващ" label_folder: "Папка" label_follows: "следва" - label_force_user_language_to_default: "Задаване на език по подразбиране за потребителите с липсващи в системата езици" label_form_configuration: "Конфигурация на формата" label_formula: "Formula" label_gantt_chart: "Диаграма на Гант" @@ -5011,7 +5007,7 @@ bg: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Изтрий" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Какво искате да правите?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/ca.yml b/config/locales/crowdin/ca.yml index 4675ae2a7ea..34ba075e5cf 100644 --- a/config/locales/crowdin/ca.yml +++ b/config/locales/crowdin/ca.yml @@ -1372,9 +1372,6 @@ ca: dependencies: "Dependències" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3512,7 +3509,6 @@ ca: label_float: "Flotant" label_folder: "Carpeta" label_follows: "segueix" - label_force_user_language_to_default: "Definir la llengua pels usuaris que tenen una llengua per defecte no permesa" label_form_configuration: "Configuració del formulari" label_formula: "Formula" label_gantt_chart: "Diagrama de Gantt" @@ -5002,7 +4998,7 @@ ca: text_default_administrator_account_changed: "S'ha canviat el compte d'administrador predeterminat" text_default_encoding: "Per defecte: UTF-8" text_destroy: "Esborrar" - text_destroy_with_associated: "Hi ha objectes addicionals associats amb el(s) paquet(s) de treball que es suprimiran. Aquests objectes són dels tipus següents:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Què vols fer?" text_diff_truncated: "... Aquestes diferències s'han truncat perquè excedeixen la mida màxima que es pot mostrar." text_email_delivery_not_configured: "L'entrega de correu electrònic no està configurada, i les notificacions estan desactivades.\nConfigura el teu servidor SMTP per a habilitar-les." diff --git a/config/locales/crowdin/ckb-IR.yml b/config/locales/crowdin/ckb-IR.yml index df371928f88..59f0899635c 100644 --- a/config/locales/crowdin/ckb-IR.yml +++ b/config/locales/crowdin/ckb-IR.yml @@ -1375,9 +1375,6 @@ ckb-IR: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ ckb-IR: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ ckb-IR: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/cs.yml b/config/locales/crowdin/cs.yml index dd4cf676f5e..f107d4ff779 100644 --- a/config/locales/crowdin/cs.yml +++ b/config/locales/crowdin/cs.yml @@ -1405,9 +1405,6 @@ cs: dependencies: "Závislosti" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -1531,7 +1528,7 @@ cs: enabled_modules: "Povolené moduly" identifier: "Identifikátor" latest_activity_at: "Poslední aktivita" - parent: "Nadřazený projekt" + parent: "Podprojekt" project_creation_wizard_enabled: "Project initiation request" public_value: title: "Viditelnost" @@ -1903,7 +1900,7 @@ cs: message: cannot_move_message_to_forum_of_different_project: "A message cannot be moved to a forum of a different project." notifications: - at_least_one_channel: "Pro odesílání notifikací musí být specifikován alespoň jeden kanál" + at_least_one_channel: "Alespoň jeden kanál pro odesílání oznámení musí být specifikován." attributes: read_ian: read_on_creation: "nelze nastavit na pravdivé při vytváření oznámení " @@ -2204,11 +2201,11 @@ cs: member: "Člen" news: "Novinky" notification: - one: "Notifikace" - few: "Notifikací" - many: "Notifikací" - other: "Notifikace" - placeholder_user: "Placeholder uživatel" + one: "Oznámení" + few: "Oznámení" + many: "Oznámení" + other: "Oznámení" + placeholder_user: "placeholder uživatel" project: one: "Projekt" few: "Projekty" @@ -3300,7 +3297,7 @@ cs: ai: "Artificial Intelligence (AI)" aggregation: "Agregace" api_and_webhooks: "API & Webhooky" - mail_notification: "E-mailové notifikace" + mail_notification: "E-mailová upozornění" mails_and_notifications: "E-maily a oznámení" mcp_configurations: "Model Context Protocol (MCP)" quick_add: @@ -3386,7 +3383,7 @@ cs: by_project: "Nepřečteno dle projektu" by_reason: "Důvod" inbox: "Doručená pošta" - send_notifications: "Pro tuto akci odeslat notifikaci" + send_notifications: "Odeslat oznámení pro tuto akci" work_packages: subject: created: "Pracovní balíček byl vytvořen." @@ -3627,7 +3624,6 @@ cs: label_float: "Desetinné číslo" label_folder: "Složka" label_follows: "sleduje" - label_force_user_language_to_default: "Nastavte jazyk uživatelů, kteří nemají povolený jazyk na výchozí" label_form_configuration: "Konfigurace formuláře" label_formula: "Vzorec" label_gantt_chart: "Ganttův diagram" @@ -3831,9 +3827,9 @@ cs: label_permissions: "Práva" label_permissions_report: "Přehled oprávnění" label_personalize_page: "Přizpůsobit tuto stránku" - label_placeholder_user: "Placeholder uživatel" + label_placeholder_user: "placeholder uživatel" label_placeholder_user_new: "" - label_placeholder_user_plural: "Placeholder uživatelé" + label_placeholder_user_plural: "placeholder uživatelé" label_planning: "Plánování" label_please_login: "Přihlaste se prosím" label_plugins: "Pluginy" @@ -3857,7 +3853,7 @@ cs: label_project_attribute_plural: "Atributy projektu" label_project_attribute_manage_link: "Správa atributů produktu" label_project_count: "Celkový počet projektů" - label_project_copy_notifications: "Během kopírování projektu odeslat notifikace e-mailem" + label_project_copy_notifications: "Během kopie projektu odeslat oznámení e-mailem" label_project_initiation_export_pdf: "Export PDF for %{project_creation_name}" label_project_latest: "Nejnovější projekty" label_project_default_type: "Povolit prázdný typ" @@ -4022,7 +4018,7 @@ cs: label_version_new: "Nová verze" label_version_edit: "Upravit verzi" label_version_plural: "Verze" - label_version_sharing_descendants: "S podprojekty" + label_version_sharing_descendants: "S Podprojekty" label_version_sharing_hierarchy: "S hierarchií projektu" label_version_sharing_none: "Není sdíleno" label_version_sharing_system: "Se všemi projekty" @@ -4130,28 +4126,28 @@ cs: digests: including_mention_singular: "včetně zmínky" including_mention_plural: "včetně %{number_mentioned} zmínění" - unread_notification_singular: "1 nepřečtená notifikace" - unread_notification_plural: "%{number_unread} nepřečtených notifikací" + unread_notification_singular: "1 nepřečtené oznámení" + unread_notification_plural: "%{number_unread} nepřečtených oznámení" you_have: "Máte" logo_alt_text: "Logo" mention: subject: "%{user_name} vás zmínil v #%{id} - %{subject}" notification: - center: "Centrum notifikací" + center: "Centrum oznámení" see_in_center: "Zobrazit komentář v oznamovacím centru" settings: "Změnit nastavení e-mailu" salutation: "Ahoj %{user}!" salutation_full_name: "Jméno a příjmení" work_packages: created_at: "Vytvořeno v %{timestamp} uživatelem %{user} " - login_to_see_all: "Přihlaste se pro zobrazení všech notifikací." + login_to_see_all: "Přihlaste se pro zobrazení všech oznámení." mentioned: "Byli jste zmíněni v komentáři" mentioned_by: "%{user} vás zmínil v komentáři OpenProject" more_to_see: - one: "Existuje ještě 1 pracovní balíček s notifikací." - few: "Existuje ještě %{count} pracovních balíčků s notifikacema." - many: "Existuje ještě %{count} pracovních balíčků s notifikacema." - other: "Existuje ještě %{count} pracovních balíčků s notifikacema." + one: "Máte ještě 1 pracovní balíček s notifikací." + few: "Existuje ještě %{count} pracovních balíčků s oznámeními." + many: "Máte ještě %{count} pracovních balíčků s notifikacemi." + other: "Existuje ještě %{count} pracovních balíčků s oznámeními." open_in_browser: "Otevřít v prohlížeči" reason: watched: "Sledováno" @@ -4160,7 +4156,7 @@ cs: mentioned: "Zmíněné" shared: "Sdílené" subscribed: "vše" - prefix: "Obdrženo z důvodu nastavení notifikací: %{reason}" + prefix: "Obdrženo z důvodu nastavení oznámení: %{reason}" date_alert_start_date: "Upozornění na datum" date_alert_due_date: "Upozornění na datum" reminder: "Připomínka" @@ -4461,7 +4457,7 @@ cs: permission_move_work_packages: "Přesun pracovních balíčků" permission_protect_wiki_pages: "Ochrana stránky wiki" permission_rename_wiki_pages: "Přejmenovat stránky wiki" - permission_save_queries: "Uložit zobrazení" + permission_save_queries: "Uložit pohled" permission_search_project: "Hledat projekt" permission_select_custom_fields: "Vybrat vlastní pole" permission_select_project_custom_fields: "Vyberte atributy projektu" @@ -4948,7 +4944,7 @@ cs: enable_subscriptions_text_html: Umožňuje uživatelům s nezbytnými oprávněními přihlásit se do OpenProject kalendářů a získat přístup k informacím o pracovním balíčku prostřednictvím externího klienta kalendáře. Poznámka: Před povolením si prosím přečtěte podrobnosti o odběru. language_name_being_default: "%{language_name} (výchozí)" notifications: - events_explanation: "Určuje, pro kterou událost je odeslán e-mail. Pracovní balíčky jsou z tohoto seznamu vyloučeny, protože notifikace pro ně mohou být nastavena speciálně pro každého uživatele." + events_explanation: "Určuje, pro kterou událost je odeslán e-mail. Pracovní balíčky jsou z tohoto seznamu vyloučeny, protože oznámení pro ně mohou být nastavena speciálně pro každého uživatele." delay_minutes_explanation: "Odesílání e-mailu může být pozdrženo, aby bylo uživatelům s nakonfigurovaným v oznámení aplikace před odesláním pošty potvrzeno oznámení. Uživatelé, kteří si přečtou oznámení v aplikaci, nedostanou e-mail pro již přečtené oznámení." other: "Ostatní" passwords: "Hesla" @@ -5126,10 +5122,10 @@ cs: text_default_administrator_account_changed: "Výchozí účet správce změněn" text_default_encoding: "Výchozí: UTF-8" text_destroy: "Odstranit" - text_destroy_with_associated: "Existují další objekty, které jsou přiřazeny k pracovním balíčkům a které mají být odstraněny. Tyto objekty jsou následující typy:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Co chcete udělat?" text_diff_truncated: "... Toto rozlišení bylo zkráceno, protože přesahuje maximální velikost, kterou lze zobrazit." - text_email_delivery_not_configured: "Doručení e-mailu není nakonfigurováno a notifikace jsou zakázány.\nNakonfigurujte váš SMTP server pro jejich povolení." + text_email_delivery_not_configured: "Doručení e-mailu není nakonfigurováno a oznámení jsou zakázána.\nNakonfigurujte váš SMTP server pro jejich povolení." text_enumeration_category_reassign_to: "Přiřadit je k této hodnotě:" text_enumeration_destroy_question: "%{count} objektů je přiřazeno k této hodnotě." text_file_repository_writable: "Do adresáře příloh lze zapisovat" diff --git a/config/locales/crowdin/da.yml b/config/locales/crowdin/da.yml index 945398f223a..5090f8fe75f 100644 --- a/config/locales/crowdin/da.yml +++ b/config/locales/crowdin/da.yml @@ -1373,9 +1373,6 @@ da: dependencies: "Aflæggere" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3513,7 +3510,6 @@ da: label_float: "Flyd" label_folder: "Folder" label_follows: "følger" - label_force_user_language_to_default: "Sæt sprog for brugere, der har forhåndsvalgt et ikke gangbart sprog" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5007,7 +5003,7 @@ da: text_default_administrator_account_changed: "Forhåndsvalgt administratorkonto ændret" text_default_encoding: "Forhåndsvalgt: UTF-8" text_destroy: "Slet" - text_destroy_with_associated: "Der er yderligere objekter tilknyttet arbejdspakken/-kerne som skal slettes. Disse er af følgende art:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Hvad vil du lave?" text_diff_truncated: "... Denne afvigelse blev undertrykt da den overstiger den længde, der kan vises." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/de.yml b/config/locales/crowdin/de.yml index 7ad65decd38..1fa19d1ecc7 100644 --- a/config/locales/crowdin/de.yml +++ b/config/locales/crowdin/de.yml @@ -86,11 +86,11 @@ de: type_token_text: "Ihr Enterprise-Token-Text" token_placeholder: "Enterprise-Token Text hier einfügen" token_caption: "Weitere Informationen über die Aktivierung der Enterprise Edition finden Sie in unserer [Dokumentation](docs_url)." - add_token: "Enterprise-Edition Support Token hochladen" + add_token: "Enterprise edition Support Token hochladen" replace_token: "Aktuellen Enterprise edition Support Token ersetzen" order: "Enterprise on-premises bestellen" - paste: "Enterprise-Edition Support Token hier einfügen" - required_for_feature: "Dieses Add-on ist nur mit einem aktiven Enterprise-Edition Support-Token verfügbar." + paste: "Enterprise edition Support Token hier einfügen" + required_for_feature: "Dieses Add-on ist nur mit einem aktiven Enterprise edition Support-Token verfügbar." enterprise_link: "Klicken Sie hier für weitere Informationen." start_trial: "Kostenlose Testversion starten" book_now: "Jetzt buchen" @@ -1133,10 +1133,10 @@ de: tab: "Titel konfigurieren" manually_editable_subjects: label: "Manuell bearbeitbare Titel" - caption: "Nutzer:innen können die Titel der Arbeitspakete ohne Einschränkungen manuell eingeben und bearbeiten." + caption: "Benutzer können die Titel der Arbeitspakete ohne Einschränkungen manuell eingeben und bearbeiten." automatically_generated_subjects: label: "Automatisch generierte Titel" - caption: "Definieren Sie ein Schema aus referenzierten Attributen und Freitext für die automatische Generierung von Arbeitspakettiteln. Nutzer:innen können diese nicht manuell editieren." + caption: "Definieren Sie ein Schema aus referenzierten Attributen und Freitext für die automatische Generierung von Arbeitspakettiteln. Nutzer können diese nicht manuell editieren." token: label_with_context: "%{attribute_context}: %{attribute_label}" context: @@ -1190,7 +1190,7 @@ de: manual_with_children: "Hat Unteraufgaben aber ihre Startdaten werden ignoriert." title: automatic_mobile: "Automatisch geplant." - automatic_with_children: "Unteraufgaben bestimmen Termine." + automatic_with_children: "Die Termine sind durch untergeordnete Arbeitspakete bestimmt." automatic_with_predecessor: "Der Anfangstermin wird von einem Vorgänger festgelegt." manual_mobile: "Manuell geplant." manually_scheduled: "Manuell geplant – Daten unabhängig von Beziehungen." @@ -1292,7 +1292,7 @@ de: label_child_plural: "Unteraufgaben" new_child: "Neue Unteraufgabe" new_child_description: "Erstellt ein zugehöriges Arbeitspaket als Unteraufgabe des aktuellen (übergeordneten) Arbeitspakets" - child: "Unteraufgabe" + child: "Kind" child_description: "Macht das zugehörige Arbeitspaket zu einer Unteraufgabe des aktuellen (übergeordneten) Arbeitspakets" parent: "Übergeordnetes Arbeitspaket" parent_description: "Wandelt das verknüpfte in ein übergeordnetes Arbeitspaket dieses Arbeitspakets um" @@ -1367,9 +1367,6 @@ de: dependencies: "Abhängigkeiten" activerecord: attributes: - agile/sprint: - sharing: "Teilen" - finish_date: "Enddatum" jira_import: projects: "Projekte" "import/jira": @@ -1548,7 +1545,7 @@ de: column_names: "Spalten" relations_to_type_column: "Beziehungen zu %{type}" relations_of_type_column: "Beziehungen der Art: %{type}" - child_work_packages: "Unteraufgaben" + child_work_packages: "Kinder" group_by: "Gruppiere Ergebnisse nach" sort_by: "Ergebnisse sortieren nach" filters: "Filter" @@ -2048,7 +2045,7 @@ de: status_transition_invalid: "ist ungültig, da kein valider Übergang vom alten zum neuen Status für die aktuelle Rolle des Nutzers existiert." status_invalid_in_type: "ist ungültig, da der aktuelle Status nicht in diesem Typ vorhanden ist." type: - cannot_be_milestone_due_to_children: "kann kein Meilenstein werden, da dieses Arbeitspaket Unteraufgaben besitzt." + cannot_be_milestone_due_to_children: "kann kein Meilenstein werden, da dieses Arbeitspaket Unterelemente besitzt." priority_id: only_active_priorities_allowed: "muss aktiv sein." category: @@ -2862,7 +2859,7 @@ de: error_custom_option_not_found: "Option ist nicht vorhanden." error_enterprise_plan_needed: "Sie benötigen den Enterprise-Plan %{plan}, um diese Aktion durchzuführen." error_enterprise_activation_user_limit: "Ihr Konto konnte nicht aktiviert werden (Nutzerlimit erreicht). Bitte kontaktieren Sie Ihren Administrator um Zugriff zu erhalten." - error_enterprise_token_invalid_domain: "Die Enterprise-Edition ist nicht aktiv. Die aktuelle Domain (%{actual}) entspricht nicht dem erwarteten Hostnamen (%{expected})." + error_enterprise_token_invalid_domain: "Die Enterprise edition ist nicht aktiv. Die aktuelle Domain (%{actual}) entspricht nicht dem erwarteten Hostnamen (%{expected})." error_failed_to_delete_entry: "Fehler beim Löschen dieses Eintrags." error_in_dependent: "Fehler beim Versuch, abhängiges Objekt zu ändern: %{dependent_class} #%{related_id} - %{related_subject}: %{error}" error_in_new_dependent: "Fehler beim Versuch, abhängiges Objekt zu erstellen: %{dependent_class} - %{related_subject}: %{error}" @@ -3150,7 +3147,7 @@ de: dates: working: "%{date} ist jetzt ein Arbeitstag" non_working: "%{date} ist jetzt ein arbeitsfreier Tag" - progress_mode_changed_to_status_based: Fortschrittberechnung wurde auf Status-bezogen gesetzt + progress_mode_changed_to_status_based: Fortschrittberechnung wurde auf Status-basiert gesetzt status_excluded_from_totals_set_to_false_message: jetzt in den Gesamtwerten der Hierarchie enthalten status_excluded_from_totals_set_to_true_message: jetzt von den Hierarchie-Gesamtwerten ausgeschlossen status_percent_complete_changed: "% abgeschlossen von %{old_value}% auf %{new_value} % geändert" @@ -3474,7 +3471,7 @@ de: label_enumerations: "Aufzählungen" label_enterprise: "Enterprise" label_enterprise_active_users: "%{current}/%{limit} gebuchte aktive Nutzer" - label_enterprise_edition: "Enterprise Edition" + label_enterprise_edition: "Enterprise edition" label_enterprise_support: "Enterprise Support" label_environment: "Umgebung" label_estimates_and_progress: "Schätzungen und Fortschritt" @@ -3507,7 +3504,6 @@ de: label_float: "Gleitkommazahl" label_folder: "Ordner" label_follows: "folgt" - label_force_user_language_to_default: "Setze Sprache für Nutzer, die eine nicht erlaubte Sprache gewählt haben, auf die Standard-Sprache" label_form_configuration: "Formularkonfiguration" label_formula: "Formel" label_gantt_chart: "Gantt-Diagramm" @@ -4535,7 +4531,7 @@ de: update_timeout: "Speichere die Informationen bzgl. des genutzten Festplattenspeichers eines Projektarchivs für N Minuten.\nErhöhen Sie diesen Wert zur Verbesserung der Performance, da die Erfassung des genutzten Festplattenspeichers Ressourcen-intensiv ist." oauth_application_details: "Der Client Geheimcode wird nach dem Schließen dieses Fensters nicht mehr zugänglich sein. Bitte kopieren Sie diese Werte in die Nextcloud OpenProject Integrationseinstellungen:" oauth_application_details_link_text: "Zu den Einstellungen gehen" - setup_documentation_details: "Wenn Sie Hilfe bei der Konfiguration eines neuen Dateispeichers benötigen, konsultieren Sie bitte die Dokumentation: " + setup_documentation_details: "Wenn Sie Hilfe bei der Konfiguration eines neuen Datei-Speichers benötigen, konsultieren Sie bitte die Dokumentation: " setup_documentation_details_link_text: "Dateispeicher einrichten" show_warning_details: "Um diesen Dateispeicher nutzen zu können, müssen Sie das Modul und den spezifischen Speicher in den Projekteinstellungen jedes gewünschten Projekts aktivieren." subversion: @@ -5005,7 +5001,7 @@ de: text_default_administrator_account_changed: "Administrator-Kennwort geändert" text_default_encoding: "Default: UTF-8" text_destroy: "Löschen" - text_destroy_with_associated: "Es sind weitere Datenobjekte mit den bzw. dem zu löschenden Arbeitpaket(en) verbunden. Es handelt sich dabei um Objekte der folgenden Typen:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Was soll mit den Objekten geschehen?" text_diff_truncated: "... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet." text_email_delivery_not_configured: "E-Mail-Zustellung ist nicht konfiguriert und Benachrichtigungen sind deaktiviert.\nKonfigurieren Sie Ihren SMTP-Server, um sie zu aktivieren." @@ -5176,7 +5172,7 @@ de: warning_user_limit_reached_admin: > Das Hinzufügen zusätzlicher Benutzer überschreitet das aktuelle Benutzerlimit. Bitte aktualisieren Sie Ihr Abonnement um sicherzustellen, dass externe Benutzer auf diese Instanz zugreifen können. warning_user_limit_reached_instructions: > - Du hast dein Nutzerlimit erreicht (%{current}/%{max} active users). Bitte kontaktiere sales@openproject.com um deinen Enterprise Edition Plan upzugraden und weitere Nutzer hinzuzufügen. + Du hast dein Nutzerlimit erreicht (%{current}/%{max} active users). Bitte kontaktiere sales@openproject.com um deinen Enterprise edition Plan upzugraden und weitere Nutzer hinzuzufügen. warning_protocol_mismatch_html: > warning_bar: diff --git a/config/locales/crowdin/el.yml b/config/locales/crowdin/el.yml index 03e7bc2aa24..b77bd6605be 100644 --- a/config/locales/crowdin/el.yml +++ b/config/locales/crowdin/el.yml @@ -1371,9 +1371,6 @@ el: dependencies: "Εξαρτήσεις" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3511,7 +3508,6 @@ el: label_float: "Float" label_folder: "Φάκελος" label_follows: "ακολουθεί" - label_force_user_language_to_default: "Ορίστε την γλώσσα των χρηστών που έχουν μια μη επιτρεπόμενη γλώσσα ως προεπιλογή" label_form_configuration: "Διαμόρφωση φόρμας" label_formula: "Formula" label_gantt_chart: "Διάγραμμα Gantt" @@ -5006,7 +5002,7 @@ el: text_default_administrator_account_changed: "Ο προεπιλεγμένος λογαριασμός του διαχειριστή άλλαξε" text_default_encoding: "Προεπιλογή: UTF-8" text_destroy: "Διαγραφή" - text_destroy_with_associated: "Υπάρχουν επιπλέον αντικείμενα μαζί με το πακέτο εργασίας που πρόκειται να διαγραφεί. Τα αντικείμενα αυτά είναι των παρακάτω τύπων:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Τι θέλετε να κάνετε;" text_diff_truncated: "... Αυτό το diff περικόπηκε επειδή υπερβαίνει το μέγιστο μέγεθος που μπορεί να εμφανιστεί." text_email_delivery_not_configured: "Η διανομή email δεν είναι διαμορφωμένη και οι ειδοποιήσεις είναι απενεργοποιημένες.\nΔιαμορφώστε τον SMTP εξυπηρετητή για τις ενεργοποιήσετε." diff --git a/config/locales/crowdin/eo.yml b/config/locales/crowdin/eo.yml index 6779fbdf68b..b6518d29d63 100644 --- a/config/locales/crowdin/eo.yml +++ b/config/locales/crowdin/eo.yml @@ -1375,9 +1375,6 @@ eo: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ eo: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Formulara agordo" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ eo: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Forigi" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/es.yml b/config/locales/crowdin/es.yml index 7cf426f2210..0e5879e962a 100644 --- a/config/locales/crowdin/es.yml +++ b/config/locales/crowdin/es.yml @@ -1198,7 +1198,7 @@ es: automatic_with_children: "Fechas determinadas por paquetes de trabajo secundarios." automatic_with_predecessor: "La fecha de inicio la fija un predecesor." manual_mobile: "Programado manualmente." - manually_scheduled: "Programado manualmente. No afectadas por relaciones." + manually_scheduled: "Programado manualmente. Fechas no afectadas por relaciones." blankslate: title: "Sin predecesores" description: "Para activar la programación automática, este paquete de trabajo debe tener al menos un predecesor. Entonces se programará automáticamente para que comience después del predecesor más cercano." @@ -1372,9 +1372,6 @@ es: dependencies: "Dependencias" activerecord: attributes: - agile/sprint: - sharing: "Uso compartido" - finish_date: "Fecha de finalización" jira_import: projects: "Proyectos" "import/jira": @@ -3512,7 +3509,6 @@ es: label_float: "Desprender" label_folder: "Carpeta" label_follows: "sigue" - label_force_user_language_to_default: "Configurar idioma de usuarios que tienen un lenguaje no permitido por defecto" label_form_configuration: "Configuración del formato" label_formula: "Fórmula" label_gantt_chart: "Diagrama de Gantt" @@ -5007,7 +5003,7 @@ es: text_default_administrator_account_changed: "Se ha cambiado la cuenta de administrador por defecto" text_default_encoding: "Por defecto: UTF-8" text_destroy: "Borrar" - text_destroy_with_associated: "Hay objetos adicionales asociados a los paquetes de trabajo que van a ser eliminados. Esos objetos son de los siguientes tipos:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "¿Qué quieres hacer?" text_diff_truncated: "... Esta diferencia se truncó porque excede el tamaño máximo que puede visualizarse." text_email_delivery_not_configured: "No se ha configurado la entrega de correo electrónico y se han deshabilitado las notificaciones.\nConfigure el servidor SMTP para habilitarlas." diff --git a/config/locales/crowdin/et.yml b/config/locales/crowdin/et.yml index f13d5fd5a4f..a6fcc60babc 100644 --- a/config/locales/crowdin/et.yml +++ b/config/locales/crowdin/et.yml @@ -1375,9 +1375,6 @@ et: dependencies: "Sõltuvused" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ et: label_float: "Ujuvkomaarv" label_folder: "Kaust" label_follows: "järgneb" - label_force_user_language_to_default: "Sea keel kasutajatele, kellel on vaikimisi keeleks valitud mittelubatud keel" label_form_configuration: "Vormi seadistamine" label_formula: "Formula" label_gantt_chart: "Gantt diagrammi" @@ -5011,7 +5007,7 @@ et: text_default_administrator_account_changed: "Algne administraatori konto on muudetud" text_default_encoding: "Vaikimisi: UTF-8" text_destroy: "Kustuta" - text_destroy_with_associated: "Kustutamiseks määratud teema(de)ga on seostatud veel objekte. Objektid on järgnevate tüüpidega:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Mis sa soovid ette võtta?" text_diff_truncated: "... Osa erinevusi jäi välja, sest neid on näitamiseks liiga palju." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/eu.yml b/config/locales/crowdin/eu.yml index 76789185613..572dcf3dbad 100644 --- a/config/locales/crowdin/eu.yml +++ b/config/locales/crowdin/eu.yml @@ -1375,9 +1375,6 @@ eu: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ eu: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ eu: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/fa.yml b/config/locales/crowdin/fa.yml index 12cc5d96079..dfb4d8e381c 100644 --- a/config/locales/crowdin/fa.yml +++ b/config/locales/crowdin/fa.yml @@ -1375,9 +1375,6 @@ fa: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ fa: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "نمودار گانت" @@ -5011,7 +5007,7 @@ fa: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "حذف" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/fi.yml b/config/locales/crowdin/fi.yml index 6b5fb883c21..9bda2f6de4f 100644 --- a/config/locales/crowdin/fi.yml +++ b/config/locales/crowdin/fi.yml @@ -1375,9 +1375,6 @@ fi: dependencies: "Riippuvuudet" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ fi: label_float: "Liukuluku" label_folder: "Kansio" label_follows: "seuraa" - label_force_user_language_to_default: "Määritä oletuskieli jos käytössä on kieli joka ei ole sallittu" label_form_configuration: "Lomakkeen muokkaus" label_formula: "Formula" label_gantt_chart: "Gantt-kaavio" @@ -5011,7 +5007,7 @@ fi: text_default_administrator_account_changed: "Oletuarvoinen järjestelmävalvoja muutettu" text_default_encoding: "Default: UTF-8" text_destroy: "Poista" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Mitä sinä haluat tehdä?" text_diff_truncated: "... Tämä katkaistiin koska suurin mahdollinen näytettävä määrä tuli täyteen." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/fil.yml b/config/locales/crowdin/fil.yml index 5431f3454f8..cea711ea957 100644 --- a/config/locales/crowdin/fil.yml +++ b/config/locales/crowdin/fil.yml @@ -1375,9 +1375,6 @@ fil: dependencies: "Dependencia" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ fil: label_float: "Lumutang" label_folder: "Folder" label_follows: "sundan" - label_force_user_language_to_default: "Magtakda ng linggwahe ng mga gumagamit na mayroon pinayagang linggwahe sa default" label_form_configuration: "Form kompigurasyon" label_formula: "Formula" label_gantt_chart: "Gantt tsart" @@ -5009,7 +5005,7 @@ fil: text_default_administrator_account_changed: "I-default ang tagapangasiwa ng pagbago ng akwant" text_default_encoding: "I-default: UTF-8" text_destroy: "Burahin" - text_destroy_with_associated: "Mayroong karagdagang mga bagay naka-associaye sa work packGe na maaring burahin. Yung mga bagay ay ang mga sumusunod na uri:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Ano gusto mong gawin?" text_diff_truncated: "... Itong diff ay naka-truncatw dahil ito ay lamps na sa pinakamataas na laki na maaring ipakita." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/fr.yml b/config/locales/crowdin/fr.yml index 559073e7009..c2de1c2c5c7 100644 --- a/config/locales/crowdin/fr.yml +++ b/config/locales/crowdin/fr.yml @@ -615,13 +615,11 @@ fr: op_dry_validation: or: "ou" errors: - unexpected_key: "is not allowed." array?: "doit être un tableau." decimal?: "doit être une décimale." defined: "ne doit pas être défini." eql?: "doit être égal à %{left}." filled?: "doit être rempli." - format?: "is in invalid format." greater_or_equal_zero: "doit être supérieur ou égal à 0." gteq?: "doit être supérieur ou égal à %{num}." hash?: "doit être un hachage." @@ -648,9 +646,7 @@ fr: parent: not_descendant: "doit être un descendant de la racine de la hiérarchie." str?: "doit être une chaîne de caractères." - time?: "must be a time." type?: "doit être de type : %{type}." - uri?: "is not a valid URI." rules: copy_workflow_from: "Type de copie du flux de travail" enabled: "Activé" @@ -1373,9 +1369,6 @@ fr: dependencies: "Dépendances" activerecord: attributes: - agile/sprint: - sharing: "Partage" - finish_date: "Échéance" jira_import: projects: "Projets" "import/jira": @@ -3513,7 +3506,6 @@ fr: label_float: "Flottant" label_folder: "Dossier" label_follows: "suivi" - label_force_user_language_to_default: "Pour cet utilisateur, revenir au langage par defaut" label_form_configuration: "Configuration du formulaire" label_formula: "Formule" label_gantt_chart: "Diagramme de Gantt" @@ -5009,7 +5001,7 @@ fr: text_default_administrator_account_changed: "Le compte administrateur par défaut a été changé" text_default_encoding: "Défaut: UTF-8" text_destroy: "Supprimer" - text_destroy_with_associated: "Il y a des objets supplémentaires associés à ce(s) lot(s) de travaux qui doivent être supprimés. Ces objets sont des types suivants :" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Que voulez-vous faire?" text_diff_truncated: "... Cette « diff » a été tronquée car elle dépasse la taille maximale d'affichage." text_email_delivery_not_configured: "L'envoi d'e-mails n'est pas configuré et les notifications sont désactivées.\nConfigurer votre serveur SMTP pour les activer." diff --git a/config/locales/crowdin/he.yml b/config/locales/crowdin/he.yml index 09834c23b46..82f3fd67b62 100644 --- a/config/locales/crowdin/he.yml +++ b/config/locales/crowdin/he.yml @@ -1405,9 +1405,6 @@ he: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3627,7 +3624,6 @@ he: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5127,7 +5123,7 @@ he: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "ברירת מחדל: UTF-8" text_destroy: "מחק" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/hi.yml b/config/locales/crowdin/hi.yml index 9edebc1a0b8..ad8fa0e62ed 100644 --- a/config/locales/crowdin/hi.yml +++ b/config/locales/crowdin/hi.yml @@ -1373,9 +1373,6 @@ hi: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3513,7 +3510,6 @@ hi: label_float: "फ्लोट" label_folder: "Folder" label_follows: "फॉलो करें" - label_force_user_language_to_default: "डिफ़ॉल्ट करने के लिए एक गैर अनुमति भाषा वाले उपयोगकर्ताओं की भाषा सेट करें" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "गैंट चार्ट" @@ -5009,7 +5005,7 @@ hi: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "मिटाएँ" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/hr.yml b/config/locales/crowdin/hr.yml index b31fd47a678..4cd1e2e5d6b 100644 --- a/config/locales/crowdin/hr.yml +++ b/config/locales/crowdin/hr.yml @@ -1390,9 +1390,6 @@ hr: dependencies: "Ovisnosti" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3571,7 +3568,6 @@ hr: label_float: "Plutajući" label_folder: "Mapa" label_follows: "slijedi" - label_force_user_language_to_default: "Postavi zadani jezik za korisnike koji imaju nedozvoljeni jezik kao zadani" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5069,7 +5065,7 @@ hr: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Zadana vrijednost: UTF-8" text_destroy: "Obriši" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Što želite uraditi?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/hu.yml b/config/locales/crowdin/hu.yml index ffaba5f5d36..adf2b2fa51b 100644 --- a/config/locales/crowdin/hu.yml +++ b/config/locales/crowdin/hu.yml @@ -1374,9 +1374,6 @@ hu: dependencies: "Szükséges összetevők" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3514,7 +3511,6 @@ hu: label_float: "Mozgó" label_folder: "Mappa" label_follows: "követi ezt" - label_force_user_language_to_default: "Nyelv beállitása a felhasználóknak, amelyeknél nem az alapértelmezett nyelv az elfogadott" label_form_configuration: "Űrlap konfiguráció" label_formula: "Formula" label_gantt_chart: "Gantt-diagram" @@ -5009,7 +5005,7 @@ hu: text_default_administrator_account_changed: "Az alapértelmezett rendszergazdai fiók megváltozott" text_default_encoding: "Alapértelmezett érték: UTF-8" text_destroy: "Törlés" - text_destroy_with_associated: "További objektumok vannak hozzárendelve, a feladatcsoport(ok)hoz, amelyeket törölni kell. Ezek az objektumok a következők:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Mit szeretne tenni?" text_diff_truncated: "... Ez a különbség csonkolva lett, mert meghaladja a maximálisan megjeleníthető méretet." text_email_delivery_not_configured: "Email küldés nincs bekonfigurálva, és az értesítések ki vannak kapcsolva.\nÁllítsa be az SMTP szervert, hogy engedélyezze azokat." diff --git a/config/locales/crowdin/id.yml b/config/locales/crowdin/id.yml index 97be06bd288..2957fc980f8 100644 --- a/config/locales/crowdin/id.yml +++ b/config/locales/crowdin/id.yml @@ -1356,9 +1356,6 @@ id: dependencies: "Dependensi" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3455,7 +3452,6 @@ id: label_float: "Float" label_folder: "Folder" label_follows: "Berikut" - label_force_user_language_to_default: "Set bahasa user ke default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Bagan Gantt" @@ -4946,7 +4942,7 @@ id: text_default_administrator_account_changed: "Akun administrator default terlah dirubah" text_default_encoding: "Default: UTF-8" text_destroy: "Hapus" - text_destroy_with_associated: "Ada tambahan objek terasosiasi dengan Work Package yang akan dihapus. Objek tersebut adalah Tipe :" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Apa yang ingin Anda lakukan?" text_diff_truncated: "... diff ini telah dipotong karena melebihi batas maks. yang dapat ditampilkan." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/it.yml b/config/locales/crowdin/it.yml index 71fae7fe806..831602def76 100644 --- a/config/locales/crowdin/it.yml +++ b/config/locales/crowdin/it.yml @@ -1372,9 +1372,6 @@ it: dependencies: "Dipendenze" activerecord: attributes: - agile/sprint: - sharing: "Condivisione" - finish_date: "Data di fine" jira_import: projects: "Progetti" "import/jira": @@ -3512,7 +3509,6 @@ it: label_float: "Virgola mobile" label_folder: "Cartella" label_follows: "segue" - label_force_user_language_to_default: "Imposta la lingua degli utenti che non hanno una lingua consentita a quella predefinita" label_form_configuration: "Configurazione del modulo" label_formula: "Formula" label_gantt_chart: "Diagramma di Gantt" @@ -5008,7 +5004,7 @@ it: text_default_administrator_account_changed: "Account amministratore predefinito cambiato" text_default_encoding: "Predefinito: UTF-8" text_destroy: "Cancella" - text_destroy_with_associated: "Ci sono ulteriori oggetti associati con la/le macro-attività che devono essere eliminati. Tali oggetti sono dei seguenti tipi:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Cosa vuoi fare?" text_diff_truncated: "... Questo diff è stato troncato perché supera la dimensione massima che può essere visualizzata." text_email_delivery_not_configured: "Consegna email non configurata. Le notifiche sono state disabilitate.\nConfigura il tuo server SMTP per abilitarle." diff --git a/config/locales/crowdin/ja.yml b/config/locales/crowdin/ja.yml index a3bf9537a79..41d0383ae59 100644 --- a/config/locales/crowdin/ja.yml +++ b/config/locales/crowdin/ja.yml @@ -108,7 +108,7 @@ ja: jemalloc_allocator: Jemalloc メモリアロケータ journal_aggregation: explanation: - text: "ユーザーの個々のアクション(例えば、ワークパッケージを2回更新する)は、それらの年齢差が指定されたタイムスパン未満である場合、単一のアクションに集約されます。これらはアプリケーション内で1つのアクションとして表示されます。これはまた、送信されるメールの数を減らし、 %{webhook_link} の遅延にも影響します。" + text: "ユーザーの個々のアクション (例:ワークパッケージを2回更新する)は、指定された時間範囲よりも時間差が小さい場合、単一のアクションに集約されます。 これらはアプリケーション内で単一のアクションとして表示されます。 これにより、送信されるメールの数が減少し、 %{webhook_link} の遅延にも影響します。" link: "webhook" import: title: "Import" @@ -318,13 +318,13 @@ ja: success: "MCP configuration was updated successfully." scim_clients: authentication_methods: - sso: "IDプロバイダーからのJWT" - oauth2_client: "OAuth 2.0クライアント認証情報" + sso: "アイデンティティプロバイダからのJWT" + oauth2_client: "OAuth 2.0 クライアント資格情報" oauth2_token: "静的アクセストークン" created_client_credentials_dialog_component: - title: "クライアント認証情報の作成" - heading: "クライアント認証情報が生成されました" - one_time_hint: "クライアント・シークレットが表示されるのはこの時だけです。必ずコピーしてください。" + title: "クライアントの資格情報が作成されました" + heading: "クライアントの資格情報が生成されました" + one_time_hint: "クライアントのシークレットが表示される唯一の時間です。今すぐコピーしてください。" created_token_dialog_component: title: "トークンを作成しました" heading: "トークンが生成されました" @@ -337,21 +337,21 @@ ja: edit: label_delete_scim_client: "SCIM クライアントを削除" form: - auth_provider_description: "これは、SCIM プロバイダによって追加されたユーザが OpenProject で認証するために使用するサービスです。" - authentication_method_description_html: "これは SCIM クライアントが OpenProject で認証する方法です。OAuth トークンにscim_v2スコープが含まれていることを確認してください。" - description: "これらの設定オプションの詳細については、[SCIMクライアントの設定に関する文書](docs_url)を参照してください。" + auth_provider_description: "これは、SCIMプロバイダが追加したユーザーがOpenProjectでの認証に使用するサービスです。" + authentication_method_description_html: "これは SCIM クライアントが OpenProject で認証する方法です。OAuth トークンに scim_v2 スコープが含まれていることを確認してください。" + description: "設定オプションの詳細については、[SCIM クライアントの設定に関するドキュメント](docs_url)を参照してください。" jwt_sub_description: "例えば、Keycloakの場合、これはSCIMクライアントに関連付けられたサービスアカウントのUUIDです。あなたのユースケースにあった Subject claim を見つける方法については [ドキュメント](docs_url) を参照してください。" - name_description: "このクライアントが設定された理由を他の管理者が理解しやすい名前を選んでください。" + name_description: "他の管理者がこのクライアントが設定された理由を理解するのに役立つ名前を選択してください。" index: - description: "ここで設定された SCIM クライアントは、OpenProject SCIM サーバ API と対話し、ユーザアカウントやグループのプロビジョニング、更新、デプロビジョニングを行うことができます。" - label_create_button: "SCIMクライアントの追加" + description: "ここで設定されたSCIMクライアントは、OpenProjectのSCIMサーバー APIと相互作用して、ユーザーアカウントとグループのプロビジョニング、更新、およびデプロビジョニングを行うことができます。" + label_create_button: "SCIMクライアントを追加" new: title: "新しいSCIMクライアント" revoke_static_token_dialog_component: confirm_button: "取り消す" - title: "静的トークンの失効" - heading: "このトークンを本当に取り消しますか?" - description: "このトークンを使っている SCIM クライアントは、OpenProject の SCIM サーバ API にアクセスできなくなります。" + title: "静的トークンを取り消す" + heading: "このトークンを取り消してもよろしいですか?" + description: "このトークンを使用する SCIM クライアントは、OpenProject の SCIM サーバ API にアクセスできなくなります。" table_component: blank_slate: title: "SCIMクライアントがまだ設定されていません" @@ -902,26 +902,26 @@ ja: other: "また、 %{shared_work_packages_link} はこのユーザーと共有されています。" remove_project_membership_or_work_package_shares_too: "直接のメンバーとしてのユーザーだけを削除したい(および共有を維持したい)、またはワークパッケージの共有も削除しますか?" will_remove_all_user_access_priveleges: "このメンバーを削除すると、プロジェクトへのユーザーのすべてのアクセス権が削除されます。ユーザーはまだサイトの一部として存在します。" - will_remove_all_group_access_priveleges: "このメンバを削除すると、プロジェクトに対するグループのすべてのアクセス権が削除されます。グループはサイトの一部としてまだ存在します。" - cannot_delete_inherited_membership: "このプロジェクトのメンバーであるグループに所属しているため、このメンバーを削除することはできません。" - cannot_delete_inherited_membership_note_admin_html: "%{administration_settings_link}で、プロジェクトのメンバーとしてグループを削除することも、特定のメンバーをグループから削除することもできます。" - cannot_delete_inherited_membership_note_non_admin: "プロジェクトのメンバーとしてグループを削除するか、管理者に連絡してこの特定のメンバーをグループから削除することができます。" + will_remove_all_group_access_priveleges: "このメンバーを削除すると、グループのすべてのアクセス権がプロジェクトに削除されます。グループはサイトの一部として存在します。" + cannot_delete_inherited_membership: "このメンバーはこのプロジェクトのメンバーであるグループに属しているため、削除できません。" + cannot_delete_inherited_membership_note_admin_html: "プロジェクトのメンバーとしてグループを削除するか、 %{administration_settings_link} のグループからこの特定のメンバーを削除することができます。" + cannot_delete_inherited_membership_note_non_admin: "プロジェクトのメンバーとしてグループを削除するか、管理者に問い合わせてグループから特定のメンバーを削除することができます。" delete_work_package_shares_dialog: - title: "ワーク・パッケージ・シェアの破棄" + title: "ワークパッケージの共有の取り消し" shared_with_this_user_html: other: "%{all_shared_work_packages_link} はこのユーザーと共有されています。" shared_with_this_group_html: other: "%{all_shared_work_packages_link} はこのグループと共有されています。" shared_with_permission_html: other: "%{shared_work_packages_link} のみが %{shared_role_name} 権限と共有されています。" - revoke_all_or_with_role: "すべての共有ワークパッケージ、または %{shared_role_name} 権限を持つワークパッケージのみへのアクセス権を剥奪しますか?" - will_not_affect_inherited_shares: "(これは、そのグループと共有しているワークパッケージには影響しません)。" - cannot_remove_inherited: "グループで共有されたワークパッケージの共有は削除できません。" - cannot_remove_inherited_with_role: "ロール %{shared_role_name} で共有されるワークパッケージは、グループを介して共有され、削除することはできません。" - cannot_remove_inherited_note_admin_html: "%{administration_settings_link}、グループへの共有を取り消すか、グループからこの特定のメンバーを削除することができます。" - cannot_remove_inherited_note_non_admin: "グループへの共有を取り消すか、管理者に連絡して特定のメンバーをグループから削除することができます。" - will_revoke_directly_granted_access: "このアクションは、グループと共有されているワークパッケージ以外の、すべてのワークパッケージへのアクセス権を剥奪する。" - will_revoke_access_to_all: "このアクションは、すべてのアクセス権を剥奪する。" + revoke_all_or_with_role: "共有されたワークパッケージ、または %{shared_role_name} 権限を持つワークパッケージのみへのアクセスを取り消しますか?" + will_not_affect_inherited_shares: "(これはグループと共有されているワークパッケージには影響しません)。" + cannot_remove_inherited: "グループ間で共有されるワークパッケージは削除できません。" + cannot_remove_inherited_with_role: "ワークパッケージとロール %{shared_role_name} が共有されているため、削除できません。" + cannot_remove_inherited_note_admin_html: "あなたは、グループへの共有を取り消すか、 %{administration_settings_link} のグループからこの特定のメンバーを削除することができます。" + cannot_remove_inherited_note_non_admin: "共有をグループに取り消すか、管理者に問い合わせてグループから特定のメンバーを削除することができます。" + will_revoke_directly_granted_access: "このアクションは、すべてのユーザーへのアクセスを取り消しますが、グループと共有されているワークパッケージです。" + will_revoke_access_to_all: "このアクションは、すべてのユーザーへのアクセスを取り消します。" my: access_token: dialog: @@ -945,7 +945,7 @@ ja: no_results_title_text: "現在、有効なアクセス トークンはありません。" notice_api_token_revoked: "APIトークンが削除されました。新しいトークンを作成するには、APIセクションの作成ボタンを使用してください。" notice_rss_token_revoked: "RSSトークンが削除されました。新しいトークンを作成するには、RSSセクションのリンクを使用してください。" - notice_ical_token_revoked: 'プロジェクト "%{project_name}" のカレンダー "%{calendar_name}" の iCalendar トークン "%{token_name}" が失効しました。このトークンを持つiCalendar URLは無効になりました。' + notice_ical_token_revoked: 'プロジェクト "%{token_name}" のカレンダー "%{calendar_name}" の iCalendar トークン "%{project_name}" が取り消されました。 このトークンのiCalendar URLは無効です。' password_confirmation_dialog: confirmation_required: "You need to enter your account password to confirm this change." title: "Confirm your password to continue" @@ -966,7 +966,7 @@ ja: matrix_check_uncheck_all_in_col_label_html: "Toggle all %{module} permissions for %{role} role" users: autologins: - prompt: "ログインしたまま %{num_days}" + prompt: "%{num_days} のログインを維持" sessions: session_name: "%{browser_name} %{browser_version} の %{os_name}" browser: "ブラウザ" @@ -980,17 +980,17 @@ ja: current: "Current (this device)" title: "セッション管理" instructions: "You are logged in to your account through the following devices. Revoke sessions that you do not recognise or from devices you do not control." - may_not_delete_current: "現在のセッションを削除することはできません。" + may_not_delete_current: "現在のセッションは削除できません。" deletion_warning: "Are you sure you want to revoke this session? You will be logged out on this device." groups: member_in_these_groups: "このユーザーは現在以下のグループのメンバーです:" no_results_title_text: このユーザーは現在どのグループのメンバーでもありません。 - summary_with_more: '%{names} と %{count_link}のメンバー。' - more: "%{count} もっと見る" - summary: '%{names}のメンバー。' + summary_with_more: '%{names} と %{count_link} のメンバー。' + more: "%{count} 以上" + summary: '%{names} のメンバー .' memberships: no_results_title_text: このユーザは現在プロジェクトのメンバーではありません。 - open_profile: "プロフィール" + open_profile: "プロファイルを開く" invite_user_modal: invite: "招待" title: @@ -1047,7 +1047,7 @@ ja: placeholder_users: right_to_manage_members_missing: > プレースホルダーユーザを削除する権限がありません。 プレースホルダー ユーザーがメンバーであるすべてのプロジェクトのメンバーを管理する権利はありません。 - delete_tooltip: "プレースホルダー・ユーザーの削除" + delete_tooltip: "プレースホルダー ユーザーを削除" deletion_info: heading: "プレースホルダー ユーザー %{name} を削除" data_consequences: > @@ -1065,11 +1065,11 @@ ja: reactions: action_title: "リアクト" add_reaction: "リアクションを追加" - react_with: "%{reaction} と リアクト" - and_user: "および %{user}" + react_with: "%{reaction} で反応する" + and_user: "と %{user}" and_others: other: と %{count} その他 - reaction_by: "%{reaction} によって" + reaction_by: "%{reaction} による" reportings: index: no_results_title_text: 現在、ステータス報告はありません。 @@ -1080,19 +1080,20 @@ ja: このステータスの色を割り当てたり変更する場合にクリックします。 ステータスボタンに表示され、テーブル内のワークパッケージを強調表示するために使用できます。 status_default_text: |- - 新しいワークパッケージは、デフォルトでこのタイプに設定される。読み取り専用にはできない。 + 新しいワークパッケージはデフォルトでこのタイプに設定されています。読み取り専用にすることはできません。 status_excluded_from_totals_text: |- - このステータスを持つワークパッケージを、階層内の「作業」、「 - 残作業」、「完了率」の合計から除外するには、このオプションをオンにします。 + このオプションをオンにすると、このステータスのワークパッケージを合計作業量、 + 残作業量、および階層構造で完了させることができます。 status_percent_complete_text: |- ステータスベースの進捗計算モードでは、このステータスが選択されると、作業 パッケージの「完了%」が自動的にこの値に設定される。 ワークベースモードでは無視される。 status_readonly_html: | - このステータスを持つワークパッケージを読み取り専用としてマークするには、このオプションをチェックする。 - ステータス以外の属性は変更できません。 + ワークパッケージを読み取り専用としてマークするには、このオプションをオンにしてください。 + ステータスを除いて変更することはできません。 +
- 注意: 継承された値 (子やリレーションなど) は適用されます。 + メモ: 継承された値 (例えば、子や関連) が適用されます。 index: no_results_title_text: 現在、ワークパッケージのステータスはありません。 no_results_content_text: 新しいステータスを追加 @@ -1102,7 +1103,7 @@ ja: is_readonly: "読み取り専用" excluded_from_totals: "合計から除外" themes: - dark: "暗い" + dark: "ダーク" light: "ライト" sync_with_os: "自動(OSのテーマ設定に追従)" types: @@ -1220,15 +1221,15 @@ ja: could_not_be_saved: "次のワークパッケージを保存できませんでした:" none_could_be_saved: "%{total} ワークパッケージのどれも更新できませんでした。" x_out_of_y_could_be_saved: "%{failing} の %{total} ワークパッケージのうち、 %{success} を更新できませんでした。" - selected_because_descendants: "%{selected} のワークパッケージが選択されたが、合計 %{total} のワークパッケージが影響を受け、その中には子孫も含まれる。" - descendant: "選択された子孫" + selected_because_descendants: "%{selected} ワークパッケージが選択されている間、合計で %{total} ワークパッケージが子孫を含む影響を受けます。" + descendant: "選択された子孫です" move: no_common_statuses_exists: "選択されたすべてのワークパッケージに利用できるステータスはありません。 それらの状態は変更できません。" unsupported_for_multiple_projects: "複数のプロジェクトからのワークパッケージの一括移動 / コピーはサポートされていません" current_type_not_available_in_target_project: > - ワークパッケージの現在のタイプがターゲットプロジェクトで有効になっていません。変更しない場合は、ターゲットプロジェクトでタイプを有効にしてください。そうでない場合は、リストからターゲットプロジェクトで使用可能なタイプを選択してください。 + ターゲット プロジェクトで現在のワークパッケージのタイプが有効になっていません。 変更を行わないようにしたい場合は、対象プロジェクトのタイプを有効にしてください。 それ以外の場合は、リストからターゲット プロジェクトで使用可能なタイプを選択します。 bulk_current_type_not_available_in_target_project: > - ワークパッケージの現在のタイプがターゲットプロジェクトで有効になっていません。変更しない場合は、ターゲットプロジェクトでタイプを有効にしてください。そうでない場合は、リストからターゲットプロジェクトで使用可能なタイプを選択してください。 + 現在のタイプのワークパッケージはターゲット プロジェクトで有効になっていません。 変更を行わないようにしたい場合は、対象プロジェクトのタイプを有効にしてください。 それ以外の場合は、リストからターゲット プロジェクトで使用可能なタイプを選択します。 sharing: missing_workflow_warning: title: "ワークパッケージの共有のためのワークフローがありません" @@ -1252,9 +1253,9 @@ ja: no_results_title_text: 現在、有効なバージョンはありません。 work_package_relations_tab: index: - action_bar_title: "他のワークパッケージとのリレーションを追加して、それらの間にリンクを作成する。" - no_results_title_text: 現在、利用可能な関係はない。 - blankslate_heading: "関係なし" + action_bar_title: "他のワークパッケージにリレーションを追加して、その間にリンクを作成します。" + no_results_title_text: 現在利用可能なリレーションはありません。 + blankslate_heading: "リレーションなし" blankslate_description: "このワークパッケージにはまだリレーションがありません。" label_add_child_button: "子要素" label_add_x: "%{x} を追加" @@ -1358,9 +1359,6 @@ ja: dependencies: "依存関係" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3457,7 +3455,6 @@ ja: label_float: "小数" label_folder: "フォルダ" label_follows: "次の項目に後続" - label_force_user_language_to_default: "ユーザーの言語設定が利用許可されていない場合、デフォルト言語を利用する" label_form_configuration: "フォーム設定" label_formula: "数式" label_gantt_chart: "ガントチャート" @@ -4951,7 +4948,7 @@ ja: text_default_administrator_account_changed: "管理者アカウントでデフォルト設定が変更済み" text_default_encoding: "既定値: UTF-8" text_destroy: "削除" - text_destroy_with_associated: "削除されるワークパッケージと追加の対象物が関連付けています。それらの対象物は次の種類です:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "どれかを選択して下さい。" text_diff_truncated: "... 差分の行数が表示可能な上限を超えました。超過分は表示しません。" text_email_delivery_not_configured: "メール配信が設定されておらず、通知が無効になっています。\nSMTPサーバーを有効にしてください。" diff --git a/config/locales/crowdin/js-ca.yml b/config/locales/crowdin/js-ca.yml index b4773fd1995..1ad220d568b 100644 --- a/config/locales/crowdin/js-ca.yml +++ b/config/locales/crowdin/js-ca.yml @@ -104,7 +104,7 @@ ca: button_save: "Desa" button_settings: "Configuració" button_uncheck_all: "Desmarca-ho tot" - button_update: "Actualitzar" + button_update: "Actualitza" button_export-atom: "Descarregar Atom" button_generate_pdf: "Generate PDF" button_create: "Crear" diff --git a/config/locales/crowdin/js-de.yml b/config/locales/crowdin/js-de.yml index 902f1884686..d37cb74b060 100644 --- a/config/locales/crowdin/js-de.yml +++ b/config/locales/crowdin/js-de.yml @@ -138,7 +138,7 @@ de: description_available_columns: "Verfügbare Spalten" description_current_position: "Sie sind hier: " description_select_work_package: "Arbeitspaket #%{id} auswählen" - description_subwork_package: "Unteraufgabe von Arbeitspaket #%{id}" + description_subwork_package: "Kind von Arbeitspaket #%{id}" editor: revisions: "Lokale Änderungen anzeigen" no_revisions: "Keine lokalen Änderungen gefunden" @@ -455,7 +455,7 @@ de: label_total_progress: "%{percent}% Gesamtfortschritt" label_total_amount: "Gesamt: %{amount}" label_updated_on: "aktualisiert am" - label_value_derived_from_children: "(aggregierter Wert von Unteraufgaben)" + label_value_derived_from_children: "(aggregierter Wert von Kindelementen)" label_children_derived_duration: "Aggregierte Dauer der Unteraufgaben" label_warning: "Warnung" label_work_package: "Arbeitspaket" @@ -864,7 +864,7 @@ de: title: "Neues Arbeitspaket" header: "Neu: %{type}" header_no_type: "Neues Arbeitspaket (Typ noch nicht gesetzt)" - header_with_parent: "Neu: %{type} (Unteraufgabe von %{parent_type} #%{id})" + header_with_parent: "Neu: %{type} (Kind von %{parent_type} #%{id})" button: "Erstellen" duplicate: title: "Arbeitspaket duplizieren" @@ -1061,7 +1061,7 @@ de: single_text: "Sind Sie sicher, dass Sie das Arbeitspaket löschen möchten?" bulk_text: "Sind Sie sicher, dass Sie die folgenden %{label} löschen möchten?" has_children: "Dieses Arbeitspaket hat %{childUnits}:" - confirm_deletion_children: "Ich bestätige, dass alle Unteraufgaben der hier aufgeführten Arbeitspakete rekursiv entfernt werden." + confirm_deletion_children: "Ich bestätige, dass alle untergordneten Elemente der hier aufgeführten Arbeitspakete rekursiv entfernt werden." deletes_children: "Alle Unteraufgaben und deren Nachkommen werden auch rekursiv gelöscht." destroy_time_entry: title: "Löschen der Zeitbuchung bestätigen" diff --git a/config/locales/crowdin/js-ja.yml b/config/locales/crowdin/js-ja.yml index f4cba405f83..7121902ba95 100644 --- a/config/locales/crowdin/js-ja.yml +++ b/config/locales/crowdin/js-ja.yml @@ -32,12 +32,12 @@ ja: draggable_hint: | 埋め込み画像または添付ファイルをエディタにドラッグします。 ドラッグしつづけると閉じているエディタ領域が開きます。 - quarantined_hint: "ウイルスが発見されたように、ファイルは隔離されています。ダウンロードできません。" + quarantined_hint: "ウイルスが発見されたため,ファイルは隔離されています。ダウンロードできません。" autocomplete_ng_select: - add_tag: "アイテムを追加" + add_tag: "項目を追加" clear_all: "すべてクリア" loading: "読み込み中..." - not_found: "アイテムが見つかりません" + not_found: "見つかりませんでした" type_to_search: "検索キーワードを入力" autocomplete_select: placeholder: @@ -67,7 +67,7 @@ ja: button_back_to_list_view: "リスト表示に戻る" button_cancel: "キャンセル" button_close: "閉じる" - button_change_project: "別のプロジェクトに移動" + button_change_project: "他のプロジェクトに移る" button_check_all: "全てを選択" button_configure-form: "フォームを設定" button_confirm: "確認" @@ -75,7 +75,7 @@ ja: button_copy: "コピー" button_copy_to_clipboard: "クリップボードにコピー" button_copy_link_to_clipboard: "クリップボードにリンクをコピー" - button_copy_to_other_project: "別のプロジェクトで複製" + button_copy_to_other_project: "別のプロジェクトで複製する" button_custom-fields: "カスタムフィールド" button_delete: "削除" button_delete_watcher: "ウォッチャーを削除" @@ -97,7 +97,7 @@ ja: button_open_fullscreen: "全画面表示を開く" button_show_cards: "カードビュー表示" button_show_list: "リストビュー表示" - button_show_table: "テーブルビューを表示" + button_show_table: "テーブル表示" button_show_gantt: "ガントビューを表示" button_show_fullscreen: "全画面表示" button_more_actions: "その他の操作" @@ -107,7 +107,7 @@ ja: button_uncheck_all: "全てを選択解除" button_update: "更新" button_export-atom: "Atomをダウンロード" - button_generate_pdf: "PDFを生成" + button_generate_pdf: "PDF作成" button_create: "作成" card: add_new: "新規カード追加" @@ -141,8 +141,8 @@ ja: description_select_work_package: "ワークパッケージを選択 #%{id}" description_subwork_package: "ワークパッケージの子 #%{id}" editor: - revisions: "ローカルの変更を表示" - no_revisions: "ローカルの変更は見つかりませんでした" + revisions: "ローカルの修正を表示" + no_revisions: "ローカルでの修正は見つからず" preview: "プレビューモードの切り替え" source_code: "Markdown ソースモードの切り替え" error_saving_failed: "次のエラーで文書を保存するのに失敗しました: %{error}" @@ -155,7 +155,7 @@ ja: attribute_reference: macro_help_tooltip: "このテキストセグメントはマクロによって動的にレンダリングされています。" not_found: "要求されたリソースが見つかりませんでした" - nested_macro: "このマクロは %{model} %{id} を再帰的に参照しています。" + nested_macro: "このマクロは %{model} %{id}を再帰的に参照している。" invalid_attribute: "選択した属性 '%{name}' は存在しません。" child_pages: button: "子ページへのリンク" @@ -211,10 +211,10 @@ ja: calendar: empty_state_header: "休業日" empty_state_description: '休業日が定義されていません。「休業日を追加」ボタンをクリックして日付を追加してください。' - new_date: "(新規)" + new_date: "(新)" add_non_working_day: "休業日を追加" - already_added_error: "この日付の非作業日はすでに存在します。それぞれの日付に1つの非作業日が作成されます。" - change_button: "保存してスケジュールを変更" + already_added_error: "この日付の非営業日はすでに存在します。一意の日付に対して作成できる非営業日は1つだけです。" + change_button: "保存して再スケジュール" change_title: "営業日を変更する" removed_title: "以下の日を非稼働日リストから削除します:" change_description: "営業日とみなす曜日を変更すると、このサイト内のすべてのプロジェクトのすべてのワークパッケージの開始日と終了日に影響を与える可能性があります。" @@ -296,14 +296,14 @@ ja: ical_sharing_modal: title: "カレンダーを購読する" inital_setup_error_message: "データ取得中にエラーが発生しました。" - description: "URL(iCalendar)を使って外部クライアントでこのカレンダーを購読し、そこから最新のワークパッケージ情報を見ることができます。" - warning: "このURLを他のユーザーと共有しないでください。このリンクがあれば、誰でもアカウントやパスワードなしでワークパッケージの詳細を見ることができます。" - token_name_label: "どこで使うのですか?" + description: "URL(iCalendar)を使用して、外部クライアントでこのカレンダーを購読し、そこから最新のワークパッケージ情報を表示することができます。" + warning: "このURLを他のユーザーと共有しないでください。このリンクを持つ誰でもアカウントやパスワードなしでワークパッケージの詳細を表示することができます。" + token_name_label: "どこで使うのですか??" token_name_placeholder: '名前を入力してください。例:"電話"' token_name_description_text: 'If you subscribe to this calendar from multiple devices, this name will help you distinguish between them in your access tokens list.' copy_url_label: "URLをコピー" - ical_generation_error_text: "カレンダー URL の生成中にエラーが発生しました。" - success_message: 'URL "%{name}" は正常にクリップボードにコピーされました。サブスクリプションを完了するためにカレンダークライアントに貼り付けてください。' + ical_generation_error_text: "カレンダーのURL生成時にエラーが発生しました。" + success_message: 'URL "%{name}" がクリップボードにコピーされました。カレンダークライアントに貼り付けて購読を完了してください。' label_activate: "有効にする" label_assignee: "担当者" label_assignee_alt_text: "This work package is assigned to %{name}" @@ -316,7 +316,7 @@ ja: label_add_row_before: "前に行を追加" label_add_selected_columns: "選択した列を追加" label_added_by: "追加した人" - label_added_time_by: '%{author} が %{age} に追加しました' + label_added_time_by: '追加 %{author} %{age}' label_ago: "○日前" label_all: "全て" label_all_projects: "すべてのプロジェクト" @@ -429,7 +429,7 @@ ja: label_repository_plural: "リポジトリ" label_resize_project_menu: "Resize project menu" label_save_as: "名前をつけて保存" - label_search_columns: "列を検索" + label_search_columns: "列を検索する" label_select_watcher: "ウォッチャーを選択..." label_selected_filter_list: "選択されたフィルタ" label_show_attributes: "すべての属性を表示" @@ -467,8 +467,8 @@ ja: label_watch_work_package: "ワークパッケージをウォッチ" label_watcher_added_successfully: "ウォッチャーが正常に追加されました !" label_watcher_deleted_successfully: "ウォッチャーが正常に削除されました !" - label_work_package_details_you_are_here: "あなたは %{tab} %{type} %{subject} のタブにいます。" - label_work_package_context_menu: "ワークパッケージのコンテキスト メニュー" + label_work_package_details_you_are_here: "あなたは %{type} %{subject}の %{tab} タブを表示しています。" + label_work_package_context_menu: "ワークパッケージのコンテキストメニュー" label_unwatch: "ウォッチしない" label_unwatch_work_package: "ワークパッケージのウォッチを削除" label_uploaded_by: "アップロードした人" @@ -499,7 +499,7 @@ ja: label_version_plural: "バージョン" label_view_has_changed: "このビューには未保存の変更があります。 クリックすると保存します。" help_texts: - show_modal: "ヘルプテキストを表示" + show_modal: "ヘルプテキストを表示する" onboarding: buttons: skip: "スキップ" @@ -507,7 +507,7 @@ ja: got_it: "了承" steps: help_menu: "ヘルプ(?)メニューは、その他のヘルプリソースを提供します。ここでは、ユーザーガイド、役立つハウツービデオなどを見つけることができます。
OpenProjectでの作業をお楽しみください!" - members: "新しい メンバー をプロジェクトに招待します。" + members: "新しいメンバーをプロジェクトに招待する。" quick_add_button: "ヘッダーナビゲーションにあるプラス(+)アイコンをクリックして、新規プロジェクトを作成したり、同僚を招待したりできます。" sidebar_arrow: "プロジェクトのメインメニューに戻るには、左上の矢印を使います。" welcome: "3分間のイントロダクションツアーで、最も重要な機能を学びましょう。
最後までステップを完了することをお勧めします。ツアーはいつでも再開できます。" @@ -614,33 +614,33 @@ ja: work_package_commented: "すべての新着コメント" work_package_created: "新しいワークパッケージ" work_package_processed: "すべてのステータス変更" - work_package_prioritized: "すべての優先度の変更" - work_package_scheduled: "すべての日付の変更" + work_package_prioritized: "すべての優先順位の変更" + work_package_scheduled: "すべての日付変更" global: immediately: title: "参加" - description: "自分が関与しているワークパッケージのすべてのアクティビティに関する通知(アサイニー、アカウンタブル、ウォッチャー)。" + description: "自分が関与しているワークパッケージのすべてのアクティビティに関する通知(担当、責任、ウォッチャー)。" delayed: title: "不参加" - description: "すべてのプロジェクトでのアクティビティの追加通知。" + description: "全プロジェクトにおける活動の追加通知。" date_alerts: title: "日付アラート" - description: "あなたが関与している(アサイニー、アカウンタブル、ウォッチャー)オープンワークパッケージの重要な日付が近づくと自動通知。" + description: "あなたが関与している(担当、責任、ウォッチャー)オープンワークパッケージの重要な日付が近づくと自動通知。" overdue: 期限を過ぎた場合 project_specific: title: "プロジェクト固有の通知設定" - description: "これらのプロジェクト固有の設定は、上記のデフォルト設定を上書きする。" + description: "これらのプロジェクト固有の設定は、上記のデフォルト設定を上書きします。" add: "プロジェクトの設定を追加する" - already_selected: "このプロジェクトは既に選択されています" + already_selected: "このプロジェクトはすでに選ばれている" remove: "プロジェクトの設定を削除する" pagination: no_other_page: "このページだけです。" - pages_skipped: "ページがスキップされました。" + pages_skipped: "ページスキップ。" page_navigation: "ページネーション・ナビゲーション" per_page_navigation: 'ページ毎のアイテム選択' pages: page_number: ページ %{number} - show_per_page: ページあたり %{number} を表示 + show_per_page: ページごとに %{number} placeholders: default: "-" subject: "ここにタイトルを入力します" @@ -650,7 +650,7 @@ ja: project: autocompleter: label: "プロジェクト名の入力補完" - click_to_switch_to_project: "プロジェクト: %{projectname}" + click_to_switch_to_project: "プロジェクト: %{projectname}" context: "プロジェクトのコンテキスト" not_available: "プロジェクトなし" required_outside_context: > @@ -658,30 +658,30 @@ ja: reminders: settings: daily: - add_time: "時間を追加" + add_time: "時間を追加する" enable: "毎日のEメールリマインダーを有効にする" explanation: "このリマインダーは、未読の通知に対してのみ、指定した時間帯にのみ届きます。 %{no_time_zone}" no_time_zone: "アカウントにタイムゾーンを設定するまでは、時間はUTCで解釈されます。" time_label: "時間 %{counter}:" - title: "未読の通知を毎日メールで通知する" + title: "未読通知メールのリマインダーを毎日送信する" workdays: title: "これらの日にリマインダーメールを受け取る" immediate: title: "電子メールのリマインダーを送信" mentioned: "@mentionするとすぐに" - personal_reminder: "個人的なリマインダーを受け取ったら直ちに" + personal_reminder: "個人的なリマインダーを受け取ったとき" alerts: title: "その他の項目(ワークパッケージではないもの)に対する電子メールアラート" explanation: > 本日の通知はワークパッケージに限定されています。これらのイベントが通知に含まれるようになるまで、Eメールアラートを受信し続けることを選択できます: news_added: "ニュースが追加されました。" news_commented: "ニュースへのコメント" - document_added: "追加された書類" + document_added: "ドキュメントの追加" forum_messages: "新しいフォーラムメッセージ" wiki_page_added: "Wikiページが追加されました。" wiki_page_updated: "Wikiページが更新されました。" - membership_added: "メンバーシップが追加されました" - membership_updated: "メンバーシップ更新" + membership_added: "メンバーシップの追加" + membership_updated: "メンバーシップの更新" title: "電子メールによるリマインダー" pause: label: "毎日のEメールリマインダーを一時停止する" @@ -1172,7 +1172,7 @@ ja: toggle_title: "ベースライン" clear: "クリア" apply: "適用" - header_description: "過去のいずれかの時点からこのリストに加えられた変更を強調する。" + header_description: "過去の選択した時点からこのリストに加えられた変更をハイライト" show_changes_since: "以降の変更を表示する" help_description: "ベースラインの基準タイムゾーン。" time_description: "現地時間: %{datetime}" diff --git a/config/locales/crowdin/js-no.yml b/config/locales/crowdin/js-no.yml index 37d78447d18..1594fc635ae 100644 --- a/config/locales/crowdin/js-no.yml +++ b/config/locales/crowdin/js-no.yml @@ -104,7 +104,7 @@ button_save: "Lagre" button_settings: "Innstillinger" button_uncheck_all: "Avmerk alle" - button_update: "Oppdater" + button_update: "Oppdatèr" button_export-atom: "Last ned Atom" button_generate_pdf: "Generate PDF" button_create: "Opprett" diff --git a/config/locales/crowdin/js-ro.yml b/config/locales/crowdin/js-ro.yml index 16b185d9890..7b76eb3d468 100644 --- a/config/locales/crowdin/js-ro.yml +++ b/config/locales/crowdin/js-ro.yml @@ -104,7 +104,7 @@ ro: button_save: "Salvează" button_settings: "Setări" button_uncheck_all: "Deselectează tot" - button_update: "Actualizează" + button_update: "Actualizare" button_export-atom: "Descarcă Atom" button_generate_pdf: "Generează PDF" button_create: "Creează" diff --git a/config/locales/crowdin/js-ru.yml b/config/locales/crowdin/js-ru.yml index 9a21927c4a5..66046d33eda 100644 --- a/config/locales/crowdin/js-ru.yml +++ b/config/locales/crowdin/js-ru.yml @@ -104,7 +104,7 @@ ru: button_save: "Сохранить" button_settings: "Настройки" button_uncheck_all: "Снять все отметки" - button_update: "Обновить" + button_update: "Обновление" button_export-atom: "Скачать Atom" button_generate_pdf: "Создать PDF" button_create: "Создать" diff --git a/config/locales/crowdin/js-vi.yml b/config/locales/crowdin/js-vi.yml index 8be53ce498e..fd2da032e6c 100644 --- a/config/locales/crowdin/js-vi.yml +++ b/config/locales/crowdin/js-vi.yml @@ -104,7 +104,7 @@ vi: button_save: "lưu lại" button_settings: "cài đặt" button_uncheck_all: "Bỏ chọn tất cả" - button_update: "cập nhật" + button_update: "Cập Nhật" button_export-atom: "Tải xuống nguyên tử" button_generate_pdf: "Tạo PDF" button_create: "Tạo mới" diff --git a/config/locales/crowdin/ka.yml b/config/locales/crowdin/ka.yml index d3437bd3170..6689745c517 100644 --- a/config/locales/crowdin/ka.yml +++ b/config/locales/crowdin/ka.yml @@ -1375,9 +1375,6 @@ ka: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ ka: label_float: "მცურავი" label_folder: "საქაღალდე" label_follows: "მიჰყვება" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "განტის დიაგრამა" @@ -5011,7 +5007,7 @@ ka: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "ნაგულისხმევი: UTF-8" text_destroy: "წაშლა" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/kk.yml b/config/locales/crowdin/kk.yml index 60142b65d61..2c7415da883 100644 --- a/config/locales/crowdin/kk.yml +++ b/config/locales/crowdin/kk.yml @@ -1375,9 +1375,6 @@ kk: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ kk: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ kk: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/ko.yml b/config/locales/crowdin/ko.yml index 7c128c388a5..4697b359e9e 100644 --- a/config/locales/crowdin/ko.yml +++ b/config/locales/crowdin/ko.yml @@ -1360,9 +1360,6 @@ ko: dependencies: "종속성" activerecord: attributes: - agile/sprint: - sharing: "공유" - finish_date: "종료 날짜" jira_import: projects: "프로젝트" "import/jira": @@ -3459,7 +3456,6 @@ ko: label_float: "부동" label_folder: "폴더" label_follows: "팔로우" - label_force_user_language_to_default: "허용되지 않는 언어가 있는 사용자의 언어를 기본값으로 설정" label_form_configuration: "양식 구성" label_formula: "공식" label_gantt_chart: "Gantt 차트" @@ -4950,7 +4946,7 @@ ko: text_default_administrator_account_changed: "기본 관리자 계정 변경됨" text_default_encoding: "기본: UTF-8" text_destroy: "삭제" - text_destroy_with_associated: "삭제할 작업 패키지와 연결된 추가 개체가 있습니다. 이러한 개체의 유형은 다음과 같습니다." + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "어떤 작업을 수행하시겠습니까?" text_diff_truncated: "... 이 차이점은 표시할 수 있는 최대 크기를 초과하므로 잘렸습니다." text_email_delivery_not_configured: "이메일 배달이 구성되지 않았고, 알림이 비활성화되었습니다.\nSMTP 서버를 구성하여 활성화하세요." diff --git a/config/locales/crowdin/lt.yml b/config/locales/crowdin/lt.yml index 2695f2ed81b..adc352b8586 100644 --- a/config/locales/crowdin/lt.yml +++ b/config/locales/crowdin/lt.yml @@ -1402,9 +1402,6 @@ lt: dependencies: "Priklausomybės" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3624,7 +3621,6 @@ lt: label_float: "Skaičius su kableliu" label_folder: "Aplankas" label_follows: "seka" - label_force_user_language_to_default: "Nustatyti numatytąją kalbą vartotojams, kurie nurodo, sistemoje nežinomą kalbą" label_form_configuration: "Formos konfigūracija" label_formula: "Formula" label_gantt_chart: "Ganto grafikas" @@ -5121,7 +5117,7 @@ lt: text_default_administrator_account_changed: "Administratoriaus numatytoji paskyra pakeista" text_default_encoding: "Numatytasis: UTF-8" text_destroy: "Ištrinti" - text_destroy_with_associated: "Yra papildomų objektų, kurie susieti su darbų paketu(-ais), kurie ketinami ištrinti.\nTie objektai yra šių tipų:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Ką norite daryti?" text_diff_truncated: "... Šis diff'as sutrauktas, nes viršija maksimalų rodomų eilučių skaičių." text_email_delivery_not_configured: "El. laiškų pristatymas yra nesukonfigūruotas, dėl to pranešimai yra išjungti.\nSukonfigūruokite prisijungimą prie SMTP serverio pranešimų įjungimui." diff --git a/config/locales/crowdin/lv.yml b/config/locales/crowdin/lv.yml index bf427456467..26388cf32d9 100644 --- a/config/locales/crowdin/lv.yml +++ b/config/locales/crowdin/lv.yml @@ -1390,9 +1390,6 @@ lv: dependencies: "Saistītie projekti" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3571,7 +3568,6 @@ lv: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5069,7 +5065,7 @@ lv: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Dzēst" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/mn.yml b/config/locales/crowdin/mn.yml index 4d9b083a9c1..459ddc86681 100644 --- a/config/locales/crowdin/mn.yml +++ b/config/locales/crowdin/mn.yml @@ -1375,9 +1375,6 @@ mn: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ mn: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ mn: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/ms.yml b/config/locales/crowdin/ms.yml index a9aebc96b7d..fecb0931294 100644 --- a/config/locales/crowdin/ms.yml +++ b/config/locales/crowdin/ms.yml @@ -1358,9 +1358,6 @@ ms: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3457,7 +3454,6 @@ ms: label_float: "Terapung" label_folder: "Folder" label_follows: "ikuti" - label_force_user_language_to_default: "Tetapkan bahasa pengguna yang mempunyai bahasa yang tidak dibenarkan kepada default" label_form_configuration: "Konfigurasi borang" label_formula: "Formula" label_gantt_chart: "Carta Gantt" @@ -4950,7 +4946,7 @@ ms: text_default_administrator_account_changed: "Akaun default pentadbir diubah" text_default_encoding: "Default: UTF-8" text_destroy: "Padam" - text_destroy_with_associated: "Terdapat objek tambahan yang berkaitan dengan pakej kerja yang perlu dipadam. Objek tersebut adalah daripada jenis berikut:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Apakah yang anda ingin lakukan?" text_diff_truncated: "... Perbezaan ini dipendekkan kerana ia melebihi saiz maksimum yang boleh dipaparkan." text_email_delivery_not_configured: "Penghantaran e-mel belum dikonfigurasi, dan pemberitahuan telah dinyahaktifkan.\nKonfigurasikan server SMTP anda untuk mengaktifkannya." diff --git a/config/locales/crowdin/ne.yml b/config/locales/crowdin/ne.yml index f6a50986e85..687b15e21e0 100644 --- a/config/locales/crowdin/ne.yml +++ b/config/locales/crowdin/ne.yml @@ -1375,9 +1375,6 @@ ne: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ ne: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ ne: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/nl.yml b/config/locales/crowdin/nl.yml index 27a36ab4708..e81aa4da576 100644 --- a/config/locales/crowdin/nl.yml +++ b/config/locales/crowdin/nl.yml @@ -1371,9 +1371,6 @@ nl: dependencies: "Afhankelijkheden" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3511,7 +3508,6 @@ nl: label_float: "Zwevend" label_folder: "Map" label_follows: "volgt" - label_force_user_language_to_default: "Taal van gebruikers met een niet toegestane taal op standaard instellen" label_form_configuration: "Formulierconfiguratie" label_formula: "Formula" label_gantt_chart: "Gantt-grafiek" @@ -5006,7 +5002,7 @@ nl: text_default_administrator_account_changed: "Standaard beheerdersaccount gewijzigd" text_default_encoding: "Standaardinstelling: UTF-8" text_destroy: "Verwijderen" - text_destroy_with_associated: "Er zijn extra objecten gerelateerd aan het/de te verwijderen werkpakket(ten). Deze objecten zijn van de soorten:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Wat wilt u doen?" text_diff_truncated: "... deze diff is truncated vanwege dat de maximale grote is overschreden om te tonen." text_email_delivery_not_configured: "E-mailbezorging is niet geconfigureerd, en meldingen zijn uitgeschakeld.\nConfigureer uw SMTP-server om deze in te schakelen." diff --git a/config/locales/crowdin/no.yml b/config/locales/crowdin/no.yml index 11cab7c8348..3dfdb5c7143 100644 --- a/config/locales/crowdin/no.yml +++ b/config/locales/crowdin/no.yml @@ -1374,9 +1374,6 @@ dependencies: "Avhengigheter" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3514,7 +3511,6 @@ label_float: "Flyt" label_folder: "Mappe" label_follows: "følger" - label_force_user_language_to_default: "Sett språk for brukere som har et ikke-tillatt språk som standard" label_form_configuration: "Skjema konfigurering" label_formula: "Formula" label_gantt_chart: "Gantt diagram" @@ -5010,7 +5006,7 @@ text_default_administrator_account_changed: "Standard administratorkonto er endret" text_default_encoding: "Standard: UTF-8" text_destroy: "Slett" - text_destroy_with_associated: "Det er andre objekter tilknyttet arbeidspakken(e) som er i ferd med å bli slettet. Disse objektene er av følgende typer:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Hva vil du gjøre?" text_diff_truncated: "... Denne diff ble forkortet fordi den overstiger maksimalgrensen for hva som kan vises." text_email_delivery_not_configured: "E-postlevering er ikke konfigurert, og varsler er deaktivert.\nKonfigurer SMTP-serveren for å aktivere dem." diff --git a/config/locales/crowdin/pl.yml b/config/locales/crowdin/pl.yml index b9e6678eb5f..033e0aa738c 100644 --- a/config/locales/crowdin/pl.yml +++ b/config/locales/crowdin/pl.yml @@ -1401,9 +1401,6 @@ pl: dependencies: "Zależności" activerecord: attributes: - agile/sprint: - sharing: "Udostępnianie" - finish_date: "Data zakończenia" jira_import: projects: "Projekty" "import/jira": @@ -3623,7 +3620,6 @@ pl: label_float: "Liczba rzeczywista" label_folder: "Katalog" label_follows: "Następujący" - label_force_user_language_to_default: "Ustaw język domyślny dla użytkowników o innych ustawieniach językowych niż dozwolone" label_form_configuration: "Konfiguracja formularza" label_formula: "Wzór" label_gantt_chart: "Wykres Gantta" @@ -5120,7 +5116,7 @@ pl: text_default_administrator_account_changed: "Domyślne konto administratora zostało zmienione" text_default_encoding: "Domyślnie: UTF-8" text_destroy: "Usuń" - text_destroy_with_associated: "Istnieją dodatkowe obiekty powiązane z pakietami roboczymi, które będą usunięte. Te obiekty są następujących typów:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Co chcesz zrobić?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Dostarczanie poczty elektronicznej nie jest skonfigurowane, a powiadomienia są wyłączone.\nSkonfiguruj swój serwer SMTP, aby je włączyć." diff --git a/config/locales/crowdin/pt-BR.yml b/config/locales/crowdin/pt-BR.yml index 4d569b0080d..66dabdb19a8 100644 --- a/config/locales/crowdin/pt-BR.yml +++ b/config/locales/crowdin/pt-BR.yml @@ -1372,9 +1372,6 @@ pt-BR: dependencies: "Dependências" activerecord: attributes: - agile/sprint: - sharing: "Compartilhamento" - finish_date: "Data de término" jira_import: projects: "Projetos" "import/jira": @@ -3512,7 +3509,6 @@ pt-BR: label_float: "Ponto flutuante" label_folder: "Pasta" label_follows: "Segue" - label_force_user_language_to_default: "Configurar o idioma de usuário que tem uma linguagem não permitida como padrão" label_form_configuration: "Configuração do formulário" label_formula: "Fórmula" label_gantt_chart: "Gráfico de Gantt" @@ -5007,7 +5003,7 @@ pt-BR: text_default_administrator_account_changed: "Conta do administrador padrão alterada" text_default_encoding: "Padrão: UTF-8" text_destroy: "Excluir" - text_destroy_with_associated: "Existem objetos adicionais associados com o pacote de trabalho que serão excluídos. Esses objetos são dos seguintes tipos:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "O que você quer fazer?" text_diff_truncated: "... Este diff foi truncado porque excede o tamanho máximo que pode ser exibido." text_email_delivery_not_configured: "O envio de e-mails não está configurado e as notificações estão desativadas.\nConfigure seu servidor de SMTP para ativá-los." diff --git a/config/locales/crowdin/pt-PT.yml b/config/locales/crowdin/pt-PT.yml index 3e0cfb1f9c3..b2c08fb5418 100644 --- a/config/locales/crowdin/pt-PT.yml +++ b/config/locales/crowdin/pt-PT.yml @@ -1372,9 +1372,6 @@ pt-PT: dependencies: "Dependências" activerecord: attributes: - agile/sprint: - sharing: "Partilhar" - finish_date: "Data de término" jira_import: projects: "Projetos" "import/jira": @@ -3512,7 +3509,6 @@ pt-PT: label_float: "Float" label_folder: "Pasta" label_follows: "segue" - label_force_user_language_to_default: "Idioma de utilizadores definida com uma linguagem não permitida como padrão" label_form_configuration: "Configuração do formulário" label_formula: "Fórmula" label_gantt_chart: "Gráfico de Gantt" @@ -5005,7 +5001,7 @@ pt-PT: text_default_administrator_account_changed: "Conta padrão de administrador alterada" text_default_encoding: "Padrão: UTF-8" text_destroy: "Eliminar" - text_destroy_with_associated: "Existem objetos adicionais associados com o pacote de trabalho que está a ser apagado. Esses objetos são dos seguintes tipos:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "O que deseja fazer?" text_diff_truncated: "... Este diff foi truncado porque excede o tamanho máximo que pode ser mostrado." text_email_delivery_not_configured: "A entrega de emails não está configurada e as notificações estão desativadas.\nConfigure o seu servidor de SMTP para ativar." diff --git a/config/locales/crowdin/ro.yml b/config/locales/crowdin/ro.yml index 620c31bb3fb..3136130a27f 100644 --- a/config/locales/crowdin/ro.yml +++ b/config/locales/crowdin/ro.yml @@ -1390,9 +1390,6 @@ ro: dependencies: "Dependenţe" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3524,7 +3521,7 @@ ro: label_duplicated_by: "dublat de" label_duplicate: "duplicat" label_duplicates: "dublează" - label_edit: "Editează" + label_edit: "Editare" label_edit_x: "Editare: %{x}" label_view_x: "View: %{x}" label_enable_multi_select: "Comutare selecție multiplă" @@ -3571,7 +3568,6 @@ ro: label_float: "Număr real" label_folder: "Dosar" label_follows: "urmează după" - label_force_user_language_to_default: "Setare limbă pentru utilizatorii care au o limbă implicită nepermisă" label_form_configuration: "Configurare formular" label_formula: "Formulă" label_gantt_chart: "Grafic Gantt" @@ -3582,7 +3578,7 @@ ro: label_global_roles: "Roluri globale" label_git_path: "Calea catre directorul .git" label_greater_or_equal: ">=" - label_group_by: "Grupează după" + label_group_by: "Grupare după" label_group_new: "Grupare nouă" label_group: "Grup" label_group_named: "Grup %{name}" @@ -5068,7 +5064,7 @@ ro: text_default_administrator_account_changed: "Contul de administrator implicit a fost schimbat" text_default_encoding: "Implicit: UTF-8" text_destroy: "Șterge" - text_destroy_with_associated: "Există obiecte suplimentare asociate cu pachetul(ele) de lucru care urmează să fie șters(e). Aceste obiecte sunt de următoarele tipuri:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Ce vrei să faci?" text_diff_truncated: "... Acest diff a fost trunchiat deoarece depășește dimensiunea maximă care poate fi afișată." text_email_delivery_not_configured: "Livrarea e-mailurilor nu este configurată, iar notificările sunt dezactivate.\nConfigurați serverul SMTP pentru a le activa." diff --git a/config/locales/crowdin/ru.yml b/config/locales/crowdin/ru.yml index 8eb1852a72a..f5d29996295 100644 --- a/config/locales/crowdin/ru.yml +++ b/config/locales/crowdin/ru.yml @@ -1403,9 +1403,6 @@ ru: dependencies: "Связи" activerecord: attributes: - agile/sprint: - sharing: "Совместное использование" - finish_date: "Дата окончания" jira_import: projects: "Проекты" "import/jira": @@ -3625,7 +3622,6 @@ ru: label_float: "Плавающий" label_folder: "Папка" label_follows: "Следует" - label_force_user_language_to_default: "Язык, который пользователям нельзя установить как действующий по-умолчанию" label_form_configuration: "Настройка форм" label_formula: "Формула" label_gantt_chart: "Диаграмма Ганта" @@ -5122,7 +5118,7 @@ ru: text_default_administrator_account_changed: "Первоначальная учетная запись администратора была изменена" text_default_encoding: "По-умолчанию: UTF-8" text_destroy: "Удалить" - text_destroy_with_associated: "Есть дополнительные объекты, ассоцированные с пакетом(-ами) работ, которые должны быть удалены. Это объекты следующих типов:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Что Вы хотите сделать?" text_diff_truncated: "... Этот разностность(diff) была усечена, так как она превышает максимальный размер, которые может быть отображен." text_email_delivery_not_configured: "Доставка электронной почты не настроена, уведомления отключены.\nНастройте SMTP-сервер, чтобы включить их." diff --git a/config/locales/crowdin/rw.yml b/config/locales/crowdin/rw.yml index 23514321fd0..6e3ab78041c 100644 --- a/config/locales/crowdin/rw.yml +++ b/config/locales/crowdin/rw.yml @@ -1375,9 +1375,6 @@ rw: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ rw: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ rw: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/si.yml b/config/locales/crowdin/si.yml index 014261a0f42..4b4a440ab02 100644 --- a/config/locales/crowdin/si.yml +++ b/config/locales/crowdin/si.yml @@ -1375,9 +1375,6 @@ si: dependencies: "පරායත්තතා" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ si: label_float: "පාවෙන" label_folder: "ෆෝල්ඩරය" label_follows: "පහත සඳහන්" - label_force_user_language_to_default: "පෙරනිමියට අවසර නොලත් භාෂාවක් ඇති පරිශීලකයින්ගේ භාෂාව සකසන්න" label_form_configuration: "ආකෘති වින්යාසය" label_formula: "Formula" label_gantt_chart: "ගැන්ට් සටහන" @@ -5011,7 +5007,7 @@ si: text_default_administrator_account_changed: "පෙරනිමි පරිපාලක ගිණුම වෙනස්" text_default_encoding: "පෙරනිමි: UTF-8" text_destroy: "මකන්න" - text_destroy_with_associated: "මකා දැමිය යුතු වැඩ පැකේජය (ය) සමඟ එකතු වී ඇති අතිරේක වස්තු තිබේ. එම වස්තූන් පහත සඳහන් වර්ග වලින් සමන්විත වේ:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "ඔබට කුමක් කිරීමට අවශ්යද?" text_diff_truncated: "... එය ප්රදර්ශනය කළ හැකි උපරිම ප්රමාණය ඉක්මවා යන නිසා මෙම diff ටන්ක කරන ලදී." text_email_delivery_not_configured: "විද්යුත් තැපැල් බෙදා හැරීම වින්යාසගත කර නොමැති අතර දැනුම්දීම් අක්රීය කර ඇත.\nඒවා සක්රීය කිරීම සඳහා ඔබේ SMTP සේවාදායකය සකසන්න." diff --git a/config/locales/crowdin/sk.yml b/config/locales/crowdin/sk.yml index 212388ddcce..aac195608d9 100644 --- a/config/locales/crowdin/sk.yml +++ b/config/locales/crowdin/sk.yml @@ -1405,9 +1405,6 @@ sk: dependencies: "Závislosti" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3627,7 +3624,6 @@ sk: label_float: "Plávajúce (float)" label_folder: "Priečinok" label_follows: "nasleduje" - label_force_user_language_to_default: "Nastavenie jazyka užívateľov ktorí majú nastavený ako predvolený nepovolený jazyk" label_form_configuration: "Konfigurácia formulára" label_formula: "Formula" label_gantt_chart: "Ganttov diagram" @@ -5126,7 +5122,7 @@ sk: text_default_administrator_account_changed: "Predvolené nastavenie administrátorského účtu bolo zmenené" text_default_encoding: "Predvolené: UTF-8" text_destroy: "Odstrániť" - text_destroy_with_associated: "Existujú ďalšie objekty spojené s pracovným balíčkom(-ami), ktoré sa majú vymazať. Tieto objekty sú z týchto typov:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Čo chcete urobiť?" text_diff_truncated: "... Tento výpis rozdielov bol skrátený, pretože prekračuje maximálny počet riadkov, ktorý môže byť zobrazený." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/sl.yml b/config/locales/crowdin/sl.yml index 2c8f54f95a9..4cef90ee241 100644 --- a/config/locales/crowdin/sl.yml +++ b/config/locales/crowdin/sl.yml @@ -1404,9 +1404,6 @@ sl: dependencies: "Odvisnosti" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -2655,8 +2652,8 @@ sl: - "avgust" - "september" - "oktober" - - "november" - - "december" + - "November" + - "December" order: - :leto - :mesec @@ -3626,7 +3623,6 @@ sl: label_float: "Lebdeti" label_folder: "Mapa" label_follows: "Sledi" - label_force_user_language_to_default: "Nastavite jezik uporabnikov ki nimajo dovoljenja privzetega jezika" label_form_configuration: "Konfiguracija obrazca" label_formula: "Formula" label_gantt_chart: "Gantogram" @@ -5125,7 +5121,7 @@ sl: text_default_administrator_account_changed: "Spremenjen privzeti administratorski račun" text_default_encoding: "Privzeto: UTF-8" text_destroy: "Izbriši" - text_destroy_with_associated: "Obstajajo dodatni objekti, povezani z delovnim paketom, ki jih je treba izbrisati. Ti predmeti so naslednje vrste:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Kaj želite storiti?" text_diff_truncated: "... Ta sprememba je bila odsekana ker presega največjo velikost ki je lahko prikazana." text_email_delivery_not_configured: "Dostava e-pošte ni konfigurirana in obvestila so onemogočena.\nKonfigurirajte strežnik SMTP, da jih omogočite." diff --git a/config/locales/crowdin/sr.yml b/config/locales/crowdin/sr.yml index 9043c229fb3..08d703ac8b2 100644 --- a/config/locales/crowdin/sr.yml +++ b/config/locales/crowdin/sr.yml @@ -1390,9 +1390,6 @@ sr: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3571,7 +3568,6 @@ sr: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5069,7 +5065,7 @@ sr: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/sv.yml b/config/locales/crowdin/sv.yml index 85989c190f3..7f8fcc46f81 100644 --- a/config/locales/crowdin/sv.yml +++ b/config/locales/crowdin/sv.yml @@ -1375,9 +1375,6 @@ sv: dependencies: "Beroenden" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ sv: label_float: "Flyttal" label_folder: "Mapp" label_follows: "följer" - label_force_user_language_to_default: "Välj standardspråk för användare som har ett icke tillåtet språk" label_form_configuration: "Ställ in formulär" label_formula: "Formel" label_gantt_chart: "Gantt-schema" @@ -5009,7 +5005,7 @@ sv: text_default_administrator_account_changed: "Standardadministratörskontot har ändrats" text_default_encoding: "Standard: UTF-8" text_destroy: "Ta bort" - text_destroy_with_associated: "Det finns ytterligare objekt kopplade med det/de arbetspaket som ska tas bort. Dessa objekt är av följande typer:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Vad vill du göra?" text_diff_truncated: "... Denna diff trunkerades eftersom den överskrider den maximala storleken som kan visas." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/th.yml b/config/locales/crowdin/th.yml index 8ab60a56856..0e77bbd3055 100644 --- a/config/locales/crowdin/th.yml +++ b/config/locales/crowdin/th.yml @@ -1360,9 +1360,6 @@ th: dependencies: "ส่วนที่อ้างอิง" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3459,7 +3456,6 @@ th: label_float: "ลอย" label_folder: "Folder" label_follows: "ดังต่อไปนี้" - label_force_user_language_to_default: "พบภาษาที่ไม่อนุญาตให้ใช้เป็นค่าเริ่มต้น ในชุดภาษาของผู้ใช้" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -4953,7 +4949,7 @@ th: text_default_administrator_account_changed: "มีการเปลี่ยนค่าตั้งต้นของผู้ดูแลระบบ" text_default_encoding: "ค่าเริ่มต้น: UTF-8" text_destroy: "ลบ" - text_destroy_with_associated: "มีการลบออบเจคต์ที่เกี่ยวข้องกับชุดภารกิจ ซึ่งเป็นประเภทดังต่อไปนี้:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "คุณต้องการจะทำอะไร ?" text_diff_truncated: "... มีการตัดผลต่างเนื่องจากมีความยาวเกินกว่าที่สามารถแสดงผลได้" text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/tr.yml b/config/locales/crowdin/tr.yml index 5e6f44e32b7..4be1e4dc84f 100644 --- a/config/locales/crowdin/tr.yml +++ b/config/locales/crowdin/tr.yml @@ -1375,9 +1375,6 @@ tr: dependencies: "Bağımlılıklar" activerecord: attributes: - agile/sprint: - sharing: "Paylaşım" - finish_date: "Bitiş tarihi" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ tr: label_float: "Ondalık" label_folder: "Klasör" label_follows: "takip eder" - label_force_user_language_to_default: "Varsayılan olmayan izin verildi bir dil olan kullanıcı dilini ayarlama" label_form_configuration: "Formu yapılandırma" label_formula: "Formül" label_gantt_chart: "Gantt görünümü" @@ -5009,7 +5005,7 @@ tr: text_default_administrator_account_changed: "Varsayılan yönetici hesabı değişti" text_default_encoding: "Varsayılan: UTF-8" text_destroy: "Sil" - text_destroy_with_associated: "Silinecek iş paketleri ile birlikte ek nesne(ler) vardır. Bu nesneler aşağıdaki türdedir:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Ne yapmak istiyorsun?" text_diff_truncated: "... Bu fark, görüntülenebilecek maksimum boyutu aştığı için kesildi." text_email_delivery_not_configured: "E-posta teslimi yapılandırılmamış ve bildirimler devre dışı bırakılmış.\nSMTP sunucunuzu etkinleştirmek için yapılandırın." diff --git a/config/locales/crowdin/uk.yml b/config/locales/crowdin/uk.yml index dcf7caa2c5e..e23a2de4ebc 100644 --- a/config/locales/crowdin/uk.yml +++ b/config/locales/crowdin/uk.yml @@ -1399,9 +1399,6 @@ uk: dependencies: "Залежності" activerecord: attributes: - agile/sprint: - sharing: "Надання доступу" - finish_date: "Дата завершення" jira_import: projects: "Проєкти" "import/jira": @@ -3621,7 +3618,6 @@ uk: label_float: "З плаваючою крапкою" label_folder: "Папка" label_follows: "слідкувати" - label_force_user_language_to_default: "Встановити мову користувачів, які мають не підтримувану мову, за умовчанням" label_form_configuration: "Конфігурація форми" label_formula: "Формула" label_gantt_chart: "Діаграма Ґанта" @@ -3659,7 +3655,7 @@ uk: label_index_by_title: "Індекс за назвою" label_information: "Інформація" label_information_plural: "Інформація" - label_installation_guides: "Інструкції із встановлення" + label_installation_guides: "Інструкції зі встановлення" label_integer: "Ціле число" label_interface: "Інтерфейс" label_internal: "Власне" @@ -5119,7 +5115,7 @@ uk: text_default_administrator_account_changed: "Обліковий запис адміністратора за замовчуванням змінений" text_default_encoding: "По замовчуванню: UTF-8" text_destroy: "Видалити" - text_destroy_with_associated: "Існують додаткові об'єкти, асоційовані з робочими пакетами, які потрібно видалити. Ці об'єкти мають наступні типи:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Що ти хочеш зробити?" text_diff_truncated: "... Це порівняння (diff) було скорочене, оскільки воно перевищує максимальний розмір, який можна відобразити." text_email_delivery_not_configured: "Доставку електронної пошти не налаштовано, і сповіщення вимкнуто.\nНалаштуйте свій SMTP-сервер, щоб увімкнути їх." diff --git a/config/locales/crowdin/uz.yml b/config/locales/crowdin/uz.yml index f36a383028a..527805d7d3b 100644 --- a/config/locales/crowdin/uz.yml +++ b/config/locales/crowdin/uz.yml @@ -1375,9 +1375,6 @@ uz: dependencies: "Dependencies" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3515,7 +3512,6 @@ uz: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5011,7 +5007,7 @@ uz: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/locales/crowdin/vi.yml b/config/locales/crowdin/vi.yml index 80b5eee017d..618af7ac803 100644 --- a/config/locales/crowdin/vi.yml +++ b/config/locales/crowdin/vi.yml @@ -1358,9 +1358,6 @@ vi: dependencies: "phụ thuộc" activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -2232,7 +2229,7 @@ vi: body: "cơ thể" blocks_ids: "ID của các work package bị chặn" category: "thể loại" - comment: "bình luận" + comment: "Nhận xét" comments: "bình luận" content: "Nội dung" color: "màu sắc" @@ -3457,7 +3454,6 @@ vi: label_float: "Số thực" label_folder: "Thư mục" label_follows: "theo dõi" - label_force_user_language_to_default: "Có một ngôn ngữ không được cho phép để mặc định trong thiết lập ngôn ngữ cho người sử dụng" label_form_configuration: "Mẫu cấu hình" label_formula: "Công thức" label_gantt_chart: "Biểu đồ Gantt" @@ -3831,7 +3827,7 @@ vi: label_used_by: "Được dùng bởi" label_used_by_types: "Được sử dụng bởi các loại" label_used_in_projects: "Được sử dụng trong các dự án" - label_user: "người dùng" + label_user: "Người dùng" label_user_and_permission: "Người dùng và quyền" label_user_named: "Người dùng %{name}" label_user_activity: "%{value} hoạt động" @@ -4951,7 +4947,7 @@ vi: text_default_administrator_account_changed: "Tài khoản quản trị viên mặc định đã thay đổi" text_default_encoding: "Mặc định: UTF-8" text_destroy: "xóa" - text_destroy_with_associated: "Có các đối tượng bổ sung liên quan đến (các) gói công việc sẽ bị xóa. Các đối tượng đó có các loại sau:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "Bạn muốn làm gì?" text_diff_truncated: "... Sự khác biệt này đã bị cắt bớt vì nó vượt quá kích thước tối đa có thể được hiển thị." text_email_delivery_not_configured: "Gửi email không được cấu hình và thông báo bị vô hiệu hóa.\nCấu hình máy chủ SMTP của bạn để kích hoạt chúng." diff --git a/config/locales/crowdin/zh-CN.seeders.yml b/config/locales/crowdin/zh-CN.seeders.yml index 1ee133e5684..336ac4fa27d 100644 --- a/config/locales/crowdin/zh-CN.seeders.yml +++ b/config/locales/crowdin/zh-CN.seeders.yml @@ -97,7 +97,7 @@ zh-CN: demo-project: name: 演示项目 status_explanation: 所有任务都按计划进行。相关人员均知晓各自任务。系统已完全建立。 - description: 这是对此演示 Scrum 项目目标的简短摘要。 + description: 这是对此演示项目目标的简短摘要。 news: item_0: title: 欢迎来到您的演示项目 @@ -216,7 +216,7 @@ zh-CN: scrum-project: name: Scrum 项目 status_explanation: 所有任务都按计划进行。相关人员均知晓各自任务。系统已完全建立。 - description: 这是对此演示 Scrum 项目目标的简短摘要。 + description: 这是对此演示Scrum项目目标的简短摘要。 news: item_0: title: 欢迎来到您的 Scrum 演示项目 diff --git a/config/locales/crowdin/zh-CN.yml b/config/locales/crowdin/zh-CN.yml index a36bff007f5..cfd97f17da7 100644 --- a/config/locales/crowdin/zh-CN.yml +++ b/config/locales/crowdin/zh-CN.yml @@ -88,7 +88,7 @@ zh-CN: token_caption: "要详细了解如何激活企业版,请查阅我们的[文档](docs_url)。" add_token: "上传企业版支持令牌" replace_token: "替换您当前的支持令牌" - order: "订购本地部署版的 Enterprise edition" + order: "订购本地部署的 Enterprise edition" paste: "粘贴您企业版的支持令牌" required_for_feature: "此功能仅限具激活的企业版支持令牌的订阅者使用。" enterprise_link: "如需了解详细信息,请单击此处。" @@ -1356,9 +1356,6 @@ zh-CN: dependencies: "依赖项" activerecord: attributes: - agile/sprint: - sharing: "共享" - finish_date: "结束日期" jira_import: projects: "项目" "import/jira": @@ -1445,7 +1442,7 @@ zh-CN: page: "页" row_count: "行数" column_count: "列数" - widgets: "微件" + widgets: "小部件" journal: notes: "备注" cause_type: "Cause 类型" @@ -3455,7 +3452,6 @@ zh-CN: label_float: "浮点数" label_folder: "文件夹" label_follows: "跟踪" - label_force_user_language_to_default: "设置拥有非允许语言的用户的默认语言" label_form_configuration: "表格配置" label_formula: "公式" label_gantt_chart: "甘特图" @@ -3749,7 +3745,7 @@ zh-CN: label_revision_id: "修订版本 %{value}" label_revision_plural: "修订" label_roadmap: "路线图" - label_roadmap_edit: "编辑路线图%{name}" + label_roadmap_edit: "编辑路线图 %{name}" label_roadmap_due_in: "%{value} 到期" label_roadmap_no_work_packages: "该版本没有工作包。" label_roadmap_overdue: "%{value} 超时" @@ -4473,7 +4469,7 @@ zh-CN: managed: "在 OpenProject 中创建新的存储库" storage: not_available: "磁盘存储开销不可用于此存储库。" - update_timeout: "在 N 分钟内保留存储库最后所需的磁盘空间信息。由于计算存储库所需的磁盘空间可能增加系统开销,增加该值可以减少性能影响。" + update_timeout: "在 N 分钟内保留存储库最后所需磁盘空间的信息。由于计算存储库所需的磁盘空间可能增加系统开销,增加该值可以减少性能影响。" oauth_application_details: "关闭此窗口后,将无法再次访问客户端密钥值。请将这些值复制到 Nextcloud OpenProject 集成设置中:" oauth_application_details_link_text: "转到设置页面" setup_documentation_details: "如果您在配置新文件存储方面需要帮助,请查看文档:" @@ -4687,7 +4683,7 @@ zh-CN: setting_session_ttl_hint: "当设置的值低于5时,其作用类似于禁用。" setting_session_ttl_enabled: "会话过期" setting_start_of_week: "一周起始日" - setting_sys_api_enabled: "启用存储库管理网页服务" + setting_sys_api_enabled: "启用版本库管理 web 服务" setting_sys_api_description: "存储库管理网页服务提供了集成的,用户授权的存储库访问。" setting_time_format: "时间" setting_total_percent_complete_mode: "计算 完成% 层次结构总数" @@ -4944,7 +4940,7 @@ zh-CN: text_default_administrator_account_changed: "更改默认管理员帐户" text_default_encoding: "默认值: UTF-8" text_destroy: "删除" - text_destroy_with_associated: "有额外的对象关联到要删除的工作包。这些对象是以下类型:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "你想做什么?" text_diff_truncated: "...这个比较被截断,因为它超出了可显示的最大大小。" text_email_delivery_not_configured: "电子邮件传送未配置,通知已禁用。\n配置您的 SMTP 服务器将其启用。" @@ -5114,7 +5110,7 @@ zh-CN: warning_user_limit_reached_admin: > 添加额外的用户将超出当前限制。请升级您的计划,以确保外部用户能够访问此实例。 warning_user_limit_reached_instructions: > - 您已达到用户限制(%{current}/%{max} 活跃用户)。请联系 sales@openproject.com 升级您的企业版计划以添加额外用户。 + 您达到了用户限制(%{current}/%{max}活跃用户)。 请联系sales@openproject.com以升级您的Enterprise edition计划并添加其他用户。 warning_protocol_mismatch_html: > warning_bar: diff --git a/config/locales/crowdin/zh-TW.yml b/config/locales/crowdin/zh-TW.yml index 3d4cec6af91..225b8a20937 100644 --- a/config/locales/crowdin/zh-TW.yml +++ b/config/locales/crowdin/zh-TW.yml @@ -1356,9 +1356,6 @@ zh-TW: dependencies: "依賴套件" activerecord: attributes: - agile/sprint: - sharing: "分享" - finish_date: "結束日期" jira_import: projects: "Projects" "import/jira": @@ -3450,12 +3447,11 @@ zh-TW: label_filter_add: "新增條件" label_filter_by: "篩選條件:" label_filter_any_name_attribute: "名稱屬性" - label_filter_plural: "篩選條件" + label_filter_plural: "篩選器" label_filters_toggle: "顯示/隱藏篩選條件" label_float: "浮點數" label_folder: "資料夾" label_follows: "關注" - label_force_user_language_to_default: "設置使用者使用預設語言" label_form_configuration: "表單設置" label_formula: "公式" label_gantt_chart: "甘特圖" @@ -3465,8 +3461,8 @@ zh-TW: label_global_modules: "全域模組" label_global_roles: "全域角色" label_git_path: ".git 目錄的路徑" - label_greater_or_equal: "之前" - label_group_by: "分類" + label_greater_or_equal: ">=" + label_group_by: "分組依據" label_group_new: "新增群組" label_group: "群組" label_group_named: "群組名稱 %{name}" @@ -3478,7 +3474,7 @@ zh-TW: label_hierarchy: "階層" label_hierarchy_leaf: "頁面結構頁" label_home: "Home" - label_subject_or_id: "名稱或 id" + label_subject_or_id: "主旨或 id" label_calendar_subscriptions: "訂閱行事曆" label_identifier: "識別碼" label_in: "在" @@ -3529,7 +3525,7 @@ zh-TW: label_latest_revision_plural: "最新版本" label_ldap_authentication: "LDAP 認證" label_learn_more: "了解更多" - label_less_or_equal: "之後" + label_less_or_equal: "<=" label_less_than_ago: "幾天內" label_link_url: "連結(URL)" label_list: "清單" @@ -4947,7 +4943,7 @@ zh-TW: text_default_administrator_account_changed: "預設管理者帳號已更改" text_default_encoding: "預設值: UTF-8" text_destroy: "删除" - text_destroy_with_associated: "欲刪除的工作套件還關聯有其他物件,這些物件類型如下:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "您想要做什麼?" text_diff_truncated: "... 此差異被截斷, 因為它超出了可以顯示的最大大小。" text_email_delivery_not_configured: "電子郵件傳送未配置,通知已禁用。\n配置您的 SMTP 服務器將其啓用。" diff --git a/config/locales/en.yml b/config/locales/en.yml index 25c3e9d1ca0..8269bcee3a7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -292,7 +292,7 @@ en: confirm_button: "Start import" description: > This importer is an alpha feature. It is not yet able to import all data from Jira and might leave incomplete data on this OpenProject instance. - + Do not use a production environment and create a backup of your OpenProject data before starting. confirm: "I understand and made the necessary preparations" revert_dialog: @@ -1477,9 +1477,6 @@ en: activerecord: attributes: - agile/sprint: - sharing: "Sharing" - finish_date: "End date" jira_import: projects: "Projects" "import/jira": @@ -3674,7 +3671,6 @@ en: label_float: "Float" label_folder: "Folder" label_follows: "follows" - label_force_user_language_to_default: "Set language of users having a non allowed language to default" label_form_configuration: "Form configuration" label_formula: "Formula" label_gantt_chart: "Gantt chart" @@ -5292,7 +5288,7 @@ en: text_default_administrator_account_changed: "Default administrator account changed" text_default_encoding: "Default: UTF-8" text_destroy: "Delete" - text_destroy_with_associated: "There are additional objects assossociated with the work package(s) that are to be deleted. Those objects are of the following types:" + text_destroy_with_associated: "There are additional objects associated with the work package(s) that are to be deleted. Those objects are of the following types:" text_destroy_what_to_do: "What do you want to do?" text_diff_truncated: "... This diff was truncated because it exceeds the maximum size that can be displayed." text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server to enable them." diff --git a/config/puma.rb b/config/puma.rb index 91b7e7b442c..45237d89362 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -129,8 +129,7 @@ if Rails.env.development? end if siginfo_supported - # Using on_booted is needed to override puma adding handler to show thread statuses - on_booted do + after_booted do Signal.trap("INFO") do system "open", Rails.application.root_url end diff --git a/db/migrate/20260212145213_migrate_backlogs_permissions.rb b/db/migrate/20260212145213_migrate_backlogs_permissions.rb new file mode 100644 index 00000000000..f6e0fa4675a --- /dev/null +++ b/db/migrate/20260212145213_migrate_backlogs_permissions.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require Rails.root.join("db/migrate/migration_utils/permission_renamer") +require Rails.root.join("db/migrate/migration_utils/permission_adder") + +class MigrateBacklogsPermissions < ActiveRecord::Migration[8.1] + def up + ::Migration::MigrationUtils::PermissionRenamer.rename(:view_master_backlog, :view_sprints) + ::Migration::MigrationUtils::PermissionRenamer.rename(:view_taskboards, :view_sprints) + + ::Migration::MigrationUtils::PermissionAdder.add(:manage_versions, :create_sprints) + ::Migration::MigrationUtils::PermissionRenamer.rename(:update_sprints, :create_sprints) + + ::Migration::MigrationUtils::PermissionAdder.add(:assign_versions, :manage_sprint_items) + end + + def down + # Note: Ideally the `:view_taskboards`, `:view_master_backlog`, `:manage_versions`, + # `:update_sprints` permissions should be restored too, but unfortunately we cannot know + # which one lead to the user gaining `:view_sprints` or `:create_sprints` permissions. + # There are 2 possible solutions for this issue: + # 1. Grant both the `:view_taskboards`, `:view_master_backlog` where `:view_sprints` was granted. + # Respectively, grant `:manage_versions`, `:update_sprints` permissions where `:create_sprints` + # was granted. Unfortunately this leads to users gaining permissions they didn't possibly had + # before the migration. + # 2. Grant none of the undecisible permissions, which leads to users losing permissions they had + # before the migration. + # + # The conservative approach here is to pick #2, because it avoids accidentally leaking permissions + # to users. + + # Remove new permissions that were added during the up migration + RolePermission.delete_by(permission: %w(view_sprints create_sprints manage_sprint_items)) + end +end diff --git a/db/migrate/20260304160505_fix_sprint_role_dependencies.rb b/db/migrate/20260304160505_fix_sprint_role_dependencies.rb new file mode 100644 index 00000000000..a68994188c9 --- /dev/null +++ b/db/migrate/20260304160505_fix_sprint_role_dependencies.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require Rails.root.join("db/migrate/migration_utils/permission_adder") + +class FixSprintRoleDependencies < ActiveRecord::Migration[8.1] + def change + ::Migration::MigrationUtils::PermissionAdder.add(:manage_sprint_items, :view_sprints) + ::Migration::MigrationUtils::PermissionAdder.add(:create_sprints, :view_sprints) + ::Migration::MigrationUtils::PermissionAdder.add(:view_sprints, :view_work_packages) + end +end diff --git a/db/migrate/migration_utils/permission_renamer.rb b/db/migrate/migration_utils/permission_renamer.rb index 39642d2f1ee..4547eb4f385 100644 --- a/db/migrate/migration_utils/permission_renamer.rb +++ b/db/migrate/migration_utils/permission_renamer.rb @@ -28,26 +28,17 @@ # See COPYRIGHT and LICENSE files for more details. #++ +require Rails.root.join("db/migrate/migration_utils/permission_adder") + module Migration module MigrationUtils class PermissionRenamer class << self - def rename(from, to) - ActiveRecord::Base.connection.execute <<-SQL.squish - UPDATE #{role_permissions_table} - SET permission = #{quote_value(to)} - WHERE permission = #{quote_value(from)} - SQL - end - - private - - def role_permissions_table - @role_permissions_table ||= ActiveRecord::Base.connection.quote_table_name("role_permissions") - end - - def quote_value(value) - ActiveRecord::Base.connection.quote(value) + # The `force: true` argument is the default because usually when a rename + # happens, the `from`` permission is no longer defined. + def rename(from, to, force: true) + ::Migration::MigrationUtils::PermissionAdder.add(from, to, force:) + RolePermission.delete_by(permission: from) end end end diff --git a/docker/dev/backend/Dockerfile b/docker/dev/backend/Dockerfile index bc86675ea45..e2a04a60889 100644 --- a/docker/dev/backend/Dockerfile +++ b/docker/dev/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.4.7-trixie AS develop +FROM ruby:4.0.1-trixie AS develop LABEL org.opencontainers.image.authors="operations@openproject.com" ARG DEV_UID=1000 diff --git a/docker/dev/hocuspocus/docker-compose.yml b/docker/dev/hocuspocus/docker-compose.yml index 6ff6d5fbda9..bb1eade4c7e 100644 --- a/docker/dev/hocuspocus/docker-compose.yml +++ b/docker/dev/hocuspocus/docker-compose.yml @@ -2,7 +2,7 @@ services: hocuspocus: # In case of MacOS you need to specify the platform in your `docker/dev/hocuspocus/docker-compose.override.yml`: # platform: linux/amd64 - image: openproject/hocuspocus:latest + image: openproject/hocuspocus:dev-latest labels: - "traefik.enable=true" - "traefik.http.routers.hocuspocus.rule=Host(`hocuspocus.${OPENPROJECT_DOCKER_DEV_TLD:-local}`)" @@ -14,7 +14,6 @@ services: networks: - gateway environment: - - ALLOWED_DOMAINS=openproject.${OPENPROJECT_DOCKER_DEV_TLD:-local},localhost - NODE_TLS_REJECT_UNAUTHORIZED=0 - SECRET=secret12345 networks: diff --git a/docker/dev/tls/docker-compose.yml b/docker/dev/tls/docker-compose.yml index 12411a09859..a75a3773a94 100644 --- a/docker/dev/tls/docker-compose.yml +++ b/docker/dev/tls/docker-compose.yml @@ -17,6 +17,7 @@ services: - openproject.${OPENPROJECT_DOCKER_DEV_TLD:-local} - openproject-assets.${OPENPROJECT_DOCKER_DEV_TLD:-local} - nextcloud.${OPENPROJECT_DOCKER_DEV_TLD:-local} + - xwiki.${OPENPROJECT_DOCKER_DEV_TLD:-local} - gitlab.${OPENPROJECT_DOCKER_DEV_TLD:-local} - keycloak.${OPENPROJECT_DOCKER_DEV_TLD:-local} - hocuspocus.${OPENPROJECT_DOCKER_DEV_TLD:-local} diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 1653ea1c7e8..0700b31bd99 100755 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -1,4 +1,4 @@ -ARG RUBY_VERSION="3.4.7" +ARG RUBY_VERSION="4.0.1" ARG DEBIAN_BASE="trixie" # Add SBOM scan context for intermediate steps ARG BUILDKIT_SBOM_SCAN_CONTEXT=true diff --git a/docs/api/apiv3/components/examples/meeting_collection_simple_response.yml b/docs/api/apiv3/components/examples/meeting_collection_simple_response.yml new file mode 100644 index 00000000000..ff956f7e64c --- /dev/null +++ b/docs/api/apiv3/components/examples/meeting_collection_simple_response.yml @@ -0,0 +1,30 @@ +description: |- + A simple response of a meeting collection. +value: + _type: 'Collection' + total: 2 + count: 2 + pageSize: 20 + offset: 1 + _embedded: + _abbreviated: Embedded resources shortened for brevity + elements: + - _type: 'Meeting' + id: 6 + title: 'Weekly' + startTime: '2026-02-17T20:54:35.523Z' + endTime: '2026-02-17T21:54:35.523Z' + - _type: 'Meeting' + id: 5 + title: 'Weekly' + startTime: '2026-02-10T20:54:35.523Z' + endTime: '2026-02-10T21:54:35.523Z' + _links: + self: + href: '/api/v3/meetings?filters=%5B%5D&offset=1&pageSize=20' + jumpTo: + href: '/api/v3/meetings?filters=%5B%5D&offset=%7Boffset%7D&pageSize=20' + templated: true + changeSize: + href: '/api/v3/meetings?filters=%5B%5D&offset=1&pageSize=%7Bsize%7D' + templated: true diff --git a/docs/api/apiv3/components/examples/meeting_simple_response.yml b/docs/api/apiv3/components/examples/meeting_simple_response.yml new file mode 100644 index 00000000000..337071c803e --- /dev/null +++ b/docs/api/apiv3/components/examples/meeting_simple_response.yml @@ -0,0 +1,42 @@ +description: |- + A simple meeting response. +value: + _type: 'Meeting' + id: 1 + title: 'Meeting No 42' + location: 'Death Star - Level 1337 L13/42' + lockVersion: 13 + startTime: '2026-02-16T10:00:00.000Z' + endTime: '2026-02-16T11:00:00.000Z' + duration: 1.0 + createdAt: '2026-02-13T14:47:12.315Z' + updatedAt: '2026-02-13T14:47:12.315Z' + _links: + attachments: + href: '/api/v3/meetings/1/attachments' + addAttachment: + href: '/api/v3/meetings/1/attachments' + method: 'post' + self: + href: '/api/v3/meetings/1' + title: 'Meeting No 42' + author: + href: '/api/v3/users/4' + title: 'Darth Vader' + project: + href: '/api/v3/projects/10' + title: 'Death Star' + _embedded: + _abbreviated: Embedded resources shortened for brevity + attachments: + _type: Collection + total: 2 + count: 2 + author: + _type: User + name: Darth Vader + id: 4 + project: + _type: Project + name: Death Star + id: 10 diff --git a/docs/api/apiv3/components/examples/membership-simple-response.yml b/docs/api/apiv3/components/examples/membership_simple_response.yml similarity index 100% rename from docs/api/apiv3/components/examples/membership-simple-response.yml rename to docs/api/apiv3/components/examples/membership_simple_response.yml diff --git a/docs/api/apiv3/components/examples/membership-update-additional-roles.yml b/docs/api/apiv3/components/examples/membership_update_additional_roles.yml similarity index 100% rename from docs/api/apiv3/components/examples/membership-update-additional-roles.yml rename to docs/api/apiv3/components/examples/membership_update_additional_roles.yml diff --git a/docs/api/apiv3/components/schemas/meeting_collection_model.yml b/docs/api/apiv3/components/schemas/meeting_collection_model.yml new file mode 100644 index 00000000000..ab2ec48781a --- /dev/null +++ b/docs/api/apiv3/components/schemas/meeting_collection_model.yml @@ -0,0 +1,30 @@ +# Schema: MeetingCollectionModel +--- +allOf: + - $ref: './paginated_collection_model.yml' + - type: object + required: + - _embedded + - _links + properties: + _embedded: + type: object + required: + - elements + properties: + elements: + type: array + items: + $ref: './meeting_model.yml' + _links: + type: object + required: + - self + properties: + self: + allOf: + - $ref: './link.yml' + - description: |- + The link to this meeting collection resource + + **Resource**: MeetingCollection diff --git a/docs/api/apiv3/components/schemas/meeting_model.yml b/docs/api/apiv3/components/schemas/meeting_model.yml index 37e0f4c6827..0b518a1d20e 100644 --- a/docs/api/apiv3/components/schemas/meeting_model.yml +++ b/docs/api/apiv3/components/schemas/meeting_model.yml @@ -1,7 +1,8 @@ -# Schema: Meeting_PageModel +# Schema: MeetingModel --- type: object required: + - _type - id - lockVersion - title @@ -11,11 +12,14 @@ required: - createdAt - updatedAt properties: + _type: + type: string + enum: + - Meeting id: type: integer description: Identifier of this meeting - readOnly: true - exclusiveMinimum: 0 + minimum: 1 title: type: string description: The meeting's title @@ -25,7 +29,6 @@ properties: lockVersion: type: integer description: The version of the item as used for optimistic locking - readOnly: true startTime: type: string format: date-time @@ -36,39 +39,47 @@ properties: description: The scheduled meeting start time. duration: type: number - description: The meeting duration in minutes. + description: The meeting duration in hours. createdAt: type: string format: date-time description: Time of creation. Can be writable by admins with the `apiv3_write_readonly_attributes` setting enabled. - readOnly: true updatedAt: type: string format: date-time description: Time of the most recent change to the meeting. - readOnly: true + _embedded: + type: object + required: + - attachments + - author + properties: + attachments: + $ref: './attachments_model.yml' + author: + $ref: './user_model.yml' + project: + $ref: './project_model.yml' _links: type: object properties: self: allOf: - - "$ref": "./link.yml" + - $ref: './link.yml' - description: |- This meeting **Resource**: Meeting - readOnly: true author: allOf: - - "$ref": "./link.yml" + - $ref: './link.yml' - description: |- The user having created the meeting **Resource**: User - readOnly: true project: allOf: - - "$ref": "./link.yml" + - $ref: './link.yml' - description: |- The project the meeting is in @@ -82,42 +93,10 @@ properties: **Resource**: AttachmentCollection addAttachment: allOf: - - "$ref": "./link.yml" + - $ref: './link.yml' - description: |- Attach a file to the meeting # Conditions **Permission**: edit meeting - readOnly: true -example: - _type: Meeting - id: 72 - lockVersion: 5 - title: A meeting - startTime: '2014-05-21T08:00:00.000Z' - endTime: '2014-05-21T10:00:00.000Z' - duration: 120 - createdAt: '2014-05-21T08:51:20.396Z' - updatedAt: '2014-05-21T09:14:02.776Z' - _embedded: - project: - _type: Project... - id: 12 - author: - _type: User... - id: 2 - _links: - addAttachment: - href: "/api/v3/meetings/72/attachments" - method: post - attachments: - href: "/api/v3/meetings/72/attachments" - project: - href: "/api/v3/projects/12" - title: some project - author: - href: "/api/v3/users/2" - title: Peggie Feeney - self: - href: "/api/v3/meetings/72" diff --git a/docs/api/apiv3/openapi-spec.yml b/docs/api/apiv3/openapi-spec.yml index 0ec085c9124..91d141331ab 100644 --- a/docs/api/apiv3/openapi-spec.yml +++ b/docs/api/apiv3/openapi-spec.yml @@ -266,6 +266,8 @@ paths: "$ref": "./paths/help_texts.yml" "/api/v3/help_texts/{id}": "$ref": "./paths/help_text.yml" + "/api/v3/meetings": + "$ref": "./paths/meetings.yml" "/api/v3/meetings/{id}": "$ref": "./paths/meeting.yml" "/api/v3/meetings/{id}/attachments": @@ -589,6 +591,10 @@ components: $ref: "./components/examples/hierarchy_item_collection_response.yml" HierarchyItemResponse: $ref: "./components/examples/hierarchy_item_response.yml" + MeetingCollectionSimpleResponse: + $ref: "./components/examples/meeting_collection_simple_response.yml" + MeetingSimpleResponse: + $ref: "./components/examples/meeting_simple_response.yml" MembershipCreateRequestCustomMessage: $ref: "./components/examples/membership-create-request-custom-message.yml" MembershipCreateRequestGlobalRole: @@ -602,9 +608,9 @@ components: MembershipSimpleCollectionResponse: $ref: "./components/examples/membership-simple-collection-response.yml" MembershipSimpleResponse: - $ref: "./components/examples/membership-simple-response.yml" + $ref: "./components/examples/membership_simple_response.yml" MembershipUpdateAdditionalRoles: - $ref: "./components/examples/membership-update-additional-roles.yml" + $ref: "./components/examples/membership_update_additional_roles.yml" MentionedNotification: $ref: "./components/examples/mentioned_notification.yml" NotificationCollection: @@ -827,6 +833,8 @@ components: "$ref": "./components/schemas/list_of_news_model.yml" List_workspaces_by_versionModel: "$ref": "./components/schemas/list_workspaces_by_version_model.yml" + MeetingCollectionModel: + "$ref": "./components/schemas/meeting_collection_model.yml" MeetingModel: "$ref": "./components/schemas/meeting_model.yml" MarkdownModel: diff --git a/docs/api/apiv3/paths/meeting.yml b/docs/api/apiv3/paths/meeting.yml index fc052659abc..e0ec3ef46f9 100644 --- a/docs/api/apiv3/paths/meeting.yml +++ b/docs/api/apiv3/paths/meeting.yml @@ -1,9 +1,14 @@ # /api/v3/meetings/{id} --- get: + summary: Get a meeting + operationId: get_meeting + tags: + - Meetings + description: Retrieve an individual meeting as identified by the id parameter parameters: - description: Meeting identifier - example: '1' + example: 1 in: path name: id required: true @@ -11,44 +16,14 @@ get: type: integer responses: '200': + description: OK content: application/hal+json: examples: response: - value: - _embedded: - project: - _type: Project... - id: 12 - author: - _type: User... - id: 2 - _links: - addAttachment: - href: "/api/v3/meetings/72/attachments" - method: post - attachments: - href: "/api/v3/meetings/72/attachments" - project: - href: "/api/v3/projects/12" - title: some project - author: - href: "/api/v3/users/2" - title: Peggie Feeney - self: - href: "/api/v3/meetings/72" - _type: Meeting - id: 72 - lockVersion: 5 - title: A meeting - startTime: '2014-05-21T08:00:00.000Z' - duration: 120 - createdAt: '2014-05-21T08:51:20.396Z' - updatedAt: '2014-05-21T09:14:02.776Z' + $ref: "../components/examples/meeting_simple_response.yml" schema: - "$ref": "../components/schemas/meeting_model.yml" - description: OK - headers: { } + $ref: "../components/schemas/meeting_model.yml" '404': content: application/hal+json: @@ -64,9 +39,3 @@ get: Returned if the meeting does not exist or the client does not have sufficient permissions to see it. **Required permission:** view meetings in the page's project - headers: { } - tags: - - Meetings - description: Retrieve an individual meeting as identified by the id parameter - operationId: View_Meeting - summary: View Meeting Page diff --git a/docs/api/apiv3/paths/meeting_attachments.yml b/docs/api/apiv3/paths/meeting_attachments.yml index 200d9f58528..cdceb3c141a 100644 --- a/docs/api/apiv3/paths/meeting_attachments.yml +++ b/docs/api/apiv3/paths/meeting_attachments.yml @@ -3,7 +3,7 @@ get: parameters: - description: ID of the meeting whose attachments will be listed - example: '1' + example: 1 in: path name: id required: true @@ -84,7 +84,7 @@ get: post: parameters: - description: ID of the meeting to receive the attachment - example: '1' + example: 1 in: path name: id required: true diff --git a/docs/api/apiv3/paths/meetings.yml b/docs/api/apiv3/paths/meetings.yml new file mode 100644 index 00000000000..8550f7ebabe --- /dev/null +++ b/docs/api/apiv3/paths/meetings.yml @@ -0,0 +1,19 @@ +# /api/v3/meetings +--- +get: + summary: List all visible meetings + operationId: list_meetings + tags: + - Meetings + description: |- + Retrieve a paginated collection of meetings visible to the authenticated user. + responses: + '200': + description: OK + content: + application/hal+json: + examples: + response: + $ref: "../components/examples/meeting_collection_simple_response.yml" + schema: + $ref: "../components/schemas/meeting_collection_model.yml" diff --git a/docs/api/apiv3/paths/membership.yml b/docs/api/apiv3/paths/membership.yml index eff60e35dd0..ffc8c2a9dc1 100644 --- a/docs/api/apiv3/paths/membership.yml +++ b/docs/api/apiv3/paths/membership.yml @@ -75,7 +75,7 @@ get: $ref: '../components/schemas/membership_read_model.yml' examples: 'simple membership': - $ref: '../components/examples/membership-simple-response.yml' + $ref: '../components/examples/membership_simple_response.yml' '404': content: application/hal+json: @@ -121,7 +121,7 @@ patch: $ref: '../components/schemas/membership_write_model.yml' examples: 'add roles': - $ref: '../components/examples/membership-update-additional-roles.yml' + $ref: '../components/examples/membership_update_additional_roles.yml' responses: '200': description: OK @@ -131,7 +131,7 @@ patch: $ref: '../components/schemas/membership_read_model.yml' examples: 'simple membership': - $ref: '../components/examples/membership-simple-response.yml' + $ref: '../components/examples/membership_simple_response.yml' '400': $ref: '../components/responses/invalid_request_body.yml' '403': diff --git a/docs/api/apiv3/paths/membership_form.yml b/docs/api/apiv3/paths/membership_form.yml index eb9914e3652..0e242091969 100644 --- a/docs/api/apiv3/paths/membership_form.yml +++ b/docs/api/apiv3/paths/membership_form.yml @@ -35,7 +35,7 @@ post: $ref: '../components/schemas/membership_read_model.yml' examples: 'simple membership': - $ref: '../components/examples/membership-simple-response.yml' + $ref: '../components/examples/membership_simple_response.yml' '400': $ref: '../components/responses/invalid_request_body.yml' '403': diff --git a/docs/api/apiv3/paths/memberships.yml b/docs/api/apiv3/paths/memberships.yml index 4fa8a207c20..e1aa67e29d2 100644 --- a/docs/api/apiv3/paths/memberships.yml +++ b/docs/api/apiv3/paths/memberships.yml @@ -124,7 +124,7 @@ post: $ref: '../components/schemas/membership_read_model.yml' examples: 'simple membership': - $ref: '../components/examples/membership-simple-response.yml' + $ref: '../components/examples/membership_simple_response.yml' '400': $ref: '../components/responses/invalid_request_body.yml' '403': diff --git a/docs/development/development-environment/linux/README.md b/docs/development/development-environment/linux/README.md index 6c222b0c829..223026715eb 100644 --- a/docs/development/development-environment/linux/README.md +++ b/docs/development/development-environment/linux/README.md @@ -40,10 +40,10 @@ sudo apt-get install git curl build-essential zlib1g-dev libyaml-dev libssl-dev Use [rbenv](https://github.com/rbenv/rbenv) and [ruby-build](https://github.com/rbenv/ruby-build#readme) to install Ruby. You can check available ruby versions with `rbenv install --list`. -At the time of this writing, the latest stable version is `3.4.7`, which we also require. +At the time of this writing, the latest stable version is `4.0.1`, which we also require. We suggest you install the version we require in [.ruby-version](https://github.com/opf/openproject/blob/dev/.ruby-version). -Read the first line e.g. `3.4.7` and install that version. +Read the first line e.g. `4.0.1` and install that version. #### Install rbenv and ruby-build @@ -80,18 +80,18 @@ With both installed, we can now install ruby. You can check available ruby versions with `rbenv install --list`. We suggest you install the version we require in [.ruby-version](https://github.com/opf/openproject/blob/dev/.ruby-version). -Read the first line e.g. `3.4.7` and install that version. +Read the first line e.g. `4.0.1` and install that version. ```shell # Install the required version as read from the .ruby-version file -rbenv install 3.4.7 +rbenv install 4.0.1 ``` This might take a while depending on whether ruby is built from source. After it is complete, you need to tell rbenv to globally activate this version ```shell -rbenv global 3.4.7 +rbenv global 4.0.1 rbenv rehash ``` @@ -181,10 +181,10 @@ You should now have an active ruby and node installation. Verify that it works w ```shell ruby --version -ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [arm64-darwin25] +ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [arm64-darwin25] bundler --version -Bundler version 2.7.2 +4.0.3 node --version v22.21.0 diff --git a/docs/development/development-environment/macos/README.md b/docs/development/development-environment/macos/README.md index e1f2e45519c..ba6694a3e7e 100644 --- a/docs/development/development-environment/macos/README.md +++ b/docs/development/development-environment/macos/README.md @@ -28,7 +28,7 @@ their homepage. Use [rbenv](https://github.com/rbenv/rbenv) and [ruby-build](https://github.com/rbenv/ruby-build#readme) to install Ruby. We always require the latest ruby versions, and you can check which version is required by [checking the Gemfile](https://github.com/opf/openproject/blob/dev/Gemfile#L31) for the `ruby "~> X.Y"` statement. At -the time of writing, this version is "3.4.7" +the time of writing, this version is "4.0.1" #### Install rbenv and ruby-build @@ -48,18 +48,18 @@ With both installed, we can now install the actual ruby version. You can check available ruby versions with `rbenv install --list`. We suggest you install the version we require in [.ruby-version](https://github.com/opf/openproject/blob/dev/.ruby-version). -Read the first line e.g. `3.4.7` and install that version. +Read the first line e.g. `4.0.1` and install that version. ```shell # Install the required version as read from the .ruby-version file -rbenv install 3.4.7 +rbenv install 4.0.1 ``` This might take a while depending on whether ruby is built from source. After it is complete, you need to tell rbenv to globally activate this version ```shell -rbenv global 3.4.7 +rbenv global 4.0.1 ``` You also need to install [bundler](https://github.com/bundler/bundler/), the ruby gem bundler. @@ -134,10 +134,10 @@ You should now have an active ruby and node installation. Verify that it works w ```shell $ ruby --version -ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [arm64-darwin25]4] +ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [arm64-darwin25] $ bundler --version -Bundler version 2.7.2 +4.0.3 node --version v22.21.0 diff --git a/docs/development/testing/handling-flaky-tests/README.md b/docs/development/testing/handling-flaky-tests/README.md index fe23041249b..e4332bb5224 100644 --- a/docs/development/testing/handling-flaky-tests/README.md +++ b/docs/development/testing/handling-flaky-tests/README.md @@ -21,6 +21,10 @@ Developers notice a failing spec in CI runs related to the PR they are working o The failing spec is suspicious as it seems unrelated to the changes introduced by the commits. +Out-of-hours correlation is a lead, not proof of a datetime bug. Evening or weekend failures can still be caused by +ordinary flakiness, branch-specific regressions, or infrastructure issues. Start by separating build/setup failures from +actual `Unit tests` or `Feature tests`, then look for recurring spec names before concluding that time-sensitive logic is involved. + To get the failing spec names, use `script/github_pr_errors` and give it the URL of the failing run as argument, for example: ```bash @@ -29,6 +33,16 @@ script/github_pr_errors https://github.com/opf/openproject/actions/runs/18215876 There are options to display images or display advice to reproduce the failures. Use `--help` to know more. +To aggregate recent `Test suite` failures and highlight specs that skew outside 09:00-18:00 Europe/Berlin Monday to Friday, +use: + +```bash +export GITHUB_TOKEN=... +script/report_out_of_hours_ci_failures --days 30 +``` + +The report focuses on `dev` and `release/*` runs by default and excludes failures that never reached the unit or feature test steps. + ## Confirming the spec is flaky To confirm the flakiness of the spec, either: diff --git a/docs/installation-and-operations/installation/manual/README.md b/docs/installation-and-operations/installation/manual/README.md index 0e16df33786..2092086ffbf 100644 --- a/docs/installation-and-operations/installation/manual/README.md +++ b/docs/installation-and-operations/installation/manual/README.md @@ -106,19 +106,19 @@ Please be aware that the actual installation of a specific Ruby version takes so ``` We suggest you install the version we require in [.ruby-version](https://github.com/opf/openproject/blob/dev/.ruby-version). -Read the first line e.g. `3.4.7` and install that version. +Read the first line e.g. `4.0.1` and install that version. ```shell -[openproject@host] rbenv install 3.4.7 +[openproject@host] rbenv install 4.0.1 [openproject@host] rbenv rehash -[openproject@host] rbenv global 3.4.7 +[openproject@host] rbenv global 4.0.1 ``` To check our Ruby installation we run `ruby --version`. It should output something very similar to: ```text -ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [arm64-darwin25] +ruby 4.0.1 (2026-01-13 revision e04267a14b) +PRISM [arm64-darwin25] ``` ## Installation of Node diff --git a/extensions/op-blocknote-hocuspocus/package-lock.json b/extensions/op-blocknote-hocuspocus/package-lock.json index a5e1eb211ae..30261379131 100644 --- a/extensions/op-blocknote-hocuspocus/package-lock.json +++ b/extensions/op-blocknote-hocuspocus/package-lock.json @@ -263,7 +263,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -286,7 +285,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -947,7 +945,6 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", - "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -1297,9 +1294,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], @@ -1311,9 +1308,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], @@ -1325,9 +1322,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], @@ -1339,9 +1336,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], @@ -1353,9 +1350,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], @@ -1367,9 +1364,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], @@ -1381,9 +1378,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], @@ -1395,9 +1392,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], @@ -1409,9 +1406,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], @@ -1423,9 +1420,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], @@ -1437,9 +1434,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], @@ -1451,9 +1462,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], @@ -1465,9 +1490,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], @@ -1479,9 +1504,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], @@ -1493,9 +1518,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], @@ -1507,9 +1532,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], @@ -1521,9 +1546,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], @@ -1534,10 +1559,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], @@ -1549,9 +1588,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], @@ -1563,9 +1602,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], @@ -1577,9 +1616,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], @@ -1591,9 +1630,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], @@ -1609,7 +1648,6 @@ "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", "license": "MIT", - "peer": true, "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" @@ -1682,7 +1720,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.13.0.tgz", "integrity": "sha512-iUelgiTMgPVMpY5ZqASUpk8mC8HuR9FWKaDzK27w9oWip9tuB54Z8mePTxNcQaSPb6ErzEaC8x8egrRt7OsdGQ==", "license": "MIT", - "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" @@ -1880,7 +1917,6 @@ "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.13.0.tgz", "integrity": "sha512-WKR4ucALq+lwx0WJZW17CspeTpXorbIOpvKv5mulZica6QxqfMhn8n1IXCkDws/mCoLRx4Drk5d377tIjFNsvQ==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", @@ -2026,7 +2062,6 @@ "integrity": "sha512-gWEkeiyYE4vqjON/+Obqcoeffmk0NF15WSBwSs7zwVA2bAbTaE0SJ7P0WNGoJn8uE7fiaV5a7dKYIJriEqOrmA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2122,7 +2157,6 @@ "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.48.1", "@typescript-eslint/types": "8.48.1", @@ -2277,13 +2311,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2457,7 +2491,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2739,6 +2772,7 @@ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -2870,7 +2904,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/data-urls": { "version": "5.0.0", @@ -2950,7 +2985,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/devlop": { "version": "1.1.0", @@ -3126,7 +3162,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3468,6 +3503,7 @@ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -3961,7 +3997,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -5150,9 +5185,9 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5688,7 +5723,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", "license": "MIT", - "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -5718,7 +5752,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -5767,7 +5800,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.3.tgz", "integrity": "sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -5856,6 +5888,7 @@ "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz", "integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -5866,6 +5899,7 @@ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", + "peer": true, "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", @@ -5891,6 +5925,7 @@ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", + "peer": true, "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" @@ -5913,6 +5948,7 @@ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", + "peer": true, "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" @@ -5935,6 +5971,7 @@ "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", @@ -6125,9 +6162,9 @@ "license": "MIT" }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "license": "MIT", "dependencies": { @@ -6141,28 +6178,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -6200,7 +6240,8 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/semver": { "version": "7.7.3", @@ -7082,6 +7123,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=16" }, @@ -7267,6 +7309,7 @@ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.0.0" }, @@ -7288,6 +7331,7 @@ "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -7302,6 +7346,7 @@ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -7316,6 +7361,7 @@ "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", "license": "MIT", + "peer": true, "dependencies": { "use-isomorphic-layout-effect": "^1.1.1" }, @@ -7333,6 +7379,7 @@ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", + "peer": true, "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" @@ -7416,7 +7463,6 @@ "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -7796,7 +7842,6 @@ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz", "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==", "license": "MIT", - "peer": true, "dependencies": { "lib0": "^0.2.85" }, @@ -7856,7 +7901,6 @@ "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", "integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==", "license": "MIT", - "peer": true, "dependencies": { "lib0": "^0.2.99" }, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3230c92ec7d..1616e8a14ba 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,18 +9,18 @@ "version": "0.1.0", "license": "GPLv3", "dependencies": { - "@angular/animations": "^21.1.5", - "@angular/cdk": "^21.1.5", - "@angular/cli": "^21.1.4", - "@angular/common": "^21.1.5", - "@angular/compiler": "^21.1.5", - "@angular/compiler-cli": "^21.1.5", - "@angular/core": "^21.1.5", - "@angular/elements": "^21.1.5", - "@angular/forms": "^21.1.5", - "@angular/platform-browser": "^21.1.5", - "@angular/platform-browser-dynamic": "^21.1.5", - "@angular/router": "^21.1.5", + "@angular/animations": "^21.1.6", + "@angular/cdk": "^21.1.6", + "@angular/cli": "^21.1.5", + "@angular/common": "^21.1.6", + "@angular/compiler": "^21.1.6", + "@angular/compiler-cli": "^21.1.6", + "@angular/core": "^21.1.6", + "@angular/elements": "^21.1.6", + "@angular/forms": "^21.1.6", + "@angular/platform-browser": "^21.1.6", + "@angular/platform-browser-dynamic": "^21.1.6", + "@angular/router": "^21.1.6", "@appsignal/javascript": "^1.6.1", "@appsignal/plugin-breadcrumbs-console": "^1.1.37", "@appsignal/plugin-breadcrumbs-network": "^1.1.24", @@ -65,14 +65,14 @@ "@rails/request.js": "^0.0.13", "@stimulus-components/auto-submit": "^6.0.0", "@stimulus-components/reveal": "^5.0.0", - "@tiptap/extensions": "^3.13.0", + "@tiptap/extensions": "^3.20.0", "@types/jquery.cookie": "^1.4.36", "@uirouter/angular": "^17.0.0", "@uirouter/core": "^6.1.0", "@uirouter/rx": "^1.0.0", "@w11k/ngx-componentdestroyed": "^5.0.2", "@xeokit/xeokit-bim-viewer": "2.7.1", - "autoprefixer": "^10.4.23", + "autoprefixer": "^10.4.27", "byte-base64": "^1.1.0", "chart.js": "4.5.1", "chartjs-adapter-luxon": "^1.3.1", @@ -130,16 +130,16 @@ }, "devDependencies": { "@angular-builders/custom-esbuild": "^21.0.3", - "@angular-devkit/build-angular": "^21.1.4", + "@angular-devkit/build-angular": "^21.1.5", "@angular-eslint/builder": "20.7.0", "@angular-eslint/eslint-plugin": "20.7.0", "@angular-eslint/eslint-plugin-template": "20.7.0", "@angular-eslint/schematics": "20.7.0", "@angular-eslint/template-parser": "20.7.0", - "@angular/language-service": "21.1.5", + "@angular/language-service": "21.1.6", "@eslint/js": "^9.39.2", - "@html-eslint/eslint-plugin": "^0.54.2", - "@html-eslint/parser": "^0.54.0", + "@html-eslint/eslint-plugin": "^0.57.0", + "@html-eslint/parser": "^0.57.0", "@jsdevtools/coverage-istanbul-loader": "3.0.5", "@stylistic/eslint-plugin": "^5.7.1", "@types/codemirror": "5.60.5", @@ -155,7 +155,7 @@ "@types/mousetrap": "^1.6.3", "@types/pako": "^2.0.4", "@types/rails__request.js": "^0.0.1", - "@types/react": "^19.2.10", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/resize-observer-browser": "^0.1.4", "@types/urijs": "^1.19.26", @@ -648,16 +648,16 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.4.tgz", - "integrity": "sha512-2HPCo6vEu5EIwxxFYhnmdfbktRBoOVQD3q7lG9PMQPf/jRCnyIZ70qSbXbAV96IMDLFl8mLRfY4scoaFMIYGMw==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.5.tgz", + "integrity": "sha512-B2jOBAiVl+hA3PLwpxfrbW/gA7SDu9Uv+hQwHYrdwL2XXDVwaQ+c3z9BS3yJDQTkb/TrAJ0sfa2zVLC4b/rHzg==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.4", - "@angular-devkit/build-webpack": "0.2101.4", - "@angular-devkit/core": "21.1.4", - "@angular/build": "21.1.4", + "@angular-devkit/architect": "0.2101.5", + "@angular-devkit/build-webpack": "0.2101.5", + "@angular-devkit/core": "21.1.5", + "@angular/build": "21.1.5", "@babel/core": "7.28.5", "@babel/generator": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", @@ -668,7 +668,7 @@ "@babel/preset-env": "7.28.5", "@babel/runtime": "7.28.4", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "21.1.4", + "@ngtools/webpack": "21.1.5", "ansi-colors": "4.1.3", "autoprefixer": "10.4.23", "babel-loader": "10.0.0", @@ -723,7 +723,7 @@ "@angular/platform-browser": "^21.0.0", "@angular/platform-server": "^21.0.0", "@angular/service-worker": "^21.0.0", - "@angular/ssr": "^21.1.4", + "@angular/ssr": "^21.1.5", "@web/test-runner": "^0.20.0", "browser-sync": "^3.0.2", "jest": "^30.2.0", @@ -780,12 +780,12 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.4.tgz", - "integrity": "sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "rxjs": "7.8.2" }, "bin": { @@ -798,12 +798,12 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "dev": true, "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -833,6 +833,22 @@ "node": ">=14.17.0" } }, + "node_modules/@angular-devkit/build-angular/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -863,6 +879,42 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, + "node_modules/@angular-devkit/build-angular/node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -1046,12 +1098,12 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.4.tgz", - "integrity": "sha512-lPjPxeEzUha4bnlGzD3KFFf3yxcQjOfV9wwZIa4XLsqjCZsUk95TzHQH7i64OCTw9uKTEQkJBAuO6v2WXHxopw==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.5.tgz", + "integrity": "sha512-G3mvUXiSU3DL1QKngq/yXT94Wr+IdqtOM/1VC3NmsV9KX3OSfwfc560dmhY1efqc9gBA5qL+7kLlgV7Kx/Su3A==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.2101.4", + "@angular-devkit/architect": "0.2101.5", "rxjs": "7.8.2" }, "engines": { @@ -1065,12 +1117,12 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.4.tgz", - "integrity": "sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "rxjs": "7.8.2" }, "bin": { @@ -1083,12 +1135,12 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "dev": true, "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -1109,6 +1161,22 @@ } } }, + "node_modules/@angular-devkit/build-webpack/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@angular-devkit/build-webpack/node_modules/ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -1373,9 +1441,9 @@ "license": "MIT" }, "node_modules/@angular/animations": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.5.tgz", - "integrity": "sha512-gsqHX8lCYV8cgVtHs0iLwrX8SVlmcjUF44l/xCc/jBC/TeKWRl2e6Jqrn1Wcd0NDlGiNsm+mYNyqMyy5/I7kjw==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.6.tgz", + "integrity": "sha512-Ft8B0tHBRyd7ORpbaa2S0yRqHSdsyMH6oot7ODAA3kv03k4GTwp74U6Y/NyV4JoZ+lr28+EV4YMv3mAqJwIy/g==", "dependencies": { "tslib": "^2.3.0" }, @@ -1383,17 +1451,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.5" + "@angular/core": "21.1.6" } }, "node_modules/@angular/build": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.4.tgz", - "integrity": "sha512-7CAAQPWFMMqod40ox5MOVB/CnoBXFDehyQhs0hls6lu7bOy/M0EDy0v6bERkyNGRz1mihWWBiCV8XzEinrlq1A==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.5.tgz", + "integrity": "sha512-v2eDinWKlSKuk5pyMMY8j5TMFW8HA9B1l13TrDDpxsRGAAzekg7TFNyuh1x9Y6Rq4Vn+8/8pCjMUPZigzWbMhQ==", "dev": true, "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.4", + "@angular-devkit/architect": "0.2101.5", "@babel/core": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -1436,7 +1504,7 @@ "@angular/platform-browser": "^21.0.0", "@angular/platform-server": "^21.0.0", "@angular/service-worker": "^21.0.0", - "@angular/ssr": "^21.1.4", + "@angular/ssr": "^21.1.5", "karma": "^6.4.0", "less": "^4.2.0", "ng-packagr": "^21.0.0", @@ -1486,12 +1554,12 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/architect": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.4.tgz", - "integrity": "sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", "dev": true, "dependencies": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "rxjs": "7.8.2" }, "bin": { @@ -1504,12 +1572,12 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "dev": true, "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -1530,6 +1598,22 @@ } } }, + "node_modules/@angular/build/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@angular/build/node_modules/ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -1587,9 +1671,9 @@ } }, "node_modules/@angular/cdk": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.5.tgz", - "integrity": "sha512-AlQPgqe3LLwXCyrDwYSX3m/WKnl2ppCMW7Gb+7bJpIcpMdWYEpSOSQF318jXGYIysKg43YbdJ1tWhJWY/cbn3w==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.6.tgz", + "integrity": "sha512-a6isY8eWxfRO99KPs8GLMQRDE06diNbPmD9tcYi+EWxjPFOZ88QJmMN8a0yja+LDQaG2kRbNBLjYUEro3qCpTQ==", "dependencies": { "parse5": "^8.0.0", "tslib": "^2.3.0" @@ -1626,17 +1710,17 @@ } }, "node_modules/@angular/cli": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.4.tgz", - "integrity": "sha512-XsMHgxTvHGiXXrhYZz3zMZYhYU0gHdpoHKGiEKXwcx+S1KoYbIssyg6oF2Kq49ZaE0OYCTKjnvgDce6ZqdkJ/A==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.5.tgz", + "integrity": "sha512-ljqvAzSk8FKMaYW/aZhR+SXjudbQViYYkMlJvJUClGpokjDM9KfJWPX+QZfr2J+piW5yaaHmFaIMddO9QxkUDQ==", "dependencies": { - "@angular-devkit/architect": "0.2101.4", - "@angular-devkit/core": "21.1.4", - "@angular-devkit/schematics": "21.1.4", + "@angular-devkit/architect": "0.2101.5", + "@angular-devkit/core": "21.1.5", + "@angular-devkit/schematics": "21.1.5", "@inquirer/prompts": "7.10.1", "@listr2/prompt-adapter-inquirer": "3.0.5", "@modelcontextprotocol/sdk": "1.26.0", - "@schematics/angular": "21.1.4", + "@schematics/angular": "21.1.5", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.46.2", "ini": "6.0.0", @@ -1660,11 +1744,11 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.4.tgz", - "integrity": "sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", "dependencies": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "rxjs": "7.8.2" }, "bin": { @@ -1677,11 +1761,11 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -1703,11 +1787,11 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.4.tgz", - "integrity": "sha512-Nqq0ioCUxrbEX+L4KOarETcZZJNnJ1mAJ0ubO4VM91qnn8RBBM9SnQ91590TfC34Szk/wh+3+Uj6KUvTJNuegQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.5.tgz", + "integrity": "sha512-CGmoorQL5+mVCJEHwHWOrhSd1hFxB3h66i9wUDizJAEQUM3mSml5SiglHArpWY/G4GmFwi6XVe+Jm3U8J/mcFg==", "dependencies": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -1719,6 +1803,21 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@angular/cli/node_modules/ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -1948,9 +2047,9 @@ } }, "node_modules/@angular/common": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.5.tgz", - "integrity": "sha512-olO2F0b+H8YBfsuQFEwo9Hjf+B714xGcttDW37+4jnY2IRS2uYeMu2RGIpY7ps+0uZ017c4iK3CCgSPBgmbTcA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.6.tgz", + "integrity": "sha512-qEfwyJhebl2tHwFhKHE/ZzsCLMWnQ0u/UDS23KUA4tTWaOKH8Usu7DS1B3KnUravII8R6ZpYn86L+B903v9WxA==", "dependencies": { "tslib": "^2.3.0" }, @@ -1958,14 +2057,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.5", + "@angular/core": "21.1.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.5.tgz", - "integrity": "sha512-yRUdWlL+AWcTL4d7zD0jkNqsjvxXpWEihvOfD2gc65DO0+E80DsWIpHq9A8yWeLukbfLcmBGI2QbfW9+SXAlvg==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.6.tgz", + "integrity": "sha512-8RFdfbWTyx+OiRkiK2LsYPuhv8b65S3x/4+98kZRCrzP6YNjq/qsuqpfMUIKwnjok78FfhGyEx4I5LOV3Vkabw==", "dependencies": { "tslib": "^2.3.0" }, @@ -1974,9 +2073,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.5.tgz", - "integrity": "sha512-i2r2bQuWdjjFGTd2TA7FtCWNx5yJ3BMoyTGUC9lzSfmxWAfcH/NWR+6OdaEVwv6Zap3IXYYxs8S+REkx954EwA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.6.tgz", + "integrity": "sha512-0JU2cBDMSB4hU4KwDS2ThrkGh+Njf8Yfm11CKR0NWbHGwW1xHa7whlcpUzX/USqL+FNGXQ75R0fOcZrT86YvrA==", "dependencies": { "@babel/core": "7.28.5", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -1995,7 +2094,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "21.1.5", + "@angular/compiler": "21.1.6", "typescript": ">=5.9 <6.0" }, "peerDependenciesMeta": { @@ -2152,9 +2251,9 @@ } }, "node_modules/@angular/core": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.5.tgz", - "integrity": "sha512-m61YHiyE+SIvS8UXcFLjYCucv6ShJJCwz9xxEk7ysYW9wOtHdfIf9tgyOsucZDAvrvpSyQLRj5jGBCGm1VIvXA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.6.tgz", + "integrity": "sha512-c+n9Ynq1Ar+4SOaL10C/arqBje0dUFFUaDyErXp3jPXU/L29fsFTlmKM2EWunM1RhJckYonJ/xtH0gwwrH6W9Q==", "dependencies": { "tslib": "^2.3.0" }, @@ -2162,7 +2261,7 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "21.1.5", + "@angular/compiler": "21.1.6", "rxjs": "^6.5.3 || ^7.4.0", "zone.js": "~0.15.0 || ~0.16.0" }, @@ -2176,9 +2275,9 @@ } }, "node_modules/@angular/elements": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.5.tgz", - "integrity": "sha512-SoXNNLEBn9w3NP5vZJiMQfdiJ1FLY0VuDP5VvscPVqebh/nfpDw7jOu8Pb2S9wTANsfmlFX4QK1ehl3nJVY/Fw==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.6.tgz", + "integrity": "sha512-LvF0kgh1XxLlXyw5CS5C787HYFAIGVHAg9HNgGZKINeu16Og7KCnsVszAyWiVn1t3trcLARcScYgrOcuwBYzIg==", "dependencies": { "tslib": "^2.3.0" }, @@ -2186,14 +2285,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "21.1.5", + "@angular/core": "21.1.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/forms": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.5.tgz", - "integrity": "sha512-Z8Vcgz5KYlCobRxLjyGGUBv0mA4nusuiD36GqYRn3sR780TLDcPFVwTCwVEWLdwID64oiHXG+x9jjU/Z3HzR6A==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.6.tgz", + "integrity": "sha512-Bw3nVDWihGUGyys7oq2zdJ2MjvJvU1x1WaExYmp3rKU3S7rQXGq6IxY8bopTtHirTANrY2KUEnJ2IlK+xVg9OA==", "dependencies": { "@standard-schema/spec": "^1.0.0", "tslib": "^2.3.0" @@ -2202,25 +2301,25 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.5", - "@angular/core": "21.1.5", - "@angular/platform-browser": "21.1.5", + "@angular/common": "21.1.6", + "@angular/core": "21.1.6", + "@angular/platform-browser": "21.1.6", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/language-service": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.5.tgz", - "integrity": "sha512-/MiXx/peMBjMHEX6Gm7C6ZHOlcgEzkWeBlI7WWnIEDU9vU6F3nU4kauBYdLr/EpXhHpdpOtL4eknPhAfdvVrJA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.6.tgz", + "integrity": "sha512-I2gYI9cKP/B/rUz8WVaWNpuULSdq4W+ZUm6YQyUXdGEh3dDEssK1xLucPBfUT7RAr6h7+w5/RS+zKWC1q9g24w==", "dev": true, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/@angular/platform-browser": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.5.tgz", - "integrity": "sha512-rAN0cu05Pg7HHe9JMRd3g5JyyVCeFW8QiB/jG6klUrOTF4QzyCbmwlm7MX0uTx3CWAZraWCGbdahUkLyYtuqFA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.6.tgz", + "integrity": "sha512-im6aNcgYdIYIVW2262ATkC39WUmhc+KVNVKwKtO5jlOsq9TWmxT1/esncEAlokMe5os6eeb/Ga4D6Ghj0gj4Ig==", "dependencies": { "tslib": "^2.3.0" }, @@ -2228,9 +2327,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "21.1.5", - "@angular/common": "21.1.5", - "@angular/core": "21.1.5" + "@angular/animations": "21.1.6", + "@angular/common": "21.1.6", + "@angular/core": "21.1.6" }, "peerDependenciesMeta": { "@angular/animations": { @@ -2239,9 +2338,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.5.tgz", - "integrity": "sha512-Pd8nPbJSIONnze1WS9wLBAtaFw4TYIH+ZGjKHS9G1E9l09tDWtHWyB7dY82Sc//Nc8iR4V7dcsbUmFjOJHThww==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.6.tgz", + "integrity": "sha512-lVtHkhK/jnrGdX+4S8ItfMO+5buHAU9NMHeDq+QqalnXznMaC7Qd4BPLcRWW4QAI177zG0NE1Bet5cjO75N9+w==", "dependencies": { "tslib": "^2.3.0" }, @@ -2249,16 +2348,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.5", - "@angular/compiler": "21.1.5", - "@angular/core": "21.1.5", - "@angular/platform-browser": "21.1.5" + "@angular/common": "21.1.6", + "@angular/compiler": "21.1.6", + "@angular/core": "21.1.6", + "@angular/platform-browser": "21.1.6" } }, "node_modules/@angular/router": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.5.tgz", - "integrity": "sha512-OjFn6Nw51CU712CMbl2U9TxlCkzOmjMLYPAfnV4+RdG7o+/eOS2nV0oapJ88RNCw7Yl04PA1amc3ql3agDFd4A==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.6.tgz", + "integrity": "sha512-JJn0gfeRks2czbeLmtxxjIlmKtOmjYi3yAaoAwiwpbfVHPLJeJ32axLJREAU0dBkThF8YD+r18uEJ9UrvkqrtA==", "dependencies": { "tslib": "^2.3.0" }, @@ -2266,9 +2365,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "21.1.5", - "@angular/core": "21.1.5", - "@angular/platform-browser": "21.1.5", + "@angular/common": "21.1.6", + "@angular/core": "21.1.6", + "@angular/platform-browser": "21.1.6", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -4705,9 +4804,9 @@ } }, "node_modules/@eslint/css-tree": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-3.6.8.tgz", - "integrity": "sha512-s0f40zY7dlMp8i0Jf0u6l/aSswS0WRAgkhgETgiCJRcxIWb4S/Sp9uScKHWbkM3BnoFLbJbmOYk5AZUDFVxaLA==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-3.6.9.tgz", + "integrity": "sha512-3D5/OHibNEGk+wKwNwMbz63NMf367EoR4mVNNpxddCHKEb2Nez7z62J2U6YjtErSsZDoY0CsccmoUpdEbkogNA==", "dev": true, "dependencies": { "mdn-data": "2.23.0", @@ -5156,9 +5255,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "version": "1.19.10", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.10.tgz", + "integrity": "sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw==", "engines": { "node": ">=18.14.1" }, @@ -5188,17 +5287,33 @@ "@rails/actioncable": ">=7.0" } }, + "node_modules/@html-eslint/core": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/core/-/core-0.57.0.tgz", + "integrity": "sha512-X/cKrOmXrxZSdgyKwtbaCuuJ1k/u82MK58Q6p1TzfwPatwIYx+icfBv1Vp1dLui0L0y1fwBW4H+TKhBf7mMKmg==", + "dev": true, + "dependencies": { + "@html-eslint/types": "^0.57.0", + "eslint": "^9.39.1", + "html-standard": "^0.0.13" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@html-eslint/eslint-plugin": { - "version": "0.54.2", - "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.54.2.tgz", - "integrity": "sha512-C6jhJqVGTS9AW3Z84Ni/Cs6h3XcRHUXi1YkRaAYI08MeNj6ZWIXhwKBEJgEGK2YxzOcM1TpZEvHL4d5z7aC7Eg==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.57.0.tgz", + "integrity": "sha512-9/sjZ2KHsZmco45Z9rGySOocJ734rhjjt1ofXJve7lzzBY+t8FF7fwVp9EmcXSer+0zDUb5hGBHWbgIZ0SEnJA==", "dev": true, "dependencies": { "@eslint/plugin-kit": "^0.4.1", - "@html-eslint/parser": "^0.54.0", - "@html-eslint/template-parser": "^0.54.0", - "@html-eslint/template-syntax-parser": "^0.54.0", - "@html-eslint/types": "^0.54.0" + "@html-eslint/core": "^0.57.0", + "@html-eslint/parser": "^0.57.0", + "@html-eslint/template-parser": "^0.57.0", + "@html-eslint/template-syntax-parser": "^0.57.0", + "@html-eslint/types": "^0.57.0", + "@rviscomi/capo.js": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5207,42 +5322,55 @@ "eslint": ">=8.0.0 || ^10.0.0-0" } }, - "node_modules/@html-eslint/parser": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.54.0.tgz", - "integrity": "sha512-ia3I/6jf87679pUrhIDKSgddKbw9GLkvO86fYt7tJQHHRQu+zySErIxuI2b941oeovzOq10dTpC5Hp41qbvPgg==", + "node_modules/@html-eslint/eslint-plugin/node_modules/@html-eslint/parser": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.57.1.tgz", + "integrity": "sha512-nQ5vw7Os+Snjxq9hLLBak2bv502Obn77BNOWfGK2+GIrShxtGd8w1ehlKW3EB5/RQzqBk6VDK8nPfexlR3M7kg==", "dev": true, "dependencies": { - "@eslint/css-tree": "^3.6.8", - "@html-eslint/template-syntax-parser": "^0.54.0", - "@html-eslint/types": "^0.54.0", + "@eslint/css-tree": "^3.6.9", + "@html-eslint/template-syntax-parser": "^0.57.0", + "@html-eslint/types": "^0.57.0", + "css-tree": "^3.1.0", + "es-html-parser": "0.3.1" + } + }, + "node_modules/@html-eslint/parser": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.57.0.tgz", + "integrity": "sha512-guzD7QJFM0UOGnOZco7I9WEZOBQCmXR3abt6sIhznq4x7Ws6l/KwnNo7im6r0g7/ftLTmuKnLjyNEbW8seQyMQ==", + "dev": true, + "dependencies": { + "@eslint/css-tree": "^3.6.9", + "@html-eslint/template-syntax-parser": "^0.57.0", + "@html-eslint/types": "^0.57.0", "css-tree": "^3.1.0", "es-html-parser": "0.3.1" } }, "node_modules/@html-eslint/template-parser": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/template-parser/-/template-parser-0.54.0.tgz", - "integrity": "sha512-gSjgmGwRQehNxZ3XdRUhUoXDFzYc/LYoKA7JwExjdvklGnSh5WkH/CLOlphkDh9jJsC1O/E0I04bVGrzy3idKQ==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/template-parser/-/template-parser-0.57.0.tgz", + "integrity": "sha512-tddyBo4dEl4W4Ehxuyd6H4jsSqvsfL5F7Bj9/aFfdQyv36q7BGWM2BRHb6FMmYKAPGZ3VzyEbUlcqIwXpDkY3w==", "dev": true, "dependencies": { - "@html-eslint/types": "^0.54.0", + "@html-eslint/types": "^0.57.0", "es-html-parser": "0.3.1" } }, "node_modules/@html-eslint/template-syntax-parser": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/template-syntax-parser/-/template-syntax-parser-0.54.0.tgz", - "integrity": "sha512-7mDM4AWqz42FHwnOt8Lu5xqovaZHlEuBrmwNrMg6VwC9TPaLVyh4j3zNzNnM6tjftaXZzverJup39zGB8mvXjg==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/template-syntax-parser/-/template-syntax-parser-0.57.0.tgz", + "integrity": "sha512-vHp5y4TR+HhgMDi3rAkgm90LBptSZaQUJudZSj+WdvnSBjLe/fgJC4aVjtLVHTS9ynORrFio8AmH1Bz20kYk4g==", "dev": true, "dependencies": { - "@html-eslint/types": "^0.54.0" + "@html-eslint/types": "^0.57.0" } }, "node_modules/@html-eslint/types": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/types/-/types-0.54.0.tgz", - "integrity": "sha512-bfJolxay0POMYaFWTCH1MBitEaxIEKZOoROGOLZiRBaPvQrzhwYQktuyt5X1PcHqUB4HwEtYgSdpjYGT4JbrvA==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/types/-/types-0.57.0.tgz", + "integrity": "sha512-wZAHc9FHZRVAcKyx1NdMNGpw1Jo/Anh+9y+bTQ/cKjh5MHJlbs8ogthIG8efBVFIVlIgzxEA8yrX+DPXmuWisA==", "dev": true, "dependencies": { "@types/css-tree": "^2.3.11", @@ -7046,9 +7174,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.4.tgz", - "integrity": "sha512-CgKnMofIVGTwNPqFNZmkmr2aLOFUG/AKm8lauXU+juwSaY7Z28eguFd+J42uVUOnasLxINQY9y7kr9f6deTrcg==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.5.tgz", + "integrity": "sha512-5nG9v/nEzsaKxgw5NurM6tPKPw0OYsCM3DL4ZI8+TidT55hYbsroTnyBcHBouJ1qlZlQXNtlsjsjBmBDtF7JZA==", "dev": true, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", @@ -7989,350 +8117,325 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", - "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", - "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", - "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", - "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", - "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", - "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", - "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", - "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", - "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", - "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", - "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "cpu": [ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", - "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "cpu": [ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", - "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", - "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", - "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", - "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", - "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", - "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", - "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", - "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openbsd" ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", - "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "openharmony" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", - "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", - "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", - "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", - "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -8344,13 +8447,19 @@ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true }, + "node_modules/@rviscomi/capo.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@rviscomi/capo.js/-/capo.js-2.1.0.tgz", + "integrity": "sha512-y6J+KJqsrY8AcDswLKkvd8KdpFindjS4Q9rSuK8CIpsQOepEjgRaMR4S8OtuLOQoVYLCROT3ffMQqRWrUMQdQA==", + "dev": true + }, "node_modules/@schematics/angular": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.4.tgz", - "integrity": "sha512-I1zdSNzdbrVCWpeE2NsZQmIoa9m0nlw4INgdGIkqUH6FgwvoGKC0RoOxKAmm6HHVJ48FE/sPI13dwAeK89ow5A==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.5.tgz", + "integrity": "sha512-AndJ17ePYUoqJqiIF9VaXbGAFfOqDcHuAxhwozsQlWDzwgQSOUC/WWeG9hKVCgMD6tE02Sxr2ova9DiBKsLQNg==", "dependencies": { - "@angular-devkit/core": "21.1.4", - "@angular-devkit/schematics": "21.1.4", + "@angular-devkit/core": "21.1.5", + "@angular-devkit/schematics": "21.1.5", "jsonc-parser": "3.3.1" }, "engines": { @@ -8360,11 +8469,11 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "dependencies": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -8386,11 +8495,11 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.4.tgz", - "integrity": "sha512-Nqq0ioCUxrbEX+L4KOarETcZZJNnJ1mAJ0ubO4VM91qnn8RBBM9SnQ91590TfC34Szk/wh+3+Uj6KUvTJNuegQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.5.tgz", + "integrity": "sha512-CGmoorQL5+mVCJEHwHWOrhSd1hFxB3h66i9wUDizJAEQUM3mSml5SiglHArpWY/G4GmFwi6XVe+Jm3U8J/mcFg==", "dependencies": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", @@ -8402,6 +8511,21 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@schematics/angular/node_modules/ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -8523,11 +8647,11 @@ } }, "node_modules/@schematics/angular/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -8911,17 +9035,16 @@ } }, "node_modules/@tiptap/extensions": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.15.3.tgz", - "integrity": "sha512-ycx/BgxR4rc9tf3ZyTdI98Z19yKLFfqM3UN+v42ChuIwkzyr9zyp7kG8dB9xN2lNqrD+5y/HyJobz/VJ7T90gA==", - "license": "MIT", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.20.0.tgz", + "integrity": "sha512-HIsXX942w3nbxEQBlMAAR/aa6qiMBEP7CsSMxaxmTIVAmW35p6yUASw6GdV1u0o3lCZjXq2OSRMTskzIqi5uLg==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^3.15.3", - "@tiptap/pm": "^3.15.3" + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" } }, "node_modules/@tiptap/pm": { @@ -9051,9 +9174,9 @@ } }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dependencies": { "brace-expansion": "^5.0.2" }, @@ -9402,9 +9525,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "19.2.10", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", - "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "dependencies": { "csstype": "^3.2.2" @@ -9705,25 +9828,13 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { @@ -9765,12 +9876,12 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -10049,25 +10160,13 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/type-utils/node_modules/debug": { @@ -10100,12 +10199,12 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -10154,25 +10253,13 @@ "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { @@ -10193,12 +10280,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -10341,25 +10428,13 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/utils/node_modules/debug": { @@ -10392,12 +10467,12 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -10487,6 +10562,12 @@ "vite": "^6.0.0 || ^7.0.0" } }, + "node_modules/@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "dev": true + }, "node_modules/@w11k/ngx-componentdestroyed": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@w11k/ngx-componentdestroyed/-/ngx-componentdestroyed-5.0.2.tgz", @@ -11480,9 +11561,9 @@ "integrity": "sha512-VVE1H6cc4ai+ZXo/CRWoJiHXrA1qfA31DPnx6D20+kSI547hQN5Greh51LQ1baMRMfxO5K5M4ImMtZbZt2DODQ==" }, "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "funding": [ { "type": "opencollective", @@ -11499,7 +11580,7 @@ ], "dependencies": { "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -12063,9 +12144,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001764", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", - "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "version": "1.0.30001776", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", + "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==", "funding": [ { "type": "opencollective", @@ -12079,8 +12160,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/ccount": { "version": "2.0.1", @@ -14557,11 +14637,11 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", "dependencies": { - "ip-address": "10.0.1" + "ip-address": "10.1.0" }, "engines": { "node": ">= 16" @@ -14573,14 +14653,6 @@ "express": ">= 4.11" } }, - "node_modules/express-rate-limit/node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "engines": { - "node": ">= 12" - } - }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -14759,9 +14831,9 @@ } }, "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", - "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -15202,9 +15274,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dependencies": { "brace-expansion": "^5.0.2" }, @@ -15679,9 +15751,9 @@ } }, "node_modules/hono": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", - "integrity": "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==", + "version": "4.12.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz", + "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==", "engines": { "node": ">=16.9.0" } @@ -15764,6 +15836,16 @@ "node": ">=0.10.0" } }, + "node_modules/html-standard": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/html-standard/-/html-standard-0.0.13.tgz", + "integrity": "sha512-6oNfW3c1t44O7jVXu0tp4E5MbHifWlXrHlZBPt6y7vFdgLOUUh8hyzoRhfUgozlBUK6oLLYhqP1uIqbZ8ggcBA==", + "dev": true, + "dependencies": { + "vscode-css-languageservice": "^6.3.9", + "vscode-languageserver-textdocument": "^1.0.12" + } + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -16105,9 +16187,9 @@ } }, "node_modules/ignore-walk/node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dependencies": { "brace-expansion": "^5.0.2" }, @@ -16133,9 +16215,9 @@ } }, "node_modules/immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true }, "node_modules/import-fresh": { @@ -19275,9 +19357,9 @@ "dev": true }, "node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "dependencies": { "brace-expansion": "^1.1.7" @@ -22010,11 +22092,10 @@ } }, "node_modules/rollup": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", - "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, @@ -22026,31 +22107,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.55.1", - "@rollup/rollup-android-arm64": "4.55.1", - "@rollup/rollup-darwin-arm64": "4.55.1", - "@rollup/rollup-darwin-x64": "4.55.1", - "@rollup/rollup-freebsd-arm64": "4.55.1", - "@rollup/rollup-freebsd-x64": "4.55.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", - "@rollup/rollup-linux-arm-musleabihf": "4.55.1", - "@rollup/rollup-linux-arm64-gnu": "4.55.1", - "@rollup/rollup-linux-arm64-musl": "4.55.1", - "@rollup/rollup-linux-loong64-gnu": "4.55.1", - "@rollup/rollup-linux-loong64-musl": "4.55.1", - "@rollup/rollup-linux-ppc64-gnu": "4.55.1", - "@rollup/rollup-linux-ppc64-musl": "4.55.1", - "@rollup/rollup-linux-riscv64-gnu": "4.55.1", - "@rollup/rollup-linux-riscv64-musl": "4.55.1", - "@rollup/rollup-linux-s390x-gnu": "4.55.1", - "@rollup/rollup-linux-x64-gnu": "4.55.1", - "@rollup/rollup-linux-x64-musl": "4.55.1", - "@rollup/rollup-openbsd-x64": "4.55.1", - "@rollup/rollup-openharmony-arm64": "4.55.1", - "@rollup/rollup-win32-arm64-msvc": "4.55.1", - "@rollup/rollup-win32-ia32-msvc": "4.55.1", - "@rollup/rollup-win32-x64-gnu": "4.55.1", - "@rollup/rollup-win32-x64-msvc": "4.55.1", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" } }, @@ -23514,9 +23595,9 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -24304,25 +24385,13 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/typescript-eslint/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/typescript-eslint/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/typescript-eslint/node_modules/debug": { @@ -24364,12 +24433,12 @@ } }, "node_modules/typescript-eslint/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -24981,6 +25050,36 @@ "node": ">=0.10.0" } }, + "node_modules/vscode-css-languageservice": { + "version": "6.3.10", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz", + "integrity": "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==", + "dev": true, + "dependencies": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "^3.1.0" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -26134,16 +26233,16 @@ } }, "@angular-devkit/build-angular": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.4.tgz", - "integrity": "sha512-2HPCo6vEu5EIwxxFYhnmdfbktRBoOVQD3q7lG9PMQPf/jRCnyIZ70qSbXbAV96IMDLFl8mLRfY4scoaFMIYGMw==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-21.1.5.tgz", + "integrity": "sha512-B2jOBAiVl+hA3PLwpxfrbW/gA7SDu9Uv+hQwHYrdwL2XXDVwaQ+c3z9BS3yJDQTkb/TrAJ0sfa2zVLC4b/rHzg==", "dev": true, "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.4", - "@angular-devkit/build-webpack": "0.2101.4", - "@angular-devkit/core": "21.1.4", - "@angular/build": "21.1.4", + "@angular-devkit/architect": "0.2101.5", + "@angular-devkit/build-webpack": "0.2101.5", + "@angular-devkit/core": "21.1.5", + "@angular/build": "21.1.5", "@babel/core": "7.28.5", "@babel/generator": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", @@ -26154,7 +26253,7 @@ "@babel/preset-env": "7.28.5", "@babel/runtime": "7.28.4", "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "21.1.4", + "@ngtools/webpack": "21.1.5", "ansi-colors": "4.1.3", "autoprefixer": "10.4.23", "babel-loader": "10.0.0", @@ -26197,22 +26296,22 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.4.tgz", - "integrity": "sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "dev": true, "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -26226,6 +26325,18 @@ "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true }, + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, "ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -26241,6 +26352,19 @@ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true }, + "autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "requires": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + } + }, "chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -26354,32 +26478,32 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.4.tgz", - "integrity": "sha512-lPjPxeEzUha4bnlGzD3KFFf3yxcQjOfV9wwZIa4XLsqjCZsUk95TzHQH7i64OCTw9uKTEQkJBAuO6v2WXHxopw==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2101.5.tgz", + "integrity": "sha512-G3mvUXiSU3DL1QKngq/yXT94Wr+IdqtOM/1VC3NmsV9KX3OSfwfc560dmhY1efqc9gBA5qL+7kLlgV7Kx/Su3A==", "dev": true, "requires": { - "@angular-devkit/architect": "0.2101.4", + "@angular-devkit/architect": "0.2101.5", "rxjs": "7.8.2" }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.4.tgz", - "integrity": "sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "dev": true, "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -26387,6 +26511,18 @@ "source-map": "0.7.6" } }, + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, "ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -26572,21 +26708,21 @@ } }, "@angular/animations": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.5.tgz", - "integrity": "sha512-gsqHX8lCYV8cgVtHs0iLwrX8SVlmcjUF44l/xCc/jBC/TeKWRl2e6Jqrn1Wcd0NDlGiNsm+mYNyqMyy5/I7kjw==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.1.6.tgz", + "integrity": "sha512-Ft8B0tHBRyd7ORpbaa2S0yRqHSdsyMH6oot7ODAA3kv03k4GTwp74U6Y/NyV4JoZ+lr28+EV4YMv3mAqJwIy/g==", "requires": { "tslib": "^2.3.0" } }, "@angular/build": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.4.tgz", - "integrity": "sha512-7CAAQPWFMMqod40ox5MOVB/CnoBXFDehyQhs0hls6lu7bOy/M0EDy0v6bERkyNGRz1mihWWBiCV8XzEinrlq1A==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.1.5.tgz", + "integrity": "sha512-v2eDinWKlSKuk5pyMMY8j5TMFW8HA9B1l13TrDDpxsRGAAzekg7TFNyuh1x9Y6Rq4Vn+8/8pCjMUPZigzWbMhQ==", "dev": true, "requires": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2101.4", + "@angular-devkit/architect": "0.2101.5", "@babel/core": "7.28.5", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", @@ -26616,22 +26752,22 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.4.tgz", - "integrity": "sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", "dev": true, "requires": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "dev": true, "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -26639,6 +26775,18 @@ "source-map": "0.7.6" } }, + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, "ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -26679,9 +26827,9 @@ } }, "@angular/cdk": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.5.tgz", - "integrity": "sha512-AlQPgqe3LLwXCyrDwYSX3m/WKnl2ppCMW7Gb+7bJpIcpMdWYEpSOSQF318jXGYIysKg43YbdJ1tWhJWY/cbn3w==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.1.6.tgz", + "integrity": "sha512-a6isY8eWxfRO99KPs8GLMQRDE06diNbPmD9tcYi+EWxjPFOZ88QJmMN8a0yja+LDQaG2kRbNBLjYUEro3qCpTQ==", "requires": { "parse5": "^8.0.0", "tslib": "^2.3.0" @@ -26703,17 +26851,17 @@ } }, "@angular/cli": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.4.tgz", - "integrity": "sha512-XsMHgxTvHGiXXrhYZz3zMZYhYU0gHdpoHKGiEKXwcx+S1KoYbIssyg6oF2Kq49ZaE0OYCTKjnvgDce6ZqdkJ/A==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.1.5.tgz", + "integrity": "sha512-ljqvAzSk8FKMaYW/aZhR+SXjudbQViYYkMlJvJUClGpokjDM9KfJWPX+QZfr2J+piW5yaaHmFaIMddO9QxkUDQ==", "requires": { - "@angular-devkit/architect": "0.2101.4", - "@angular-devkit/core": "21.1.4", - "@angular-devkit/schematics": "21.1.4", + "@angular-devkit/architect": "0.2101.5", + "@angular-devkit/core": "21.1.5", + "@angular-devkit/schematics": "21.1.5", "@inquirer/prompts": "7.10.1", "@listr2/prompt-adapter-inquirer": "3.0.5", "@modelcontextprotocol/sdk": "1.26.0", - "@schematics/angular": "21.1.4", + "@schematics/angular": "21.1.5", "@yarnpkg/lockfile": "1.1.0", "algoliasearch": "5.46.2", "ini": "6.0.0", @@ -26729,20 +26877,20 @@ }, "dependencies": { "@angular-devkit/architect": { - "version": "0.2101.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.4.tgz", - "integrity": "sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==", + "version": "0.2101.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2101.5.tgz", + "integrity": "sha512-eTo6wWzUW5AyBBLTbaUTpBHhGbZhzteErtNGklWkhjicCr/soNH+2mVtvg8bqA8sNreYffK1VXKFsq5NyMh5qg==", "requires": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "rxjs": "7.8.2" } }, "@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -26751,17 +26899,28 @@ } }, "@angular-devkit/schematics": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.4.tgz", - "integrity": "sha512-Nqq0ioCUxrbEX+L4KOarETcZZJNnJ1mAJ0ubO4VM91qnn8RBBM9SnQ91590TfC34Szk/wh+3+Uj6KUvTJNuegQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.5.tgz", + "integrity": "sha512-CGmoorQL5+mVCJEHwHWOrhSd1hFxB3h66i9wUDizJAEQUM3mSml5SiglHArpWY/G4GmFwi6XVe+Jm3U8J/mcFg==", "requires": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", "rxjs": "7.8.2" } }, + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, "ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -26903,25 +27062,25 @@ } }, "@angular/common": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.5.tgz", - "integrity": "sha512-olO2F0b+H8YBfsuQFEwo9Hjf+B714xGcttDW37+4jnY2IRS2uYeMu2RGIpY7ps+0uZ017c4iK3CCgSPBgmbTcA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.1.6.tgz", + "integrity": "sha512-qEfwyJhebl2tHwFhKHE/ZzsCLMWnQ0u/UDS23KUA4tTWaOKH8Usu7DS1B3KnUravII8R6ZpYn86L+B903v9WxA==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.5.tgz", - "integrity": "sha512-yRUdWlL+AWcTL4d7zD0jkNqsjvxXpWEihvOfD2gc65DO0+E80DsWIpHq9A8yWeLukbfLcmBGI2QbfW9+SXAlvg==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.1.6.tgz", + "integrity": "sha512-8RFdfbWTyx+OiRkiK2LsYPuhv8b65S3x/4+98kZRCrzP6YNjq/qsuqpfMUIKwnjok78FfhGyEx4I5LOV3Vkabw==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler-cli": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.5.tgz", - "integrity": "sha512-i2r2bQuWdjjFGTd2TA7FtCWNx5yJ3BMoyTGUC9lzSfmxWAfcH/NWR+6OdaEVwv6Zap3IXYYxs8S+REkx954EwA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.1.6.tgz", + "integrity": "sha512-0JU2cBDMSB4hU4KwDS2ThrkGh+Njf8Yfm11CKR0NWbHGwW1xHa7whlcpUzX/USqL+FNGXQ75R0fOcZrT86YvrA==", "requires": { "@babel/core": "7.28.5", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -27020,56 +27179,56 @@ } }, "@angular/core": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.5.tgz", - "integrity": "sha512-m61YHiyE+SIvS8UXcFLjYCucv6ShJJCwz9xxEk7ysYW9wOtHdfIf9tgyOsucZDAvrvpSyQLRj5jGBCGm1VIvXA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.1.6.tgz", + "integrity": "sha512-c+n9Ynq1Ar+4SOaL10C/arqBje0dUFFUaDyErXp3jPXU/L29fsFTlmKM2EWunM1RhJckYonJ/xtH0gwwrH6W9Q==", "requires": { "tslib": "^2.3.0" } }, "@angular/elements": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.5.tgz", - "integrity": "sha512-SoXNNLEBn9w3NP5vZJiMQfdiJ1FLY0VuDP5VvscPVqebh/nfpDw7jOu8Pb2S9wTANsfmlFX4QK1ehl3nJVY/Fw==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-21.1.6.tgz", + "integrity": "sha512-LvF0kgh1XxLlXyw5CS5C787HYFAIGVHAg9HNgGZKINeu16Og7KCnsVszAyWiVn1t3trcLARcScYgrOcuwBYzIg==", "requires": { "tslib": "^2.3.0" } }, "@angular/forms": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.5.tgz", - "integrity": "sha512-Z8Vcgz5KYlCobRxLjyGGUBv0mA4nusuiD36GqYRn3sR780TLDcPFVwTCwVEWLdwID64oiHXG+x9jjU/Z3HzR6A==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.1.6.tgz", + "integrity": "sha512-Bw3nVDWihGUGyys7oq2zdJ2MjvJvU1x1WaExYmp3rKU3S7rQXGq6IxY8bopTtHirTANrY2KUEnJ2IlK+xVg9OA==", "requires": { "@standard-schema/spec": "^1.0.0", "tslib": "^2.3.0" } }, "@angular/language-service": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.5.tgz", - "integrity": "sha512-/MiXx/peMBjMHEX6Gm7C6ZHOlcgEzkWeBlI7WWnIEDU9vU6F3nU4kauBYdLr/EpXhHpdpOtL4eknPhAfdvVrJA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-21.1.6.tgz", + "integrity": "sha512-I2gYI9cKP/B/rUz8WVaWNpuULSdq4W+ZUm6YQyUXdGEh3dDEssK1xLucPBfUT7RAr6h7+w5/RS+zKWC1q9g24w==", "dev": true }, "@angular/platform-browser": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.5.tgz", - "integrity": "sha512-rAN0cu05Pg7HHe9JMRd3g5JyyVCeFW8QiB/jG6klUrOTF4QzyCbmwlm7MX0uTx3CWAZraWCGbdahUkLyYtuqFA==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.1.6.tgz", + "integrity": "sha512-im6aNcgYdIYIVW2262ATkC39WUmhc+KVNVKwKtO5jlOsq9TWmxT1/esncEAlokMe5os6eeb/Ga4D6Ghj0gj4Ig==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-browser-dynamic": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.5.tgz", - "integrity": "sha512-Pd8nPbJSIONnze1WS9wLBAtaFw4TYIH+ZGjKHS9G1E9l09tDWtHWyB7dY82Sc//Nc8iR4V7dcsbUmFjOJHThww==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.1.6.tgz", + "integrity": "sha512-lVtHkhK/jnrGdX+4S8ItfMO+5buHAU9NMHeDq+QqalnXznMaC7Qd4BPLcRWW4QAI177zG0NE1Bet5cjO75N9+w==", "requires": { "tslib": "^2.3.0" } }, "@angular/router": { - "version": "21.1.5", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.5.tgz", - "integrity": "sha512-OjFn6Nw51CU712CMbl2U9TxlCkzOmjMLYPAfnV4+RdG7o+/eOS2nV0oapJ88RNCw7Yl04PA1amc3ql3agDFd4A==", + "version": "21.1.6", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.1.6.tgz", + "integrity": "sha512-JJn0gfeRks2czbeLmtxxjIlmKtOmjYi3yAaoAwiwpbfVHPLJeJ32axLJREAU0dBkThF8YD+r18uEJ9UrvkqrtA==", "requires": { "tslib": "^2.3.0" } @@ -28577,9 +28736,9 @@ } }, "@eslint/css-tree": { - "version": "3.6.8", - "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-3.6.8.tgz", - "integrity": "sha512-s0f40zY7dlMp8i0Jf0u6l/aSswS0WRAgkhgETgiCJRcxIWb4S/Sp9uScKHWbkM3BnoFLbJbmOYk5AZUDFVxaLA==", + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-3.6.9.tgz", + "integrity": "sha512-3D5/OHibNEGk+wKwNwMbz63NMf367EoR4mVNNpxddCHKEb2Nez7z62J2U6YjtErSsZDoY0CsccmoUpdEbkogNA==", "dev": true, "requires": { "mdn-data": "2.23.0", @@ -28914,9 +29073,9 @@ } }, "@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==" + "version": "1.19.10", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.10.tgz", + "integrity": "sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw==" }, "@hotwired/stimulus": { "version": "3.2.2", @@ -28937,55 +29096,83 @@ "@rails/actioncable": ">=7.0" } }, + "@html-eslint/core": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/core/-/core-0.57.0.tgz", + "integrity": "sha512-X/cKrOmXrxZSdgyKwtbaCuuJ1k/u82MK58Q6p1TzfwPatwIYx+icfBv1Vp1dLui0L0y1fwBW4H+TKhBf7mMKmg==", + "dev": true, + "requires": { + "@html-eslint/types": "^0.57.0", + "eslint": "^9.39.1", + "html-standard": "^0.0.13" + } + }, "@html-eslint/eslint-plugin": { - "version": "0.54.2", - "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.54.2.tgz", - "integrity": "sha512-C6jhJqVGTS9AW3Z84Ni/Cs6h3XcRHUXi1YkRaAYI08MeNj6ZWIXhwKBEJgEGK2YxzOcM1TpZEvHL4d5z7aC7Eg==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/eslint-plugin/-/eslint-plugin-0.57.0.tgz", + "integrity": "sha512-9/sjZ2KHsZmco45Z9rGySOocJ734rhjjt1ofXJve7lzzBY+t8FF7fwVp9EmcXSer+0zDUb5hGBHWbgIZ0SEnJA==", "dev": true, "requires": { "@eslint/plugin-kit": "^0.4.1", - "@html-eslint/parser": "^0.54.0", - "@html-eslint/template-parser": "^0.54.0", - "@html-eslint/template-syntax-parser": "^0.54.0", - "@html-eslint/types": "^0.54.0" + "@html-eslint/core": "^0.57.0", + "@html-eslint/parser": "^0.57.0", + "@html-eslint/template-parser": "^0.57.0", + "@html-eslint/template-syntax-parser": "^0.57.0", + "@html-eslint/types": "^0.57.0", + "@rviscomi/capo.js": "^2.1.0" + }, + "dependencies": { + "@html-eslint/parser": { + "version": "0.57.1", + "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.57.1.tgz", + "integrity": "sha512-nQ5vw7Os+Snjxq9hLLBak2bv502Obn77BNOWfGK2+GIrShxtGd8w1ehlKW3EB5/RQzqBk6VDK8nPfexlR3M7kg==", + "dev": true, + "requires": { + "@eslint/css-tree": "^3.6.9", + "@html-eslint/template-syntax-parser": "^0.57.0", + "@html-eslint/types": "^0.57.0", + "css-tree": "^3.1.0", + "es-html-parser": "0.3.1" + } + } } }, "@html-eslint/parser": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.54.0.tgz", - "integrity": "sha512-ia3I/6jf87679pUrhIDKSgddKbw9GLkvO86fYt7tJQHHRQu+zySErIxuI2b941oeovzOq10dTpC5Hp41qbvPgg==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/parser/-/parser-0.57.0.tgz", + "integrity": "sha512-guzD7QJFM0UOGnOZco7I9WEZOBQCmXR3abt6sIhznq4x7Ws6l/KwnNo7im6r0g7/ftLTmuKnLjyNEbW8seQyMQ==", "dev": true, "requires": { - "@eslint/css-tree": "^3.6.8", - "@html-eslint/template-syntax-parser": "^0.54.0", - "@html-eslint/types": "^0.54.0", + "@eslint/css-tree": "^3.6.9", + "@html-eslint/template-syntax-parser": "^0.57.0", + "@html-eslint/types": "^0.57.0", "css-tree": "^3.1.0", "es-html-parser": "0.3.1" } }, "@html-eslint/template-parser": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/template-parser/-/template-parser-0.54.0.tgz", - "integrity": "sha512-gSjgmGwRQehNxZ3XdRUhUoXDFzYc/LYoKA7JwExjdvklGnSh5WkH/CLOlphkDh9jJsC1O/E0I04bVGrzy3idKQ==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/template-parser/-/template-parser-0.57.0.tgz", + "integrity": "sha512-tddyBo4dEl4W4Ehxuyd6H4jsSqvsfL5F7Bj9/aFfdQyv36q7BGWM2BRHb6FMmYKAPGZ3VzyEbUlcqIwXpDkY3w==", "dev": true, "requires": { - "@html-eslint/types": "^0.54.0", + "@html-eslint/types": "^0.57.0", "es-html-parser": "0.3.1" } }, "@html-eslint/template-syntax-parser": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/template-syntax-parser/-/template-syntax-parser-0.54.0.tgz", - "integrity": "sha512-7mDM4AWqz42FHwnOt8Lu5xqovaZHlEuBrmwNrMg6VwC9TPaLVyh4j3zNzNnM6tjftaXZzverJup39zGB8mvXjg==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/template-syntax-parser/-/template-syntax-parser-0.57.0.tgz", + "integrity": "sha512-vHp5y4TR+HhgMDi3rAkgm90LBptSZaQUJudZSj+WdvnSBjLe/fgJC4aVjtLVHTS9ynORrFio8AmH1Bz20kYk4g==", "dev": true, "requires": { - "@html-eslint/types": "^0.54.0" + "@html-eslint/types": "^0.57.0" } }, "@html-eslint/types": { - "version": "0.54.0", - "resolved": "https://registry.npmjs.org/@html-eslint/types/-/types-0.54.0.tgz", - "integrity": "sha512-bfJolxay0POMYaFWTCH1MBitEaxIEKZOoROGOLZiRBaPvQrzhwYQktuyt5X1PcHqUB4HwEtYgSdpjYGT4JbrvA==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@html-eslint/types/-/types-0.57.0.tgz", + "integrity": "sha512-wZAHc9FHZRVAcKyx1NdMNGpw1Jo/Anh+9y+bTQ/cKjh5MHJlbs8ogthIG8efBVFIVlIgzxEA8yrX+DPXmuWisA==", "dev": true, "requires": { "@types/css-tree": "^2.3.11", @@ -30041,9 +30228,9 @@ } }, "@ngtools/webpack": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.4.tgz", - "integrity": "sha512-CgKnMofIVGTwNPqFNZmkmr2aLOFUG/AKm8lauXU+juwSaY7Z28eguFd+J42uVUOnasLxINQY9y7kr9f6deTrcg==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-21.1.5.tgz", + "integrity": "sha512-5nG9v/nEzsaKxgw5NurM6tPKPw0OYsCM3DL4ZI8+TidT55hYbsroTnyBcHBouJ1qlZlQXNtlsjsjBmBDtF7JZA==", "dev": true }, "@npmcli/agent": { @@ -30554,177 +30741,177 @@ "dev": true }, "@rollup/rollup-android-arm-eabi": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", - "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", - "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", - "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", - "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", - "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", - "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", - "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", - "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", - "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", - "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", "dev": true, "optional": true }, "@rollup/rollup-linux-loong64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", - "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", "dev": true, "optional": true }, "@rollup/rollup-linux-loong64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", - "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", "dev": true, "optional": true }, "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", - "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", "dev": true, "optional": true }, "@rollup/rollup-linux-ppc64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", - "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", - "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", - "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", - "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", - "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", - "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", "dev": true, "optional": true }, "@rollup/rollup-openbsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", - "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", "dev": true, "optional": true }, "@rollup/rollup-openharmony-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", - "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", - "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", - "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", - "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", - "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "dev": true, "optional": true }, @@ -30734,22 +30921,28 @@ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true }, + "@rviscomi/capo.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@rviscomi/capo.js/-/capo.js-2.1.0.tgz", + "integrity": "sha512-y6J+KJqsrY8AcDswLKkvd8KdpFindjS4Q9rSuK8CIpsQOepEjgRaMR4S8OtuLOQoVYLCROT3ffMQqRWrUMQdQA==", + "dev": true + }, "@schematics/angular": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.4.tgz", - "integrity": "sha512-I1zdSNzdbrVCWpeE2NsZQmIoa9m0nlw4INgdGIkqUH6FgwvoGKC0RoOxKAmm6HHVJ48FE/sPI13dwAeK89ow5A==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.1.5.tgz", + "integrity": "sha512-AndJ17ePYUoqJqiIF9VaXbGAFfOqDcHuAxhwozsQlWDzwgQSOUC/WWeG9hKVCgMD6tE02Sxr2ova9DiBKsLQNg==", "requires": { - "@angular-devkit/core": "21.1.4", - "@angular-devkit/schematics": "21.1.4", + "@angular-devkit/core": "21.1.5", + "@angular-devkit/schematics": "21.1.5", "jsonc-parser": "3.3.1" }, "dependencies": { "@angular-devkit/core": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.4.tgz", - "integrity": "sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.1.5.tgz", + "integrity": "sha512-KUKbllHvHefkAbTBjWNpRPyrpBqecW+6HBBAR+XNbKBuFTHkG+gxtuwMXNsvO5KECKwQphvQt5h3g05Xtaf0LQ==", "requires": { - "ajv": "8.17.1", + "ajv": "8.18.0", "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.3", @@ -30758,17 +30951,28 @@ } }, "@angular-devkit/schematics": { - "version": "21.1.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.4.tgz", - "integrity": "sha512-Nqq0ioCUxrbEX+L4KOarETcZZJNnJ1mAJ0ubO4VM91qnn8RBBM9SnQ91590TfC34Szk/wh+3+Uj6KUvTJNuegQ==", + "version": "21.1.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.1.5.tgz", + "integrity": "sha512-CGmoorQL5+mVCJEHwHWOrhSd1hFxB3h66i9wUDizJAEQUM3mSml5SiglHArpWY/G4GmFwi6XVe+Jm3U8J/mcFg==", "requires": { - "@angular-devkit/core": "21.1.4", + "@angular-devkit/core": "21.1.5", "jsonc-parser": "3.3.1", "magic-string": "0.30.21", "ora": "9.0.0", "rxjs": "7.8.2" } }, + "ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, "ajv-formats": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", @@ -30840,11 +31044,11 @@ } }, "strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "requires": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" } } } @@ -31050,9 +31254,9 @@ "integrity": "sha512-Woh/nk1/7c31D14dIaU5i9d4NMK06TBeEA+uBidhZp+JAXVGMeQGm0K2iJxBXNvNrc6aKGlZ57W1o3CPUWOlWQ==" }, "@tiptap/extensions": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.15.3.tgz", - "integrity": "sha512-ycx/BgxR4rc9tf3ZyTdI98Z19yKLFfqM3UN+v42ChuIwkzyr9zyp7kG8dB9xN2lNqrD+5y/HyJobz/VJ7T90gA==" + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.20.0.tgz", + "integrity": "sha512-HIsXX942w3nbxEQBlMAAR/aa6qiMBEP7CsSMxaxmTIVAmW35p6yUASw6GdV1u0o3lCZjXq2OSRMTskzIqi5uLg==" }, "@tiptap/pm": { "version": "3.12.0", @@ -31143,9 +31347,9 @@ } }, "minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "requires": { "brace-expansion": "^5.0.2" } @@ -31475,9 +31679,9 @@ "dev": true }, "@types/react": { - "version": "19.2.10", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", - "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "requires": { "csstype": "^3.2.2" @@ -31699,19 +31903,13 @@ "eslint-visitor-keys": "^4.2.1" } }, - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true - }, "brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { - "balanced-match": "^4.0.2" + "balanced-match": "^1.0.0" } }, "debug": { @@ -31736,12 +31934,12 @@ "dev": true }, "minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "requires": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" } } } @@ -31893,19 +32091,13 @@ "eslint-visitor-keys": "^4.2.1" } }, - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true - }, "brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { - "balanced-match": "^4.0.2" + "balanced-match": "^1.0.0" } }, "debug": { @@ -31924,12 +32116,12 @@ "dev": true }, "minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "requires": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" } } } @@ -31957,19 +32149,13 @@ "ts-api-utils": "^2.4.0" }, "dependencies": { - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true - }, "brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { - "balanced-match": "^4.0.2" + "balanced-match": "^1.0.0" } }, "debug": { @@ -31982,12 +32168,12 @@ } }, "minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "requires": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" } } } @@ -32064,19 +32250,13 @@ "eslint-visitor-keys": "^4.2.1" } }, - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true - }, "brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { - "balanced-match": "^4.0.2" + "balanced-match": "^1.0.0" } }, "debug": { @@ -32095,12 +32275,12 @@ "dev": true }, "minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "requires": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" } } } @@ -32152,6 +32332,12 @@ "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", "dev": true }, + "@vscode/l10n": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz", + "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", + "dev": true + }, "@w11k/ngx-componentdestroyed": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@w11k/ngx-componentdestroyed/-/ngx-componentdestroyed-5.0.2.tgz", @@ -32877,12 +33063,12 @@ "integrity": "sha512-VVE1H6cc4ai+ZXo/CRWoJiHXrA1qfA31DPnx6D20+kSI547hQN5Greh51LQ1baMRMfxO5K5M4ImMtZbZt2DODQ==" }, "autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "requires": { "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", + "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -33252,9 +33438,9 @@ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" }, "caniuse-lite": { - "version": "1.0.30001764", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", - "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==" + "version": "1.0.30001776", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", + "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==" }, "ccount": { "version": "2.0.1", @@ -35109,18 +35295,11 @@ } }, "express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", "requires": { - "ip-address": "10.0.1" - }, - "dependencies": { - "ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==" - } + "ip-address": "10.1.0" } }, "ext": { @@ -35205,9 +35384,9 @@ } }, "minimatch": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", - "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -35480,9 +35659,9 @@ "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==" }, "minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "requires": { "brace-expansion": "^5.0.2" } @@ -35824,9 +36003,9 @@ } }, "hono": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.0.tgz", - "integrity": "sha512-NekXntS5M94pUfiVZ8oXXK/kkri+5WpX2/Ik+LVsl+uvw+soj4roXIsPqO+XsWrAw20mOzaXOZf3Q7PfB9A/IA==" + "version": "4.12.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz", + "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==" }, "hosted-git-info": { "version": "9.0.2", @@ -35899,6 +36078,16 @@ } } }, + "html-standard": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/html-standard/-/html-standard-0.0.13.tgz", + "integrity": "sha512-6oNfW3c1t44O7jVXu0tp4E5MbHifWlXrHlZBPt6y7vFdgLOUUh8hyzoRhfUgozlBUK6oLLYhqP1uIqbZ8ggcBA==", + "dev": true, + "requires": { + "vscode-css-languageservice": "^6.3.9", + "vscode-languageserver-textdocument": "^1.0.12" + } + }, "html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -36121,9 +36310,9 @@ } }, "minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "requires": { "brace-expansion": "^5.0.2" } @@ -36138,9 +36327,9 @@ "optional": true }, "immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true }, "import-fresh": { @@ -38241,9 +38430,9 @@ "dev": true }, "minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -40115,36 +40304,36 @@ } }, "rollup": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", - "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "dev": true, "requires": { - "@rollup/rollup-android-arm-eabi": "4.55.1", - "@rollup/rollup-android-arm64": "4.55.1", - "@rollup/rollup-darwin-arm64": "4.55.1", - "@rollup/rollup-darwin-x64": "4.55.1", - "@rollup/rollup-freebsd-arm64": "4.55.1", - "@rollup/rollup-freebsd-x64": "4.55.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", - "@rollup/rollup-linux-arm-musleabihf": "4.55.1", - "@rollup/rollup-linux-arm64-gnu": "4.55.1", - "@rollup/rollup-linux-arm64-musl": "4.55.1", - "@rollup/rollup-linux-loong64-gnu": "4.55.1", - "@rollup/rollup-linux-loong64-musl": "4.55.1", - "@rollup/rollup-linux-ppc64-gnu": "4.55.1", - "@rollup/rollup-linux-ppc64-musl": "4.55.1", - "@rollup/rollup-linux-riscv64-gnu": "4.55.1", - "@rollup/rollup-linux-riscv64-musl": "4.55.1", - "@rollup/rollup-linux-s390x-gnu": "4.55.1", - "@rollup/rollup-linux-x64-gnu": "4.55.1", - "@rollup/rollup-linux-x64-musl": "4.55.1", - "@rollup/rollup-openbsd-x64": "4.55.1", - "@rollup/rollup-openharmony-arm64": "4.55.1", - "@rollup/rollup-win32-arm64-msvc": "4.55.1", - "@rollup/rollup-win32-ia32-msvc": "4.55.1", - "@rollup/rollup-win32-x64-gnu": "4.55.1", - "@rollup/rollup-win32-x64-msvc": "4.55.1", + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", "@types/estree": "1.0.8", "fsevents": "~2.3.2" } @@ -41195,9 +41384,9 @@ "dev": true }, "tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", "requires": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -41686,19 +41875,13 @@ "eslint-visitor-keys": "^4.2.1" } }, - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true - }, "brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { - "balanced-match": "^4.0.2" + "balanced-match": "^1.0.0" } }, "debug": { @@ -41723,12 +41906,12 @@ "dev": true }, "minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "requires": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" } } } @@ -42065,6 +42248,36 @@ "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", "dev": true }, + "vscode-css-languageservice": { + "version": "6.3.10", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.10.tgz", + "integrity": "sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==", + "dev": true, + "requires": { + "@vscode/l10n": "^0.0.18", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "^3.1.0" + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "dev": true + }, + "vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true + }, + "vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true + }, "w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4f083777a0f..743f8b84c2d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,16 +6,16 @@ "private": true, "devDependencies": { "@angular-builders/custom-esbuild": "^21.0.3", - "@angular-devkit/build-angular": "^21.1.4", + "@angular-devkit/build-angular": "^21.1.5", "@angular-eslint/builder": "20.7.0", "@angular-eslint/eslint-plugin": "20.7.0", "@angular-eslint/eslint-plugin-template": "20.7.0", "@angular-eslint/schematics": "20.7.0", "@angular-eslint/template-parser": "20.7.0", - "@angular/language-service": "21.1.5", + "@angular/language-service": "21.1.6", "@eslint/js": "^9.39.2", - "@html-eslint/eslint-plugin": "^0.54.2", - "@html-eslint/parser": "^0.54.0", + "@html-eslint/eslint-plugin": "^0.57.0", + "@html-eslint/parser": "^0.57.0", "@jsdevtools/coverage-istanbul-loader": "3.0.5", "@stylistic/eslint-plugin": "^5.7.1", "@types/codemirror": "5.60.5", @@ -31,7 +31,7 @@ "@types/mousetrap": "^1.6.3", "@types/pako": "^2.0.4", "@types/rails__request.js": "^0.0.1", - "@types/react": "^19.2.10", + "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/resize-observer-browser": "^0.1.4", "@types/urijs": "^1.19.26", @@ -64,18 +64,18 @@ "wscat": "^6.1.0" }, "dependencies": { - "@angular/animations": "^21.1.5", - "@angular/cdk": "^21.1.5", - "@angular/cli": "^21.1.4", - "@angular/common": "^21.1.5", - "@angular/compiler": "^21.1.5", - "@angular/compiler-cli": "^21.1.5", - "@angular/core": "^21.1.5", - "@angular/elements": "^21.1.5", - "@angular/forms": "^21.1.5", - "@angular/platform-browser": "^21.1.5", - "@angular/platform-browser-dynamic": "^21.1.5", - "@angular/router": "^21.1.5", + "@angular/animations": "^21.1.6", + "@angular/cdk": "^21.1.6", + "@angular/cli": "^21.1.5", + "@angular/common": "^21.1.6", + "@angular/compiler": "^21.1.6", + "@angular/compiler-cli": "^21.1.6", + "@angular/core": "^21.1.6", + "@angular/elements": "^21.1.6", + "@angular/forms": "^21.1.6", + "@angular/platform-browser": "^21.1.6", + "@angular/platform-browser-dynamic": "^21.1.6", + "@angular/router": "^21.1.6", "@appsignal/javascript": "^1.6.1", "@appsignal/plugin-breadcrumbs-console": "^1.1.37", "@appsignal/plugin-breadcrumbs-network": "^1.1.24", @@ -120,14 +120,14 @@ "@rails/request.js": "^0.0.13", "@stimulus-components/auto-submit": "^6.0.0", "@stimulus-components/reveal": "^5.0.0", - "@tiptap/extensions": "^3.13.0", + "@tiptap/extensions": "^3.20.0", "@types/jquery.cookie": "^1.4.36", "@uirouter/angular": "^17.0.0", "@uirouter/core": "^6.1.0", "@uirouter/rx": "^1.0.0", "@w11k/ngx-componentdestroyed": "^5.0.2", "@xeokit/xeokit-bim-viewer": "2.7.1", - "autoprefixer": "^10.4.23", + "autoprefixer": "^10.4.27", "byte-base64": "^1.1.0", "chart.js": "4.5.1", "chartjs-adapter-luxon": "^1.3.1", diff --git a/frontend/src/app/shared/components/fields/edit/field-types/multi-select-edit-field.component.ts b/frontend/src/app/shared/components/fields/edit/field-types/multi-select-edit-field.component.ts index 58262b7d6e4..079ef05844d 100644 --- a/frontend/src/app/shared/components/fields/edit/field-types/multi-select-edit-field.component.ts +++ b/frontend/src/app/shared/components/fields/edit/field-types/multi-select-edit-field.component.ts @@ -204,7 +204,7 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements if (Array.isArray(allowedValues)) { this.setValues(allowedValues); } else if (this.schema.allowedValues) { - return this.schema.allowedValues.$load().then((values:CollectionResource) => { + return (this.schema.allowedValues.$load() as Promise).then((values:CollectionResource) => { // The select options of the project shall be sorted if (values.count > 0 && (values.elements[0] as any)._type === 'Project') { this.setValues(values.elements, true); diff --git a/lib/api/open_project_api.rb b/lib/api/open_project_api.rb index 5123a3bb730..3f84f7ed400 100644 --- a/lib/api/open_project_api.rb +++ b/lib/api/open_project_api.rb @@ -40,27 +40,3 @@ module API end end end - -Grape::DSL::Routing::ClassMethods.module_eval do - # Be reload safe. otherwise, an infinite loop occurs on reload. - unless instance_methods.include?(:orig_namespace) - alias :orig_namespace :namespace - end - - def namespace(space = nil, options = {}, &) - orig_namespace(space, options) do - instance_eval(&) - apply_patches(space) - end - end - - def apply_patches(path) - (patches[path] || []).each do |patch| - instance_eval(&patch) - end - end - - def patches - Constants::APIPatchRegistry.patches_for(base) - end -end diff --git a/lib/api/v3/work_packages/work_package_representer.rb b/lib/api/v3/work_packages/work_package_representer.rb index 4a5f03e5307..4043e338c45 100644 --- a/lib/api/v3/work_packages/work_package_representer.rb +++ b/lib/api/v3/work_packages/work_package_representer.rb @@ -657,7 +657,8 @@ module API @current_user_update_allowed = current_user.allowed_in_work_package?(:edit_work_packages, represented) || current_user.allowed_in_project?(:change_work_package_status, represented.project) || - current_user.allowed_in_project?(:assign_versions, represented.project) + current_user.allowed_in_project?(:assign_versions, represented.project) || + current_user.allowed_in_project?(:manage_sprint_items, represented.project) end def view_time_entries_allowed? diff --git a/lib/open_project.rb b/lib/open_project.rb index 90ae8e60c3f..149308044e4 100644 --- a/lib/open_project.rb +++ b/lib/open_project.rb @@ -59,11 +59,9 @@ module OpenProject private_class_method def self.httpx_session session = HTTPX - .plugin(:oauth) - .plugin(:persistent) - .plugin(:basic_auth) - .plugin(:webdav) .with(headers: { "User-Agent" => "OpenProject #{OpenProject::VERSION.to_semver} HTTPX Client" }) + .plugin(:auth) + .plugin(:webdav) .with( timeout: { connect_timeout: OpenProject::Configuration.httpx_connect_timeout, @@ -74,6 +72,7 @@ module OpenProject keep_alive_timeout: OpenProject::Configuration.httpx_keep_alive_timeout } ) + OpenProject::Appsignal.enabled? ? session.plugin(HttpxAppsignal) : session end end diff --git a/lib/open_project/patches/carrierwave_fog_file.rb b/lib/open_project/patches/carrierwave_fog_file.rb deleted file mode 100644 index c1aca617364..00000000000 --- a/lib/open_project/patches/carrierwave_fog_file.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require "carrierwave/storage/fog" - -## -# Code copied straight from the CarrierWave source. -# All we did is add `options[:expire_at]`. -# -# @todo Upgrade to CarrierWave 2.0.2 to make this patch obsolete. - -module OpenProject::Patches::FogFile - extend ActiveSupport::Concern - - included do - def authenticated_url(options = {}) # rubocop:disable Metrics/AbcSize - if ["AWS", "Google", "Rackspace", "OpenStack"].include?(@uploader.fog_credentials[:provider]) - # avoid a get by using local references - local_directory = connection.directories.new(key: @uploader.fog_directory) - local_file = local_directory.files.new(key: path) - expire_at = options[:expire_at] || (::Fog::Time.now + @uploader.fog_authenticated_url_expiration) - case @uploader.fog_credentials[:provider] - when "AWS", "Google" - # Older versions of fog-google do not support options as a parameter - if url_options_supported?(local_file) - local_file.url(expire_at, options) - else - warn "Options hash not supported in #{local_file.class}. You may need to upgrade your Fog provider." - local_file.url(expire_at) - end - when "Rackspace" - connection.get_object_https_url(@uploader.fog_directory, path, expire_at, options) - when "OpenStack" - connection.get_object_https_url(@uploader.fog_directory, path, expire_at) - else - local_file.url(expire_at) - end - end - end - end -end - -OpenProject::Patches.patch_gem_version "carrierwave", "1.3.4" do - CarrierWave::Storage::Fog::File.include OpenProject::Patches::FogFile -end diff --git a/lib/open_project/patches/carrierwave_sanitized_file.rb b/lib/open_project/patches/grape_dsl_routing.rb similarity index 58% rename from lib/open_project/patches/carrierwave_sanitized_file.rb rename to lib/open_project/patches/grape_dsl_routing.rb index 33d8fa02316..8aaf0402a7f 100644 --- a/lib/open_project/patches/carrierwave_sanitized_file.rb +++ b/lib/open_project/patches/grape_dsl_routing.rb @@ -27,35 +27,35 @@ # # See COPYRIGHT and LICENSE files for more details. #++ -require "carrierwave" -## -# Adapt carrierwave to match fixes for CVE-2023-49090. -# https://github.com/carrierwaveuploader/carrierwave/security/advisories/GHSA-vfmv-jfc5-pjjw -module OpenProject::Patches::CarrierwaveSanitizedFile +module OpenProject::Patches::GrapeDslRouting extend ActiveSupport::Concern included do - def content_type - return @content_type if @content_type + # Be reload safe. otherwise, an infinite loop occurs on reload. + unless method_defined?(:orig_namespace) + alias :orig_namespace :namespace + end - if @file.respond_to?(:content_type) and @file.content_type - Marcel::MimeType.for(declared_type: @file.content_type.to_s.chomp) - elsif path - @content_type = Attachment.content_type_for(path) + def namespace(space = nil, **, &) + orig_namespace(space, **) do + instance_eval(&) + apply_patches(space) end end - # create the directory if it doesn't exist - # Overwritten to avoid ruby 2.7 deprecations - def mkdir!(path, directory_permissions) - options = {} - options[:mode] = directory_permissions if directory_permissions - FileUtils.mkdir_p(File.dirname(path), **options) + def apply_patches(path) + (patches[path] || []).each do |patch| + instance_eval(&patch) + end + end + + def patches + Constants::APIPatchRegistry.patches_for(self) end end end -OpenProject::Patches.patch_gem_version "carrierwave", "1.3.4" do - CarrierWave::SanitizedFile.include OpenProject::Patches::CarrierwaveSanitizedFile +OpenProject::Patches.patch_gem_version "grape", "3.1.1" do + Grape::DSL::Routing.include OpenProject::Patches::GrapeDslRouting end diff --git a/lib/open_project/ssrf_protection.rb b/lib/open_project/ssrf_protection.rb index fc2f4f2fb5a..c9e786c2129 100644 --- a/lib/open_project/ssrf_protection.rb +++ b/lib/open_project/ssrf_protection.rb @@ -30,35 +30,6 @@ module OpenProject class SsrfProtection < ::SsrfFilter - # ssrf_filter works by resolving the hostname to a public IP, then connecting to that IP - # directly (to prevent DNS-rebinding attacks). For HTTPS, Net::HTTP must set the SNI hostname - # so the server knows which certificate to serve. - # - # However, modern Net::HTTP (net-http gem) explicitly skips setting SNI when the address is - # an IP (correctly per RFC 6066 §3 — IP literals are not valid in SNI). ssrf_filter 1.0.x - # patches SSLSocket#hostname= to inject the original hostname, but that patch never fires - # because Net::HTTP never calls hostname= for IP addresses. - # - # This patch targets SSLSocket#connect instead, which IS called regardless of address type. - # It sets the SNI hostname from ssrf_filter's thread-local just before the TLS handshake, - # making HTTPS work for SNI-dependent servers (e.g. Cloudflare-hosted webhooks). - # - # This patch can be removed once release/17.2 is retired — on dev we use ssrf_filter ~> 1.3 - # which resolves this differently. - ::OpenSSL::SSL::SSLSocket.class_eval do - # Net::Protocol#ssl_socket_connect uses connect_nonblock when a timeout is set (the common - # case), falling back to connect only when timeout is nil. Patch both to be safe. - %i[connect connect_nonblock].each do |meth| - original = instance_method(meth) - define_method(meth) do |**kwargs| - if (hostname = ::Thread.current[::SsrfFilter::FIBER_LOCAL_KEY]) - self.hostname = hostname - end - original.bind(self).call(**kwargs) - end - end - end - class << self ## # Performs an SSRF-safe HTTP POST request to the given URL. diff --git a/lib/open_project/version.rb b/lib/open_project/version.rb index 654d711e8ec..338b47873cb 100644 --- a/lib/open_project/version.rb +++ b/lib/open_project/version.rb @@ -32,8 +32,8 @@ require "open3" module OpenProject module VERSION # :nodoc: MAJOR = 17 - MINOR = 2 - PATCH = 1 + MINOR = 3 + PATCH = 0 class << self def revision diff --git a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb index d6001afdb96..1f6b72e7d26 100644 --- a/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/lib_static/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -48,7 +48,7 @@ module Redmine # N.B. the default for validate should be false, however specs seem to think differently has_many :custom_values, -> { includes(:custom_field) - .order("#{CustomField.table_name}.position") + .order("#{CustomField.table_name}.position", "#{CustomValue.table_name}.id") }, as: :customized, dependent: :delete_all, validate: false, diff --git a/lookbook/previews/op_primer/copy_to_clipboard_component_preview.rb b/lookbook/previews/op_primer/copy_to_clipboard_component_preview.rb index f37618269db..e4766bff810 100644 --- a/lookbook/previews/op_primer/copy_to_clipboard_component_preview.rb +++ b/lookbook/previews/op_primer/copy_to_clipboard_component_preview.rb @@ -36,6 +36,12 @@ module OpPrimer render(OpPrimer::CopyToClipboardComponent.new(value)) end + # @param url text + # @param scheme [Symbol] select [value, link] + def playground(url: "https://example.org", scheme: :value) + render(OpPrimer::CopyToClipboardComponent.new(url, scheme:)) + end + # @param url text def as_link(url: "http://example.org") render(OpPrimer::CopyToClipboardComponent.new(url, scheme: :link)) diff --git a/modules/auth_saml/app/components/saml/providers/side_panel/information_component.html.erb b/modules/auth_saml/app/components/saml/providers/side_panel/information_component.html.erb index 758bde48119..260bede0a4c 100644 --- a/modules/auth_saml/app/components/saml/providers/side_panel/information_component.html.erb +++ b/modules/auth_saml/app/components/saml/providers/side_panel/information_component.html.erb @@ -9,7 +9,7 @@ end collection.with_component( - OpPrimer::CopyToClipboardComponent.new(provider.sp_entity_id, scheme: :input) + OpPrimer::CopyToClipboardComponent.new(provider.sp_entity_id, scheme: :value) ) collection.with_component(Primer::Beta::Heading.new(tag: :h5, mt: 4, mb: 1)) do diff --git a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb index 5926e1aa2e2..944eb46096d 100644 --- a/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb +++ b/modules/backlogs/app/components/backlogs/backlog_menu_component.html.erb @@ -36,7 +36,7 @@ See COPYRIGHT and LICENSE files for more details. tooltip_direction: :se ) - if user_allowed?(:update_sprints) + if user_allowed?(:create_sprints) menu.with_item( id: dom_target(sprint, :menu, :edit_sprint), label: t(".action_menu.edit_sprint"), @@ -47,7 +47,7 @@ See COPYRIGHT and LICENSE files for more details. end end - if user_allowed?(:add_work_packages) + if user_allowed?(:manage_sprint_items) menu.with_item( id: dom_target(sprint, :menu, :new_story), label: t(".action_menu.new_story"), @@ -62,7 +62,7 @@ See COPYRIGHT and LICENSE files for more details. end end - if user_allowed?(:update_sprints) || user_allowed?(:add_work_packages) + if user_allowed?(:create_sprints) || user_allowed?(:manage_sprint_items) menu.with_divider end @@ -76,15 +76,13 @@ See COPYRIGHT and LICENSE files for more details. end if backlog.sprint_backlog? - if user_allowed?(:view_taskboards) - menu.with_item( - id: dom_target(sprint, :menu, :task_board), - label: t(".action_menu.task_board"), - tag: :a, - href: backlogs_project_sprint_taskboard_path(project, sprint) - ) do |item| - item.with_leading_visual_icon(icon: :"op-view-cards") - end + menu.with_item( + id: dom_target(sprint, :menu, :task_board), + label: t(".action_menu.task_board"), + tag: :a, + href: backlogs_project_sprint_taskboard_path(project, sprint) + ) do |item| + item.with_leading_visual_icon(icon: :"op-view-cards") end menu.with_item( @@ -109,7 +107,7 @@ See COPYRIGHT and LICENSE files for more details. end end - if user_allowed?(:manage_versions) + if user_allowed?(:create_sprints) menu.with_item( id: dom_target(sprint, :menu, :properties), label: t(".action_menu.properties"), diff --git a/modules/backlogs/app/components/backlogs/new_sprint_dialog_component.html.erb b/modules/backlogs/app/components/backlogs/new_sprint_dialog_component.html.erb new file mode 100644 index 00000000000..b56a66a937a --- /dev/null +++ b/modules/backlogs/app/components/backlogs/new_sprint_dialog_component.html.erb @@ -0,0 +1,69 @@ +<%#-- 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( + title:, + size: :large, + id: DIALOG_ID + ) + ) do |d| + d.with_header(variant: :large) + + d.with_body do + render(Backlogs::NewSprintFormComponent.new(sprint: @sprint)) + end + + d.with_footer do + component_collection do |buttons| + buttons.with_component( + Primer::Beta::Button.new( + data: { + close_dialog_id: DIALOG_ID + } + ) + ) do + t("button_cancel") + end + + buttons.with_component( + Primer::Beta::Button.new( + scheme: :primary, + form: FORM_ID, + data: { turbo: true }, + type: :submit + ) + ) do + button_caption + end + end + end + end +%> diff --git a/modules/backlogs/app/components/backlogs/new_sprint_dialog_component.rb b/modules/backlogs/app/components/backlogs/new_sprint_dialog_component.rb new file mode 100644 index 00000000000..99432014903 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/new_sprint_dialog_component.rb @@ -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 Backlogs + class NewSprintDialogComponent < ApplicationComponent + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + include Primer::FetchOrFallbackHelper + + DIALOG_ID = "new-sprint-dialog" + FORM_ID = "new-sprint-dialog-form" + FOOTER_ID = "new-sprint-dialog-footer" + + STATE_DEFAULT = :create + STATE_OPTIONS = [STATE_DEFAULT, :edit].freeze + + attr_reader :sprint, :state + + delegate :create?, :edit?, to: :state + + def initialize(sprint:, state: STATE_DEFAULT) + super + + @sprint = sprint + @state = ActiveSupport::StringInquirer.new(fetch_or_fallback(STATE_OPTIONS, state, STATE_DEFAULT).to_s) + end + + private + + def title + create? ? t(:label_sprint_new) : t(:label_sprint_edit) + end + + def button_caption + create? ? t(:button_create) : t(:button_save) + end + end +end diff --git a/modules/backlogs/app/components/backlogs/new_sprint_form_component.html.erb b/modules/backlogs/app/components/backlogs/new_sprint_form_component.html.erb new file mode 100644 index 00000000000..db3d8bebb39 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/new_sprint_form_component.html.erb @@ -0,0 +1,56 @@ +<%#-- 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 do + primer_form_with( + id: Backlogs::NewSprintFormComponent::FORM_ID, + model: @sprint, + scope: :sprint, + method: http_verb, + class: "op-sprints--form", + url: form_url, + data: data_attributes + ) do |f| + flex_layout(mb: 2) do |flex| + if @base_errors&.any? + flex.with_row do + render(Primer::Alpha::Banner.new(mb: 3, icon: :stop, scheme: :danger)) { @base_errors.join("\n") } + end + end + flex.with_row(mb: 3) do + render Backlogs::Sprints::DetailsForm.new(f) + end + flex.with_row(classes: "FormControl-horizontalGroup--sm-vertical") do + render Backlogs::Sprints::DatesForm.new(f) + end + end + end + end +%> diff --git a/modules/backlogs/app/components/backlogs/new_sprint_form_component.rb b/modules/backlogs/app/components/backlogs/new_sprint_form_component.rb new file mode 100644 index 00000000000..1b8532e6add --- /dev/null +++ b/modules/backlogs/app/components/backlogs/new_sprint_form_component.rb @@ -0,0 +1,68 @@ +# 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 Backlogs + class NewSprintFormComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + FORM_ID = NewSprintDialogComponent::FORM_ID + + def initialize(sprint:, base_errors: nil) + super + + @sprint = sprint + @base_errors = base_errors + end + + private + + def http_verb + @sprint.new_record? ? :post : :put + end + + def form_url + if @sprint.new_record? + project_sprints_path(@sprint.project_id) + else + update_agile_sprint_project_sprint_path(@sprint.project_id, @sprint.id) + end + end + + def data_attributes + { + controller: "refresh-on-form-changes", + "refresh-on-form-changes-target": "form", + "refresh-on-form-changes-turbo-stream-url-value": refresh_form_project_sprints_path(@sprint.project_id) + } + end + end +end diff --git a/modules/backlogs/app/components/backlogs/sprint_component.html.erb b/modules/backlogs/app/components/backlogs/sprint_component.html.erb new file mode 100644 index 00000000000..fc6fe3ac72b --- /dev/null +++ b/modules/backlogs/app/components/backlogs/sprint_component.html.erb @@ -0,0 +1,63 @@ +<%# -- 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(tag: :section) do %> + <%= render(Primer::Beta::BorderBox.new(**@system_arguments)) do |border_box| %> + <% border_box.with_header(id: dom_target(sprint, :header)) do %> + <%= render(Backlogs::SprintHeaderComponent.new(sprint:, folded: folded?)) %> + <% end %> + <% if stories.empty? %> + <% border_box.with_row(data: { empty_list_item: true }) do %> + <%= + render Primer::Beta::Blankslate.new(role: "status", aria: { live: "polite" }) do |blankslate| + blankslate.with_heading(tag: :h4).with_content(t(".blankslate_title", name: sprint.name)) + blankslate.with_description_content(t(".blankslate_description")) + end + %> + <% end %> + <% end %> + <% stories.each do |story| %> + <% border_box.with_row( + id: dom_id(story), + classes: "Box-row--hover-blue Box-row--focus-gray Box-row--clickable Box-row--draggable", + data: draggable_item_config(story).merge( + story: true, + controller: "backlogs--story", + backlogs__story_id_value: story.id, + backlogs__story_split_url_value: details_backlogs_project_backlogs_path(project, story), + backlogs__story_full_url_value: work_package_path(story), + backlogs__story_selected_class: "Box-row--blue" + ), + tabindex: 0 + ) do %> + <%= render(Backlogs::StoryComponent.new(story:, sprint:, max_position:)) %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/modules/backlogs/app/components/backlogs/sprint_component.rb b/modules/backlogs/app/components/backlogs/sprint_component.rb new file mode 100644 index 00000000000..96dddd58110 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/sprint_component.rb @@ -0,0 +1,92 @@ +# 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 Backlogs + class SprintComponent < ApplicationComponent + include Primer::AttributesHelper + include OpTurbo::Streamable + include RbCommonHelper + + attr_reader :sprint, :current_user + + delegate :project, to: :sprint + + def initialize(sprint:, current_user: User.current, **system_arguments) + super() + + @sprint = sprint + @current_user = current_user + + @system_arguments = system_arguments + @system_arguments[:id] = dom_id(sprint) + @system_arguments[:list_id] = "#{@system_arguments[:id]}-list" + @system_arguments[:padding] = :condensed + @system_arguments[:data] = merge_data( + @system_arguments, + { data: drop_target_config } + ) + end + + def stories + sprint.work_packages + end + + def wrapper_uniq_by + sprint.id + end + + private + + def folded? + current_user.backlogs_preference(:versions_default_fold_state) == "closed" + end + + def max_position + stories.filter_map(&:position).max + end + + def drop_target_config + { + generic_drag_and_drop_target: "container", + target_container_accessor: ":scope > ul", + target_id: sprint.id, + target_allowed_drag_type: "story" + } + end + + def draggable_item_config(story) + { + draggable_id: story.id, + draggable_type: "story", + drop_url: move_backlogs_project_sprint_story_path(project, sprint, story) + } + end + end +end diff --git a/modules/backlogs/app/components/backlogs/sprint_header_component.html.erb b/modules/backlogs/app/components/backlogs/sprint_header_component.html.erb new file mode 100644 index 00000000000..1c1b3f62326 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/sprint_header_component.html.erb @@ -0,0 +1,77 @@ +<%# -- 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(tag: :header) do %> + <%= grid_layout("op-backlogs-header", tag: :div) do |grid| %> + <% grid.with_area(:collapsible) do %> + <%= + render( + Primer::OpenProject::BorderBox::CollapsibleHeader.new( + collapsible_id: "#{dom_id(sprint)}-list", + collapsed:, + multi_line: false + ) + ) do |collapsible| + collapsible.with_title { sprint.name } + collapsible.with_count( + scheme: :default, + count: story_count, + round: true, + aria: { + label: t(".label_story_count", count: story_count), + live: "polite" + } + ) + collapsible.with_description(role: "group") do + format_date_range(date_range) + end + end + %> + <% end %> + + <% grid.with_area(:points) do %> + <%= + render( + Primer::Beta::Text.new( + color: :subtle, + classes: "velocity", + aria: { live: "polite" } + ) + ) do + %> + <%= story_points %> + <%= t(:"backlogs.points_label", count: story_points) %> + <% end %> + <% end %> + + <% grid.with_area(:menu) do %> + <%= render(Backlogs::SprintMenuComponent.new(sprint:, project:)) %> + <% end %> + <% end %> +<% end %> diff --git a/spec/lib/open_project/patches/carrierwave_sanitized_file_spec.rb b/modules/backlogs/app/components/backlogs/sprint_header_component.rb similarity index 54% rename from spec/lib/open_project/patches/carrierwave_sanitized_file_spec.rb rename to modules/backlogs/app/components/backlogs/sprint_header_component.rb index 14e6fb5d0b7..968276e1d50 100644 --- a/spec/lib/open_project/patches/carrierwave_sanitized_file_spec.rb +++ b/modules/backlogs/app/components/backlogs/sprint_header_component.rb @@ -27,33 +27,52 @@ # # See COPYRIGHT and LICENSE files for more details. #++ -require "spec_helper" -# Adapt the carrierwave sanitized file tests to the content type detector -RSpec.describe OpenProject::Patches::CarrierwaveSanitizedFile do - let(:file) { FileHelpers.mock_uploaded_file(name: "original-filename.txt") } +module Backlogs + class SprintHeaderComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + include Primer::FetchOrFallbackHelper + include Redmine::I18n + include RbCommonHelper - it "uses the first one when multiple mime types are given using a semicolon" do - allow(file).to receive(:content_type).and_return("image/png; text/html") + attr_reader :sprint, :collapsed, :current_user - sanitized_file = CarrierWave::SanitizedFile.new(file) + delegate :project, to: :sprint + delegate :name, to: :sprint, prefix: :sprint - expect(sanitized_file.content_type).to eq("image/png") - end + def initialize( + sprint:, + folded: false, + current_user: User.current + ) + super() - it "uses the first one when multiple mime types are given using a comma" do - allow(file).to receive(:content_type).and_return("image/png, text/html") + @sprint = sprint + @collapsed = folded + @current_user = current_user + end - sanitized_file = CarrierWave::SanitizedFile.new(file) + def wrapper_uniq_by + sprint.id + end - expect(sanitized_file.content_type).to eq("image/png") - end + def stories + @sprint.work_packages + end - it "drops content type parameters" do - allow(file).to receive(:content_type).and_return("text/html; charset=utf-8") + private - sanitized_file = CarrierWave::SanitizedFile.new(file) + def story_points + @story_points ||= stories.sum { |story| story.story_points || 0 } + end - expect(sanitized_file.content_type).to eq("text/html") + def story_count + @story_count ||= stories.size + end + + def date_range + [sprint.start_date, sprint.finish_date] + end end end diff --git a/modules/backlogs/app/components/backlogs/sprint_menu_component.html.erb b/modules/backlogs/app/components/backlogs/sprint_menu_component.html.erb new file mode 100644 index 00000000000..13d99c433c6 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/sprint_menu_component.html.erb @@ -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. + +++# %> + +<%= + render(Primer::Alpha::ActionMenu.new(**@system_arguments)) do |menu| + menu.with_show_button( + scheme: :invisible, + icon: :"kebab-horizontal", + "aria-label": t(".label_actions"), + tooltip_direction: :se + ) + + if user_allowed?(:create_sprints) + menu.with_item( + id: dom_target(sprint, :menu, :edit_sprint), + label: t(".action_menu.edit_sprint"), + href: edit_dialog_project_sprint_path(project, sprint), + content_arguments: { data: { controller: "async-dialog" } } + ) do |item| + item.with_leading_visual_icon(icon: :pencil) + end + end + + if user_allowed?(:manage_sprint_items) + menu.with_item( + id: dom_target(sprint, :menu, :new_story), + label: t(".action_menu.new_story"), + href: new_project_work_packages_dialog_path( + project, + sprint_id: sprint.id, + type_id: available_story_types.first + ), + content_arguments: { data: { turbo_stream: true } } + ) do |item| + item.with_leading_visual_icon(icon: :compose) + end + end + + if user_allowed?(:create_sprints) || user_allowed?(:add_work_packages) + menu.with_divider + end + + menu.with_item( + # TODO: sprint_id filter does not exist for work packages. Add? + id: dom_target(sprint, :menu, :stories_tasks), + scheme: :danger, + label: t(".action_menu.stories_tasks"), + tag: :a, + href: project_work_packages_path(project, sprint_id: sprint.id) + ) do |item| + item.with_leading_visual_icon(icon: :"op-view-list") + end + + # menu.with_item( + # # TODO: what to do with the task board? + # label: t(".action_menu.task_board"), + # tag: :a, + # href: backlogs_project_sprint_taskboard_path(project, sprint) + # ) do |item| + # item.with_leading_visual_icon(icon: :"op-view-cards") + # end + # + # menu.with_item( + # # TODO: what to do with the burndown chart? + # label: t(".action_menu.burndown_chart"), + # tag: :a, + # href: backlogs_project_sprint_burndown_chart_path(project, sprint), + # disabled: !sprint.has_burndown? + # ) do |item| + # item.with_leading_visual_icon(icon: :graph) + # end + end +%> diff --git a/modules/backlogs/app/components/backlogs/sprint_menu_component.rb b/modules/backlogs/app/components/backlogs/sprint_menu_component.rb new file mode 100644 index 00000000000..91e3dc15436 --- /dev/null +++ b/modules/backlogs/app/components/backlogs/sprint_menu_component.rb @@ -0,0 +1,67 @@ +# 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 Backlogs + class SprintMenuComponent < ApplicationComponent + include RbCommonHelper + + attr_reader :sprint, :project, :current_user + + def initialize(sprint:, project:, current_user: User.current, **system_arguments) + super() + + @sprint = sprint + @project = project + @current_user = current_user + + @system_arguments = system_arguments + @system_arguments[:menu_id] = dom_target(sprint, :menu) + @system_arguments[:anchor_align] = :end + @system_arguments[:classes] = class_names( + @system_arguments[:classes], + "hide-when-print" + ) + end + + def stories + @sprint.work_packages + end + + private + + def user_allowed?(permission) + current_user.allowed_in_project?(permission, project) + end + + def available_story_types + @available_story_types ||= story_types & project.types + end + end +end diff --git a/modules/backlogs/app/contracts/sprints/base_contract.rb b/modules/backlogs/app/contracts/sprints/base_contract.rb new file mode 100644 index 00000000000..cf014093c71 --- /dev/null +++ b/modules/backlogs/app/contracts/sprints/base_contract.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Sprints + class BaseContract < ::ModelContract + def self.model + Agile::Sprint + end + + attribute :name + attribute :project_id + attribute :start_date + attribute :finish_date + end +end diff --git a/modules/backlogs/app/contracts/sprints/create_contract.rb b/modules/backlogs/app/contracts/sprints/create_contract.rb new file mode 100644 index 00000000000..c80ecedac04 --- /dev/null +++ b/modules/backlogs/app/contracts/sprints/create_contract.rb @@ -0,0 +1,45 @@ +# 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 Sprints + class CreateContract < BaseContract + validate :user_allowed_to_create + + private + + def user_allowed_to_create + return if model.project.nil? + + unless user.allowed_in_project?(:create_sprints, model.project) + errors.add :base, :error_unauthorized + end + end + end +end diff --git a/modules/backlogs/app/contracts/sprints/update_contract.rb b/modules/backlogs/app/contracts/sprints/update_contract.rb new file mode 100644 index 00000000000..e2f922d79a9 --- /dev/null +++ b/modules/backlogs/app/contracts/sprints/update_contract.rb @@ -0,0 +1,45 @@ +# 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 Sprints + class UpdateContract < BaseContract + validate :user_allowed_to_update + + private + + def user_allowed_to_update + return if model.project.nil? + + unless user.allowed_in_project?(:create_sprints, model.project) + errors.add :base, :error_unauthorized + end + end + end +end diff --git a/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb b/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb index b53f8be524b..a59b335f055 100644 --- a/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb +++ b/modules/backlogs/app/controllers/projects/settings/backlogs_controller.rb @@ -31,17 +31,10 @@ class Projects::Settings::BacklogsController < Projects::SettingsController menu_item :settings_backlogs - def show - @statuses_done_for_project = @project.done_statuses.pluck(:id) - end + def show; end def update - selected_statuses = (params[:statuses] || []).filter_map do |work_package_status| - Status.find(work_package_status[:status_id].to_i) - end - - @project.done_statuses = selected_statuses - @project.save! + @project.update!(params.expect(project: { done_status_ids: [] })) flash[:notice] = I18n.t(:notice_successful_update) diff --git a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb index dd6838e553b..c9062c36dae 100644 --- a/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb +++ b/modules/backlogs/app/controllers/rb_master_backlogs_controller.rb @@ -36,10 +36,20 @@ class RbMasterBacklogsController < RbApplicationController before_action :load_backlogs, only: :index def index - if turbo_frame_request? - render partial: "list", layout: false + if OpenProject::FeatureDecisions.scrum_projects_active? + # Feature flag is active, render the new views + if turbo_frame_request? + render partial: "agile_list", layout: false + else + render :agile_index + end else - render :index + # Feature flag is not active, render legacy views + if turbo_frame_request? # rubocop:disable Style/IfInsideElse + render partial: "list", layout: false + else + render :index + end end end @@ -48,7 +58,12 @@ class RbMasterBacklogsController < RbApplicationController render "work_packages/split_view", layout: false else load_backlogs - render :index + + if OpenProject::FeatureDecisions.scrum_projects_active? + render :agile_index + else + render :index + end end end @@ -58,6 +73,11 @@ class RbMasterBacklogsController < RbApplicationController def load_backlogs @owner_backlogs = Backlog.owner_backlogs(@project) - @sprint_backlogs = Backlog.sprint_backlogs(@project) + + if OpenProject::FeatureDecisions.scrum_projects_active? + @sprints = Agile::Sprint.for_project(@project).not_completed.order_by_date + else + @sprint_backlogs = Backlog.sprint_backlogs(@project) + end end end diff --git a/modules/backlogs/app/controllers/rb_sprints_controller.rb b/modules/backlogs/app/controllers/rb_sprints_controller.rb index 9107a003bfb..0dbbe03a5b5 100644 --- a/modules/backlogs/app/controllers/rb_sprints_controller.rb +++ b/modules/backlogs/app/controllers/rb_sprints_controller.rb @@ -31,6 +31,81 @@ class RbSprintsController < RbApplicationController include OpTurbo::ComponentStream + NEW_SPRINT_ACTIONS = %i[new_dialog + edit_dialog + create + refresh_form + update_agile_sprint].freeze + + skip_before_action :load_sprint_and_project, only: NEW_SPRINT_ACTIONS + + before_action :not_authorized_on_feature_flag_inactive, + :load_project, + only: NEW_SPRINT_ACTIONS + + def new_dialog + call = Sprints::SetAttributesService.new( + user: current_user, + model: Agile::Sprint.new, + contract_class: EmptyContract + ).call(attributes: converted_agile_sprint_params) + + respond_with_dialog Backlogs::NewSprintDialogComponent.new(sprint: call.result) + end + + def edit_dialog + @sprint = Agile::Sprint.for_project(@project).visible.find(params[:id]) + + respond_with_dialog Backlogs::NewSprintDialogComponent.new(sprint: @sprint, state: :edit) + end + + def refresh_form + id = edit_agile_sprint_params.dig(:sprint, :id) + sprint = id.present? ? Agile::Sprint.for_project(@project).visible.find(id) : Agile::Sprint.new + + call = Sprints::SetAttributesService.new( + user: current_user, + model: sprint, + contract_class: EmptyContract + ).call(attributes: converted_agile_sprint_params) + + update_via_turbo_stream(component: Backlogs::NewSprintFormComponent.new(sprint: call.result)) + + respond_with_turbo_streams + end + + def create # rubocop:disable Metrics/AbcSize + call = Sprints::CreateService + .new(user: current_user) + .call(attributes: converted_agile_sprint_params) + + if call.success? + flash[:notice] = I18n.t(:notice_successful_create) + render turbo_stream: turbo_stream.redirect_to(backlogs_project_backlogs_path(@project)) + else + update_new_sprint_form_component_via_turbo_stream(sprint: call.result, base_errors: call.errors[:base]) + respond_with_turbo_streams + end + end + + # Called like this due to `update` being taken by legacy sprints. + def update_agile_sprint # rubocop:disable Metrics/AbcSize + @sprint = Agile::Sprint.for_project(@project).visible.find(params[:id]) + + call = Sprints::UpdateService + .new(user: current_user, model: @sprint) + .call(attributes: agile_sprint_params[:sprint]) + + if call.success? + render_success_flash_message_via_turbo_stream(message: I18n.t(:notice_successful_update)) + update_sprint_header_component_via_turbo_stream(sprint: call.result) + else + update_new_sprint_form_component_via_turbo_stream(sprint: call.result, base_errors: call.errors[:base]) + end + + respond_with_turbo_streams + end + def edit_name update_header_component_via_turbo_stream(state: :edit) respond_with_turbo_streams @@ -78,15 +153,54 @@ class RbSprintsController < RbApplicationController ) end + def update_sprint_header_component_via_turbo_stream(sprint:) + update_via_turbo_stream( + component: Backlogs::SprintHeaderComponent.new(sprint:), + method: :morph + ) + end + + def update_new_sprint_form_component_via_turbo_stream(sprint:, base_errors: nil) + update_via_turbo_stream( + component: Backlogs::NewSprintFormComponent.new( + sprint:, + base_errors: + ), + status: :bad_request + ) + end + # Overrides load_sprint_and_project to load the sprint from :id instead of :sprint_id def load_sprint_and_project @sprint = Sprint.visible.find(params[:id]) - @project = @sprint.project - # This overrides sprint's project if we set another project, say a subproject + load_project + end + + def load_project @project = Project.visible.find(params[:project_id]) end def sprint_params params.expect(sprint: %i[name start_date effective_date]) end + + def agile_sprint_params + params.permit(sprint: %i[name start_date finish_date]) + end + + def edit_agile_sprint_params + params.permit(sprint: %i[id name start_date finish_date]) + end + + def converted_agile_sprint_params + # Do some preprocessing to make the params easier to use + converted_sprint_params = agile_sprint_params[:sprint].to_h + converted_sprint_params[:project] = @project + + converted_sprint_params + end + + def not_authorized_on_feature_flag_inactive + render_403 unless OpenProject::FeatureDecisions.scrum_projects_active? + end end diff --git a/modules/backlogs/app/forms/backlogs/sprints/dates_form.rb b/modules/backlogs/app/forms/backlogs/sprints/dates_form.rb new file mode 100644 index 00000000000..8104e6929f3 --- /dev/null +++ b/modules/backlogs/app/forms/backlogs/sprints/dates_form.rb @@ -0,0 +1,77 @@ +# 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 Backlogs + module Sprints + class DatesForm < ApplicationForm + form do |f| + f.group(layout: :horizontal) do |dates| + dates.text_field( + name: :start_date, + type: :date, + label: attribute_name(:start_date), + placeholder: attribute_name(:start_date), + required: true, + input_width: :small, + data: { + action: "change->refresh-on-form-changes#triggerTurboStream" + } + ) + dates.text_field( + name: :finish_date, + type: :date, + label: attribute_name(:finish_date), + placeholder: attribute_name(:finish_date), + required: true, + input_width: :small, + data: { + action: "change->refresh-on-form-changes#triggerTurboStream" + } + ) + dates.text_field( + name: :duration, + label: attribute_name(:duration), + input_width: :xsmall, + readonly: true, + value: display_duration + ) + end + end + + def display_duration + if model.duration.present? + [model.duration, I18n.t("datetime.units.day", count: model.duration)].join(" ") + else + "" + end + end + end + end +end diff --git a/modules/backlogs/app/forms/backlogs/sprints/details_form.rb b/modules/backlogs/app/forms/backlogs/sprints/details_form.rb new file mode 100644 index 00000000000..d49b9ccc3c2 --- /dev/null +++ b/modules/backlogs/app/forms/backlogs/sprints/details_form.rb @@ -0,0 +1,55 @@ +# 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 Backlogs + module Sprints + class DetailsForm < ApplicationForm + form do |f| + f.hidden(name: :id) + + f.text_field( + label: attribute_name(:name), + name: :name, + required: true, + autofocus: true, + w: :full + ) + + # f.text_area( + # label: attribute_name(:goal), + # name: :goal, + # required: false, + # w: :full, + # rows: 3 + # ) + end + end + end +end diff --git a/modules/backlogs/app/forms/projects/settings/backlogs_settings_form.rb b/modules/backlogs/app/forms/projects/settings/backlogs_settings_form.rb new file mode 100644 index 00000000000..316380b7529 --- /dev/null +++ b/modules/backlogs/app/forms/projects/settings/backlogs_settings_form.rb @@ -0,0 +1,70 @@ +# 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 Settings + class BacklogsSettingsForm < ApplicationForm + form do |f| + f.autocompleter( + name: :done_status_ids, + label: I18n.t(:"backlogs.definition_of_done"), + caption: I18n.t(:"backlogs.definition_of_done_caption"), + autocomplete_options: { + multiple: true, + closeOnSelect: false, + clearable: false, + decorated: true, + data: { + test_selector: "done_status_ids_autocomplete" + } + } + ) do |list| + available_statuses.each do |label, value| + active = value.in?(model.done_status_ids) + + list.option( + label:, + value:, + selected: active + ) + end + end + + f.submit(scheme: :primary, name: :apply, label: I18n.t(:button_save)) + end + + private + + def available_statuses + Status.pluck(:name, :id) + end + end + end +end diff --git a/modules/backlogs/app/helpers/rb_common_helper.rb b/modules/backlogs/app/helpers/rb_common_helper.rb index b28a5891b70..7b3dc947d54 100644 --- a/modules/backlogs/app/helpers/rb_common_helper.rb +++ b/modules/backlogs/app/helpers/rb_common_helper.rb @@ -132,6 +132,14 @@ module RbCommonHelper item.remaining_hours.blank? || item.remaining_hours == 0 ? "" : item.remaining_hours end + def scrum_projects_enabled? + OpenProject::FeatureDecisions.scrum_projects_active? + end + + def allow_sprint_creation?(project) + scrum_projects_enabled? && current_user.allowed_in_project?(:create_sprints, project) + end + private def work_package_status_for_id(id) diff --git a/modules/backlogs/app/models/agile/sprint.rb b/modules/backlogs/app/models/agile/sprint.rb index d236ca0bc74..8905ffd709d 100644 --- a/modules/backlogs/app/models/agile/sprint.rb +++ b/modules/backlogs/app/models/agile/sprint.rb @@ -38,6 +38,15 @@ module Agile belongs_to :project has_many :work_packages, dependent: :nullify + scope :for_project, ->(project) { where(project:) } + scope :not_completed, -> { !completed } + scope :order_by_date, -> do + order(arel_table[:start_date].asc.nulls_last, + arel_table[:finish_date].asc.nulls_last) + end + # FIXME: replace this stub with a meaningful implementation. + scope :visible, -> { all } + enum :status, { in_planning: "in_planning", @@ -60,15 +69,26 @@ module Agile validates :name, presence: true validates :project, presence: true validates :start_date, presence: true + validates :finish_date, presence: true validates :finish_date, - presence: true, - comparison: { greater_than_or_equal_to: :start_date } + comparison: { greater_than_or_equal_to: :start_date }, + if: :start_date? validate :validate_only_one_active_sprint_per_project # TODO: validate sharing is set to an allowed value, e.g. only admins may share systemwide (#71374, #71253) # TODO: implement sharing logic once it has been defined (#71374) + def date_range_set? + start_date? && finish_date? + end + + def duration + return nil unless date_range_set? + + Day.working.from_range(from: start_date, to: finish_date).count + end + private # TODO: consider moving this validation to the database level to ensure data integrity. diff --git a/modules/backlogs/app/seeders/common.yml b/modules/backlogs/app/seeders/common.yml index 1a0213b5753..f3146e87aec 100644 --- a/modules/backlogs/app/seeders/common.yml +++ b/modules/backlogs/app/seeders/common.yml @@ -30,9 +30,8 @@ modules_permissions: backlogs: - role: :default_role_member add: - - :view_master_backlog - - :view_taskboards + - :view_sprints + - :manage_sprint_items - role: :default_role_reader add: - - :view_master_backlog - - :view_taskboards + - :view_sprints diff --git a/modules/backlogs/app/services/sprints/create_service.rb b/modules/backlogs/app/services/sprints/create_service.rb new file mode 100644 index 00000000000..0feb2e40ad2 --- /dev/null +++ b/modules/backlogs/app/services/sprints/create_service.rb @@ -0,0 +1,35 @@ +# 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 Sprints::CreateService < BaseServices::Create + def instance_class + Agile::Sprint + end +end diff --git a/modules/backlogs/app/services/sprints/set_attributes_service.rb b/modules/backlogs/app/services/sprints/set_attributes_service.rb new file mode 100644 index 00000000000..22625e0b759 --- /dev/null +++ b/modules/backlogs/app/services/sprints/set_attributes_service.rb @@ -0,0 +1,74 @@ +# 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 Sprints + class SetAttributesService < ::BaseServices::SetAttributes + private + + def sprint_name_from_predecessor + return model.name unless model.new_record? + + predecessor = model.project.sprints.last + next_name_in_succession(predecessor) + end + + def set_default_attributes(_params) + set_sprint_name + set_status_and_sharing + end + + def set_sprint_name + model.name ||= sprint_name_from_predecessor + end + + def set_status_and_sharing + model.status ||= "in_planning" + model.sharing ||= "none" + end + + def next_name_in_succession(predecessor) + if predecessor.nil? + default_sprint_name + elsif (match = predecessor.name.match(/\A(.*)\s(\d+)\z/)) + # If the predecessor's name ends with a number, increment that number for the new sprint's name. + # E.g., if the previous sprint was called "Be ambitious 42", the next one will be "Be ambitious 43". + [match[1], match[2].to_i + 1].join(" ") + else + # The predecessor's name doesn't end with a number. The user has chosen a custom name. Do not assume + # how the next sprint should be called. Return an empty string and let the user choose. + "" + end + end + + def default_sprint_name + [I18n.t("activerecord.models.sprint"), 1].join(" ") + end + end +end diff --git a/modules/backlogs/app/services/sprints/update_service.rb b/modules/backlogs/app/services/sprints/update_service.rb new file mode 100644 index 00000000000..d6f618cc6e1 --- /dev/null +++ b/modules/backlogs/app/services/sprints/update_service.rb @@ -0,0 +1,35 @@ +# 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 Sprints::UpdateService < BaseServices::Update + def instance_class + Agile::Sprint + end +end diff --git a/modules/backlogs/app/views/projects/settings/backlogs/show.html.erb b/modules/backlogs/app/views/projects/settings/backlogs/show.html.erb index bb32c332dbf..11ac6fb8e62 100644 --- a/modules/backlogs/app/views/projects/settings/backlogs/show.html.erb +++ b/modules/backlogs/app/views/projects/settings/backlogs/show.html.erb @@ -29,75 +29,39 @@ See COPYRIGHT and LICENSE files for more details. <%= render Primer::OpenProject::PageHeader.new do |header| - header.with_title { t("backlogs.definition_of_done") } + header.with_title { t(:label_backlogs) } + header.with_breadcrumbs( [{ href: project_overview_path(@project), text: @project.name }, { href: project_settings_general_path(@project), text: I18n.t(:label_project_settings) }, - t("backlogs.definition_of_done")] + t(:label_backlogs)] ) + + header.with_action_menu( + menu_arguments: { anchor_align: :end }, + button_arguments: { + icon: :"kebab-horizontal", + "aria-label": t(:label_more) + } + ) do |menu| + menu.with_item( + tag: :button, + href: rebuild_positions_project_settings_backlogs_path(@project), + label: t(:"backlogs.rebuild_positions"), + form_arguments: { method: :post } + ) do |item| + item.with_leading_visual_icon(icon: :sync) + end + end end %> -<%= styled_form_tag( - project_settings_backlogs_path(@project), - method: :patch, - id: "edit_project_#{@project.id}" - ) do %> - -
-
- - - - - - - - - - - - - <% for status in (Status.all || []) %> - - - - - <% end %> - -
-
-
- - <%= Status.model_name.human %> - -
-
-
-
-
- - <%= t("backlogs.work_package_is_closed") %> - -
-
-
- <%= status.name %> - - <% checkbox_id = status.name.parameterize.underscore %> - <%= styled_label_tag checkbox_id, t("backlogs.label_is_done_status", status_name: status.name), class: "sr-only" %> - <%= (styled_check_box_tag "statuses[][status_id]", status.id.to_s, @statuses_done_for_project.include?(status.id), id: checkbox_id) %> -
-
-
-
- <%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %> -
- -<% end %> - -

<%= t("backlogs.rebuild_positions") %>

- -<%= styled_form_tag(controller: "/projects/settings/backlogs", action: "rebuild_positions", id: @project) do %> -

<%= styled_button_tag t("backlogs.rebuild"), class: "-primary" %>

+<%= + settings_primer_form_with( + url: project_settings_backlogs_path(@project), + model: @project, + method: :patch + ) do |f| +%> + <%= render Projects::Settings::BacklogsSettingsForm.new(f) %> <% end %> diff --git a/modules/backlogs/app/views/rb_master_backlogs/_agile_list.html.erb b/modules/backlogs/app/views/rb_master_backlogs/_agile_list.html.erb new file mode 100644 index 00000000000..412df5cf8ff --- /dev/null +++ b/modules/backlogs/app/views/rb_master_backlogs/_agile_list.html.erb @@ -0,0 +1,52 @@ +<%# -- 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. + +++# %> + + + <% if @owner_backlogs.empty? && @sprints.empty? %> + <%= + render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate| + blankslate.with_visual_icon(icon: :versions) + blankslate.with_heading(tag: :h2).with_content(t(:backlogs_empty_title)) + + if current_user.allowed_in_project?(:manage_versions, @project) + blankslate.with_description_content(t(:backlogs_empty_action_text)) + end + end + %> + <% else %> +
+
+ <%= render(Backlogs::SprintComponent.with_collection(@sprints)) %> +
+
+ <%= render(Backlogs::BacklogComponent.with_collection(@owner_backlogs, project: @project)) %> +
+
+ <% end %> +
diff --git a/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb b/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb index ecc41d09447..78341f2997c 100644 --- a/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb +++ b/modules/backlogs/app/views/rb_master_backlogs/_list.html.erb @@ -1,3 +1,32 @@ +<%# -- 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. + +++# %> + <%= turbo_frame_tag :backlogs_container, refresh: :morph, class: "op-backlogs-page" do %> <% if @owner_backlogs.empty? && @sprint_backlogs.empty? %> <%= @@ -5,7 +34,7 @@ blankslate.with_visual_icon(icon: :versions) blankslate.with_heading(tag: :h2).with_content(t(:backlogs_empty_title)) - if current_user.allowed_in_project?(:manage_versions, @project) + if current_user.allowed_in_project?(:create_sprints, @project) blankslate.with_description_content(t(:backlogs_empty_action_text)) end end diff --git a/modules/backlogs/app/views/rb_master_backlogs/agile_index.html.erb b/modules/backlogs/app/views/rb_master_backlogs/agile_index.html.erb new file mode 100644 index 00000000000..16194aa9c77 --- /dev/null +++ b/modules/backlogs/app/views/rb_master_backlogs/agile_index.html.erb @@ -0,0 +1,95 @@ +<%#-- 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. + +++#%> + +<% html_title t(:label_backlogs) %> + +<% content_controller "backlogs", + "backlogs-list-url-value": backlogs_project_backlogs_path(@project), + "backlogs-backlogs--story-outlet": "li[data-story]" %> + +<% content_for :content_header do %> + <%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t(:label_backlogs) } + header.with_breadcrumbs( + [{ href: project_overview_path(@project), text: @project.name }, + t(:label_backlogs)] + ) + end + %> + + <%= + render(Primer::OpenProject::SubHeader.new) do |subheader| + if allow_sprint_creation?(@project) + # Scrum sprints are available, show an action menu to create Sprints and Versions + subheader.with_action_menu( + leading_icon: :plus, + trailing_icon: :"triangle-down", + label: t(:button_create), + button_arguments: { scheme: :primary } + ) do |menu| + menu.with_item( + label: Version.human_model_name, + href: new_project_version_path(@project) + ) + + menu.with_item( + label: Agile::Sprint.human_model_name, + href: new_dialog_project_sprints_path(@project), + content_arguments: { + data: { + controller: "async-dialog", + test_selector: "op-sprints--new-sprint-button" + } + } + ) + end + else + # No scrum sprints, we only show a "+ Version" button + subheader.with_action_button( + scheme: :primary, + leading_icon: :plus, + label: I18n.t(:label_version_new), + tag: :a, + href: new_project_version_path(@project) + ) do + Version.human_model_name + end + end + end + %> +<% end %> + +<% content_for :content_body do %> + <%= render partial: "agile_list" %> +<% end %> + +<% content_for :content_body_right do %> + <%= render(split_view_instance) if render_work_package_split_view? %> +<% end %> diff --git a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb index f35c81f31a6..f6d59c4d4a7 100644 --- a/modules/backlogs/app/views/rb_master_backlogs/index.html.erb +++ b/modules/backlogs/app/views/rb_master_backlogs/index.html.erb @@ -43,17 +43,46 @@ See COPYRIGHT and LICENSE files for more details. end %> - <%= render(Primer::OpenProject::SubHeader.new) do |subheader| + <%= + render(Primer::OpenProject::SubHeader.new) do |subheader| + if allow_sprint_creation?(@project) + # Scrum sprints are available, show an action menu to create Sprints and Versions + subheader.with_action_menu( + leading_icon: :plus, + trailing_icon: :"triangle-down", + label: t(:button_create), + button_arguments: { scheme: :primary } + ) do |menu| + menu.with_item( + label: Version.human_model_name, + href: new_project_version_path(@project) + ) + + menu.with_item( + label: Agile::Sprint.human_model_name, + href: new_dialog_project_sprints_path(@project), + content_arguments: { + data: { + controller: "async-dialog", + test_selector: "op-sprints--new-sprint-button" + } + } + ) + end + else + # No scrum sprints, we only show a "+ Version" button subheader.with_action_button( scheme: :primary, leading_icon: :plus, label: I18n.t(:label_version_new), tag: :a, - href: url_for({ controller: "/versions", action: "new", project_id: @project }) + href: new_project_version_path(@project) ) do - t("activerecord.models.version") + Version.human_model_name end - end %> + end + end + %> <% end %> <% content_for :content_body do %> diff --git a/modules/backlogs/config/locales/crowdin/af.yml b/modules/backlogs/config/locales/crowdin/af.yml index 20d24018589..8f5d854a324 100644 --- a/modules/backlogs/config/locales/crowdin/af.yml +++ b/modules/backlogs/config/locales/crowdin/af.yml @@ -25,6 +25,12 @@ af: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ af: task_version_must_be_the_same_as_story_version: "moet dieselfde wees as die ouer storie se weergawe" sprint: cannot_end_before_it_starts: "sprint kan nie uinding voor dit begin het nie" + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "eenige" column_width: "Column width" definition_of_done: "Definisie van Gedoen" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Belemmering" label_versions_default_fold_state: "Wys weergawe gevou" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ af: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ af: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ af: label_backlogs_unconfigured: "Jy het nog nie Agterstandes opgestel nie. Gaan asseblief na %{administration} > %{plugins}, klik dan op die %{configure}-skakel vir hierdie plugin. Sodra jy die velde gestel het, kom terug na hierdie bladsy om die instrument te begin gebruik." label_blocks_ids: "ID's van geblokkeerde werkspakkette" label_column_in_backlog: "Kolom in agterstand" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Af" label_points_burn_up: "Op" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint belemmerings" + label_sprint_new: "New sprint" label_task_board: "Taak bord" - permission_view_master_backlog: "Kyk na meester-agterstand" - permission_view_taskboards: "Kyk na taakborde" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Kies gedoen statusse" - permission_update_sprints: "Opdateer sprinte" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Agterstandes" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ar.yml b/modules/backlogs/config/locales/crowdin/ar.yml index a00c06b69cd..1de0218f19a 100644 --- a/modules/backlogs/config/locales/crowdin/ar.yml +++ b/modules/backlogs/config/locales/crowdin/ar.yml @@ -25,6 +25,12 @@ ar: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ ar: task_version_must_be_the_same_as_story_version: "يجب أن يكون نفس نسخة القصة الأصلية." sprint: cannot_end_before_it_starts: "لا يمكن أن ينتهي السباق قبل أن يبدأ." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "أي" column_width: "Column width" definition_of_done: "تعريف ما تم" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "عائق" label_versions_default_fold_state: "إظهار الإصدارات مطوية" caption_versions_default_fold_state: "" @@ -83,12 +92,21 @@ ar: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -99,6 +117,14 @@ ar: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -126,14 +152,19 @@ ar: label_backlogs_unconfigured: "لم تقم بإنشاء الأعمال المتراكمة غير المنجزة بعد. من فضلك اذهب إلى %{administration} > %{plugins}، ثم اضغط على رابط %{configure} لهذا البرنامج المساعد. عندما تنتهي من تعيين الحقول، ارجع إلى هذه الصفحة لتبدأ باستخدام الأداة." label_blocks_ids: "الهويات المعرِّفة لمجموعات العمل المحظورة" label_column_in_backlog: "عمود في العمل المتراكم غير المنجز" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "الأسفل" label_points_burn_up: "الأعلى" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "عوائق السباق" + label_sprint_new: "New sprint" label_task_board: "لوحة المهمة" - permission_view_master_backlog: "عرض العمل الرئيسي المتراكم غير المنجز" - permission_view_taskboards: "شاهد لوحات المهمات" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "حدد حالات الاتمام" - permission_update_sprints: "قم بتحديث السباقات" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "الأعمال المتراكمة غير المنجزة" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/az.yml b/modules/backlogs/config/locales/crowdin/az.yml index 309b5b19a3f..6e7aea2b54f 100644 --- a/modules/backlogs/config/locales/crowdin/az.yml +++ b/modules/backlogs/config/locales/crowdin/az.yml @@ -25,6 +25,12 @@ az: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ az: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ az: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ az: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ az: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/be.yml b/modules/backlogs/config/locales/crowdin/be.yml index 12c334f48a8..771b833a2dc 100644 --- a/modules/backlogs/config/locales/crowdin/be.yml +++ b/modules/backlogs/config/locales/crowdin/be.yml @@ -25,6 +25,12 @@ be: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ be: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "любы" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -79,12 +88,21 @@ be: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -95,6 +113,14 @@ be: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -122,14 +148,19 @@ be: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/bg.yml b/modules/backlogs/config/locales/crowdin/bg.yml index e16dce07c86..f653c939045 100644 --- a/modules/backlogs/config/locales/crowdin/bg.yml +++ b/modules/backlogs/config/locales/crowdin/bg.yml @@ -25,6 +25,12 @@ bg: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ bg: task_version_must_be_the_same_as_story_version: "трябва да бъде същата като версията на родителската история." sprint: cannot_end_before_it_starts: "Спринтът не може да приключи, преди да започне." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "всяко" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Препядствие" label_versions_default_fold_state: "Показване на сгънати версиите " caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ bg: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ bg: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ bg: label_backlogs_unconfigured: "Все още не сте конфигурирали Неизпълнени задачи. Моля, отидете на %{administration} > %{plugins}, след което щракнете върху връзката %{configure} за този плъгин. След като зададете полетата, върнете се на тази страница, за да започнете да използвате инструмента." label_blocks_ids: "ID на блокирани работни пакети" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Надолу" label_points_burn_up: "Нагоре" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Пречки за спринт" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Изберете готови състояния" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Назад" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ca.yml b/modules/backlogs/config/locales/crowdin/ca.yml index 4d52aad8369..c7253773dba 100644 --- a/modules/backlogs/config/locales/crowdin/ca.yml +++ b/modules/backlogs/config/locales/crowdin/ca.yml @@ -25,6 +25,12 @@ ca: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ ca: task_version_must_be_the_same_as_story_version: "ha de ser la mateixa que la versió de la història pare." sprint: cannot_end_before_it_starts: "El sprint no pot acabar abans de que comenci." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "qualsevol" column_width: "Column width" definition_of_done: "Definició de fet" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Mostra les versions contretes" caption_versions_default_fold_state: "Les versions no s'expandiran per defecte quan es visualitzin els registres enrere. Cada un ha d'ampliar-se manualment." @@ -75,12 +84,21 @@ ca: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ ca: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ ca: label_backlogs_unconfigured: "No has configurat les llistes de pendents encara. Si us plau, ves a %{administration} > %{plugins}, a continuació, fes clic a l'enllaç de %{configure} per a aquest plugin. Una vegada hagis omplert els camps, torna a aquesta pàgina per començar a utilitzar l'eina." label_blocks_ids: "Identificadors dels paquets de treball bloquejats" label_column_in_backlog: "Columna al backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "A baix" label_points_burn_up: "Amunt" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Impediments de sprint" + label_sprint_new: "New sprint" label_task_board: "Tauler de tasques" - permission_view_master_backlog: "Visualitza el backlog mestre" - permission_view_taskboards: "Visualitza els taulers de tasques" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Selecciona els estats acabats" - permission_update_sprints: "Actualitza els sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ckb-IR.yml b/modules/backlogs/config/locales/crowdin/ckb-IR.yml index 3bc24142a22..60450ebe853 100644 --- a/modules/backlogs/config/locales/crowdin/ckb-IR.yml +++ b/modules/backlogs/config/locales/crowdin/ckb-IR.yml @@ -25,6 +25,12 @@ ckb-IR: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ ckb-IR: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ ckb-IR: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ ckb-IR: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ ckb-IR: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/cs.yml b/modules/backlogs/config/locales/crowdin/cs.yml index f5a7fbc7a36..de225baae06 100644 --- a/modules/backlogs/config/locales/crowdin/cs.yml +++ b/modules/backlogs/config/locales/crowdin/cs.yml @@ -25,6 +25,12 @@ cs: description: "Tento modul přidává funkce umožňující agilním týmům pracovat s OpenProject v Scrum projektech." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Doba trvání sprintu" work_package: @@ -42,12 +48,15 @@ cs: task_version_must_be_the_same_as_story_version: "musí být stejná jako verze nadřazeného příběhu." sprint: cannot_end_before_it_starts: "Sprint nemůže končit dříve, než začne." + models: + sprint: "Sprint" attributes: task_type: "Typ úlohy" backlogs: any: "jakákoliv" column_width: "Column width" definition_of_done: "Definice dokončena" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: " Verze zobrazit srolovaně" caption_versions_default_fold_state: "Verze se při prohlížení nevyřízených žádostí ve výchozím nastavení nerozbalují. Každou z nich je třeba rozbalit ručně." @@ -79,12 +88,21 @@ cs: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -95,6 +113,14 @@ cs: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -122,14 +148,19 @@ cs: label_backlogs_unconfigured: "Zatím nemáte nakonfigurované nevyřízené záznamy. Přejděte na %{administration} > %{plugins}a poté klikněte na odkaz %{configure} pro tento plugin. Jakmile nastavíte pole, vraťte se na tuto stránku a začněte používat nástroj." label_blocks_ids: "ID blokovaných pracovních balíčků" label_column_in_backlog: "Sloupec v nevyřízené pozici" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Dolů" label_points_burn_up: "Nahoru" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Běh impedimenty" + label_sprint_new: "New sprint" label_task_board: "Tabule úkolů" - permission_view_master_backlog: "Zobrazit hlavní nevyřízené položky" - permission_view_taskboards: "Zobrazit tabuly úkolů" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Vybrat stavy dokončených" - permission_update_sprints: "Aktualizovat běhy" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Nevyřízené položky" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/da.yml b/modules/backlogs/config/locales/crowdin/da.yml index b903386f5d8..d29bad1355a 100644 --- a/modules/backlogs/config/locales/crowdin/da.yml +++ b/modules/backlogs/config/locales/crowdin/da.yml @@ -25,6 +25,12 @@ da: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ da: task_version_must_be_the_same_as_story_version: "skal være identisk med hovedhistorieversionen." sprint: cannot_end_before_it_starts: "Sprint kan ikke afsluttes før det starter." + models: + sprint: "Sprint" attributes: task_type: "Opgavetype" backlogs: any: "alle" column_width: "Column width" definition_of_done: "Definition af Udført" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Hindring" label_versions_default_fold_state: "Vis versioner sammenfoldet" caption_versions_default_fold_state: "Versioner vil ikke blive udvidet som standard, når man ser på backlogs. Hver enkelt version skal udvides manuelt." @@ -75,12 +84,21 @@ da: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ da: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ da: label_backlogs_unconfigured: "Man har ikke opsat Backlogs endnu. Gå til %{administration} > %{plugins}, og klik dernæst på linket %{configure} til dette plugin. Når man har indstillet feltindhold, vend tilbage til denne side for at begynde at bruge værktøjet." label_blocks_ids: "ID'er for blokerede arbejdspakker" label_column_in_backlog: "Kolonne i backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Ned" label_points_burn_up: "Op" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint-hindringer" + label_sprint_new: "New sprint" label_task_board: "Opgaveoversigt" - permission_view_master_backlog: "Se hoved-backlog" - permission_view_taskboards: "Vis opgaveoversigter" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Vælg udført-statusser" - permission_update_sprints: "Opdatere sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/de.yml b/modules/backlogs/config/locales/crowdin/de.yml index ef0b991f0af..f2e91491024 100644 --- a/modules/backlogs/config/locales/crowdin/de.yml +++ b/modules/backlogs/config/locales/crowdin/de.yml @@ -25,6 +25,12 @@ de: description: "Dieses Modul fügt Funktionen hinzu, die es agilen Teams ermöglichen, mit OpenProject in Scrum-Projekten zu arbeiten." activerecord: attributes: + agile/sprint: + duration: "Dauer" + finish_date: "Endtermin" + goal: "Sprint-Ziel" + name: "Sprint-Name" + sharing: "Teilen" sprint: duration: "Sprint-Dauer" work_package: @@ -42,12 +48,15 @@ de: task_version_must_be_the_same_as_story_version: "muss der Version der übergeordneten Story entsprechen." sprint: cannot_end_before_it_starts: "Sprint kann nicht enden, bevor er begonnen hat." + models: + sprint: "Sprint" attributes: task_type: "Aufgaben-Typ" backlogs: any: "beliebig" column_width: "Spaltenbreite" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Hindernis" label_versions_default_fold_state: "Versionen eingeklappt anzeigen" caption_versions_default_fold_state: "Versionen werden beim Anzeigen des Backlogs standardmäßig nicht aufgeklappt. Sie müssen manuell geöffnet werden." @@ -75,12 +84,21 @@ de: backlog_component: blankslate_title: "%{name} ist leer" blankslate_description: "Noch keine Arbeitspakete geplant. Ziehen Sie Arbeitspakete hierher, um sie hinzuzufügen." + sprint_component: + blankslate_title: "%{name} ist leer" + blankslate_description: "Noch keine Arbeitspakete geplant. Ziehen Sie Arbeitspakete hierher, um sie hinzuzufügen." backlog_header_component: label_toggle_backlog: "%{name} ein-/ausklappen" label_story_count: zero: "Keine Arbeitspakete im Backlog" one: "%{count} Arbeitspakete im Backlog" other: "%{count} Geschichten im Backlog" + sprint_header_component: + label_toggle_backlog: "%{name} ein-/ausklappen" + label_story_count: + zero: "Keine Stories im Sprint" + one: "%{count} Stories im Sprint" + other: "%{count} Stories im Sprint" backlog_menu_component: label_actions: "Backlog-Aktionen" action_menu: @@ -91,6 +109,14 @@ de: burndown_chart: "Burndown-Diagramm" wiki: "Wiki" properties: "Eigenschaften" + sprint_menu_component: + label_actions: "Sprint-Aktionen" + action_menu: + edit_sprint: "Sprint bearbeiten" + new_story: "Neue Story" + stories_tasks: "Stories/Aufgaben" + task_board: "Taskboard" + burndown_chart: "Burndown-Diagramm" story_component: label_drag_story: "%{name} verschieben" story_menu_component: @@ -118,14 +144,19 @@ de: label_backlogs_unconfigured: "Sie haben noch keine Backlogs konfiguriert. Bitte gehen Sie auf %{administration} > %{plugins}, klicken Sie dann auf den %{configure} Link für dieses Plugin. Kommen Sie hierher zurück, sobald sie die Felder konfiguriert haben." label_blocks_ids: "IDs der blockierten Arbeitspakete" label_column_in_backlog: "Spalte im Backlog" + label_used_as_backlog: "Als Backlog verwendet" label_points_burn_down: "Runter" label_points_burn_up: "Hoch" + label_sprint_edit: "Sprint bearbeiten" label_sprint_impediments: "Sprint Hindernisse" + label_sprint_new: "Neuer Sprint" label_task_board: "Taskboard" - permission_view_master_backlog: "Master Backlog ansehen" - permission_view_taskboards: "Taskboard ansehen" + permission_create_sprints: "Sprints erstellen" + permission_manage_sprint_items: "Sprint-Elemente verwalten" permission_select_done_statuses: "Abgeschlossene Status auswählen" - permission_update_sprints: "Sprints bearbeiten" + permission_share_sprint: "Sprint teilen" + permission_start_complete_sprint: "Sprint starten/abschließen" + permission_view_sprints: "Sprints ansehen" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/el.yml b/modules/backlogs/config/locales/crowdin/el.yml index 53021e93381..0d1a7eac949 100644 --- a/modules/backlogs/config/locales/crowdin/el.yml +++ b/modules/backlogs/config/locales/crowdin/el.yml @@ -25,6 +25,12 @@ el: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ el: task_version_must_be_the_same_as_story_version: "πρέπει να είναι το ίδιο με την έκδοση ιστορίας του γονέα." sprint: cannot_end_before_it_starts: "Το sprint δεν μπορεί να λήξει πριν αρχίσει." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "οποιoδήποτε" column_width: "Column width" definition_of_done: "Ορισμός των Ολοκληρωμένων" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Εμπόδιο" label_versions_default_fold_state: "Εμφάνιση συμπτυγμένων εκδόσεων" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ el: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ el: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ el: label_backlogs_unconfigured: "Δεν έχετε διαμορφώσει τα Backlogs ακόμη. Παρακαλούμε πηγαίνετε στο %{administration} > %{plugins}, έπειτα κάντε κλικ στον σύνδεσμο %{configure} για αυτό το πρόσθετο. Μόλις έχετε ορίσει τα πεδία, επιστρέψτε σε αυτή την σελίδα για να αρχίσετε να χρησιμοποιείτε το εργαλείο." label_blocks_ids: "Ταυτότητες μπλοκαρισμένων πακέτων εργασίας" label_column_in_backlog: "Στήλη στο backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Κάτω" label_points_burn_up: "Πάνω" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Εμπόδια Sprint" + label_sprint_new: "New sprint" label_task_board: "Πίνακας εργασιών" - permission_view_master_backlog: "Εμφάνιση του κύριου backlog" - permission_view_taskboards: "Εμφάνιση πινάκων εργασίας" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Ενημέρωση των sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/eo.yml b/modules/backlogs/config/locales/crowdin/eo.yml index 9823ba39aec..82f8ebbb1c3 100644 --- a/modules/backlogs/config/locales/crowdin/eo.yml +++ b/modules/backlogs/config/locales/crowdin/eo.yml @@ -25,6 +25,12 @@ eo: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ eo: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "ajna" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ eo: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ eo: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ eo: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "ID de baritaj laborpakaĵoj" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Malsupren" label_points_burn_up: "Supren" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/es.yml b/modules/backlogs/config/locales/crowdin/es.yml index f019f9814a1..09b6a6aef4f 100644 --- a/modules/backlogs/config/locales/crowdin/es.yml +++ b/modules/backlogs/config/locales/crowdin/es.yml @@ -25,6 +25,12 @@ es: description: "Este módulo añade funciones que permiten a los equipos Agile trabajar con OpenProject en proyectos Scrum." activerecord: attributes: + agile/sprint: + duration: "Duración" + finish_date: "Fecha de finalización" + goal: "Objetivo del sprint" + name: "Nombre del sprint" + sharing: "Uso compartido" sprint: duration: "Duración del sprint" work_package: @@ -42,12 +48,15 @@ es: task_version_must_be_the_same_as_story_version: "debe coincidir con la versión de la historia principal." sprint: cannot_end_before_it_starts: "El sprint no puede terminar antes del inicio." + models: + sprint: "Sprint" attributes: task_type: "Tipo de tarea" backlogs: any: "cualquiera" column_width: "Ancho de columna" definition_of_done: "Criterio de Aceptación" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impedimento" label_versions_default_fold_state: "Mostrar versiones colapsadas" caption_versions_default_fold_state: "Las versiones no se expandirán por defecto al visualizar los trabajos pendientes. Cada una deberá expandirse manualmente." @@ -75,12 +84,21 @@ es: backlog_component: blankslate_title: "%{name} está vacío" blankslate_description: "Aún no hay elementos previstos. Arrastre los elementos aquí para añadirlos." + sprint_component: + blankslate_title: "%{name} está vacío" + blankslate_description: "Aún no hay elementos previstos. Arrastre los elementos aquí para añadirlos." backlog_header_component: label_toggle_backlog: "Contraer/expandir %{name}" label_story_count: zero: "No hay historias en backlog" one: "%{count} historia en backlog" other: "%{count} historias en backlog" + sprint_header_component: + label_toggle_backlog: "Contraer/expandir %{name}" + label_story_count: + zero: "No hay historias en sprint" + one: "%{count} historia en sprint" + other: "%{count} historias en sprint" backlog_menu_component: label_actions: "Acciones de backlog" action_menu: @@ -91,6 +109,14 @@ es: burndown_chart: "Diagrama Burndown" wiki: "Wiki" properties: "Propiedades" + sprint_menu_component: + label_actions: "Acciones de sprint" + action_menu: + edit_sprint: "Editar sprint" + new_story: "Nueva historia" + stories_tasks: "Historias/tareas" + task_board: "Panel de tareas" + burndown_chart: "Diagrama Burndown" story_component: label_drag_story: "Mover %{name}" story_menu_component: @@ -118,14 +144,19 @@ es: label_backlogs_unconfigured: "Todavía no ha configurado backlogs. Por favor, visite %{administration} > %{plugins}, luego haga clic en el enlace de %{configure} para esta extensión. Cuando haya establecido los campos, vuelva a esta página para empezar a usar la herramienta." label_blocks_ids: "ID de los paquetes de trabajo bloqueados" label_column_in_backlog: "Columna en backlog" + label_used_as_backlog: "Se usa como backlog" label_points_burn_down: "Abajo" label_points_burn_up: "Arriba" + label_sprint_edit: "Editar sprint" label_sprint_impediments: "Impedimentos de sprint" + label_sprint_new: "Nuevo sprint" label_task_board: "Tablero de tareas" - permission_view_master_backlog: "Ver backlog maestro" - permission_view_taskboards: "Ver tablero de tareas" + permission_create_sprints: "Crear sprints" + permission_manage_sprint_items: "Gestionar elementos de sprint" permission_select_done_statuses: "Seleccionar estados de finalización" - permission_update_sprints: "Actualizar sprints" + permission_share_sprint: "Compartir sprint" + permission_start_complete_sprint: "Iniciar/completar sprint" + permission_view_sprints: "Ver sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/et.yml b/modules/backlogs/config/locales/crowdin/et.yml index 70ca6ef2f2f..7c7d3d3daa6 100644 --- a/modules/backlogs/config/locales/crowdin/et.yml +++ b/modules/backlogs/config/locales/crowdin/et.yml @@ -25,6 +25,12 @@ et: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ et: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ et: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ et: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ et: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Alla" label_points_burn_up: "Üles" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Vali valmis staatused" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/eu.yml b/modules/backlogs/config/locales/crowdin/eu.yml index aee74542e1a..30e0af5f5cb 100644 --- a/modules/backlogs/config/locales/crowdin/eu.yml +++ b/modules/backlogs/config/locales/crowdin/eu.yml @@ -25,6 +25,12 @@ eu: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ eu: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "edozein" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ eu: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ eu: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ eu: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Behera" label_points_burn_up: "Gora" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/fa.yml b/modules/backlogs/config/locales/crowdin/fa.yml index f6111ae7bb9..510a1c9685b 100644 --- a/modules/backlogs/config/locales/crowdin/fa.yml +++ b/modules/backlogs/config/locales/crowdin/fa.yml @@ -25,6 +25,12 @@ fa: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "مدت زمان اسپرینت" work_package: @@ -42,12 +48,15 @@ fa: task_version_must_be_the_same_as_story_version: "باید از همان نگارش داستان زادآوران باشد." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "نوع وظیفه" backlogs: any: "any" column_width: "عرض ستون" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "در هنگام مشاهده backlog ها، نسخه‌ها به صورت پیش‌فرض بازنخواهندشد. هر کدام باید به صورت دستی باز شوند." @@ -75,12 +84,21 @@ fa: backlog_component: blankslate_title: "%{name} خالی است" blankslate_description: "هنوز موردی برنامه ریزی نشده است. موارد را برای اضافه کردن به اینجا بکشید." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "جمع کردن/باز کردن %{name}" label_story_count: zero: "هیچ داستانی در لیست انتظار نیست" one: "%{count} داستان در لیست کار های عقب افتاده" other: "%{count} داستان های در لیست کارهای عقب افتاده" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "اقدامات عقب افتاده" action_menu: @@ -91,6 +109,14 @@ fa: burndown_chart: "نمودار خلاصه" wiki: "ویکی" properties: "ویژگی ها" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "انتقال %{name}" story_menu_component: @@ -118,14 +144,19 @@ fa: label_backlogs_unconfigured: "شما هنوز پس‌افت‌ها را پیکربندی نکرده‌اید. لطفاً به %{administration} > %{plugins} بروید، سپس برای این افزونه روی %{configure} کلیک کنید. وقتی همۀ قسمت‌ها را تکمیل کردید، برای استفاده از این ابزار به همین صفحه برگردید. " label_blocks_ids: "شناسه‌های مسدود کاربسته‌ها" label_column_in_backlog: "ستون در پس‌افت" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "پایین" label_points_burn_up: "بالا" + label_sprint_edit: "Edit sprint" label_sprint_impediments: " موانع تاخت" + label_sprint_new: "New sprint" label_task_board: "تابلوی وظیفه" - permission_view_master_backlog: "نمایش بک لاگ اصلی" - permission_view_taskboards: "مشاهده تابلو وظایف" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "انتخاب وضعیت های انجام شده" - permission_update_sprints: "بروزرسانی دوره‌ها" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "وظایف" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/fi.yml b/modules/backlogs/config/locales/crowdin/fi.yml index b2b927ccdc8..853ef47c81f 100644 --- a/modules/backlogs/config/locales/crowdin/fi.yml +++ b/modules/backlogs/config/locales/crowdin/fi.yml @@ -25,6 +25,12 @@ fi: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ fi: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprintti ei voi päättyä, ennen kuin se alkaa." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "kaikki" column_width: "Column width" definition_of_done: "Valmiin määritelmä" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Este" label_versions_default_fold_state: "Näytä versiot ryhmiteltyinä" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ fi: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ fi: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ fi: label_backlogs_unconfigured: "Et ole määrittänyt vielä työjonoja. Siirry menuun %{administration} > %{plugins}, sitten klikkaa %{configure} linkkiä tälle liitännäiselle. Kun olet määrittänyt kentät, tule takaisin tälle sivulle aloittaaksesi työkalun käytön." label_blocks_ids: "Estettyjen työpakettien tunnukset" label_column_in_backlog: "Sarake työjonossa" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Alas" label_points_burn_up: "Ylös" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprintin esteet" + label_sprint_new: "New sprint" label_task_board: "Tehtävätaulu" - permission_view_master_backlog: "Näytä pääasiallinen työjono" - permission_view_taskboards: "Näytä työtaulut" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Päivitä sprinttejä" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Työjonot" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/fil.yml b/modules/backlogs/config/locales/crowdin/fil.yml index 6f0109336b2..eaa59519e54 100644 --- a/modules/backlogs/config/locales/crowdin/fil.yml +++ b/modules/backlogs/config/locales/crowdin/fil.yml @@ -25,6 +25,12 @@ fil: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ fil: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ fil: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ fil: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ fil: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "Mga ID ng naka-block na mga pakete sa gumagawa" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/fr.yml b/modules/backlogs/config/locales/crowdin/fr.yml index 16aacdfeab4..eb04e9ebdb1 100644 --- a/modules/backlogs/config/locales/crowdin/fr.yml +++ b/modules/backlogs/config/locales/crowdin/fr.yml @@ -25,6 +25,12 @@ fr: description: "Ce module ajoute des fonctionnalités permettant aux équipes agiles de travailler avec OpenProject dans le cadre de projets Scrum." activerecord: attributes: + agile/sprint: + duration: "Durée" + finish_date: "Date de fin" + goal: "Objectif du sprint" + name: "Nom du sprint" + sharing: "Partage" sprint: duration: "Durée du sprint" work_package: @@ -42,12 +48,15 @@ fr: task_version_must_be_the_same_as_story_version: "doit être identique à la version de l'histoire parente." sprint: cannot_end_before_it_starts: "Un sprint ne peut pas se terminer avant d'avoir débuté." + models: + sprint: "Sprint" attributes: task_type: "Type de tâche" backlogs: any: "tout" column_width: "Largeur de colonne" definition_of_done: "Définition de Fait" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Obstacle" label_versions_default_fold_state: "Afficher les versions de manière repliée" caption_versions_default_fold_state: "Les versions ne seront pas développées par défaut lors de l'affichage des backlogs. Chacune devra être développée manuellement." @@ -75,12 +84,21 @@ fr: backlog_component: blankslate_title: "%{name} est vide" blankslate_description: "Aucun élément n'a encore été planifié. Faites glisser les éléments ici pour les ajouter." + sprint_component: + blankslate_title: "%{name} est vide" + blankslate_description: "Aucun élément n'a encore été planifié. Faites glisser les éléments ici pour les ajouter." backlog_header_component: label_toggle_backlog: "Réduire/développer %{name}" label_story_count: zero: "Aucune histoire dans le backlog" one: "%{count} histoire dans le backlog" other: "%{count} histoires dans le backlog" + sprint_header_component: + label_toggle_backlog: "Réduire/développer %{name}" + label_story_count: + zero: "Aucune story dans le sprint" + one: "%{count} story dans le sprint" + other: "%{count} stories dans le sprint" backlog_menu_component: label_actions: "Actions dans le backlog" action_menu: @@ -91,6 +109,14 @@ fr: burndown_chart: "Graphique burndown" wiki: "Wiki" properties: "Propriétés" + sprint_menu_component: + label_actions: "Actions du sprint" + action_menu: + edit_sprint: "Modifier le sprint" + new_story: "Nouvelle story" + stories_tasks: "Stories/tâches" + task_board: "Tableau des tâches" + burndown_chart: "Graphique burndown" story_component: label_drag_story: "Déplacer %{name}" story_menu_component: @@ -118,14 +144,19 @@ fr: label_backlogs_unconfigured: "Vous n'avez pas encore configuré Backlogs. Veuillez vous rendre dans %{administration} > %{plugins}, puis cliquer sur le lien %{configure} pour ce plugin. Une fois que vous avez défini les champs, revenez sur cette page pour commencer à utiliser l'outil." label_blocks_ids: "ID des lots de travaux bloqués" label_column_in_backlog: "Colonne dans le backlog" + label_used_as_backlog: "Utilisé comme backlog" label_points_burn_down: "Vers le bas" label_points_burn_up: "Vers le haut" + label_sprint_edit: "Modifier le sprint" label_sprint_impediments: "Obstacles de sprint" + label_sprint_new: "Nouveau sprint" label_task_board: "Tableau des tâches" - permission_view_master_backlog: "Afficher le backlog principal" - permission_view_taskboards: "Voir les tableaux des tâches" + permission_create_sprints: "Créer des sprints" + permission_manage_sprint_items: "Gérer les éléments du sprint" permission_select_done_statuses: "Sélectionner les statuts terminés" - permission_update_sprints: "Éditer les sprints" + permission_share_sprint: "Partager le sprint" + permission_start_complete_sprint: "Commencer/terminer le sprint" + permission_view_sprints: "Voir les sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/he.yml b/modules/backlogs/config/locales/crowdin/he.yml index a3d2729ba67..31015e32fb7 100644 --- a/modules/backlogs/config/locales/crowdin/he.yml +++ b/modules/backlogs/config/locales/crowdin/he.yml @@ -25,6 +25,12 @@ he: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ he: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "הכל" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -79,12 +88,21 @@ he: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -95,6 +113,14 @@ he: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -122,14 +148,19 @@ he: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "המזהים של חבילות עבודה חסומים" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/hi.yml b/modules/backlogs/config/locales/crowdin/hi.yml index c1624aaae8f..c4d105746f4 100644 --- a/modules/backlogs/config/locales/crowdin/hi.yml +++ b/modules/backlogs/config/locales/crowdin/hi.yml @@ -25,6 +25,12 @@ hi: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ hi: task_version_must_be_the_same_as_story_version: "मूल कहानी के संस्करण के समान होना चाहिए।" sprint: cannot_end_before_it_starts: "स्प्रिंट शुरू होने से पहले खत्म नहीं हो सकता।" + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "कोई" column_width: "Column width" definition_of_done: "पूर्ण की परिभाषा" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "बाधा" label_versions_default_fold_state: "मुड़े हुए संस्करण दिखाएं" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ hi: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ hi: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ hi: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/hr.yml b/modules/backlogs/config/locales/crowdin/hr.yml index 4a46ad8f6c5..f4318fddb9e 100644 --- a/modules/backlogs/config/locales/crowdin/hr.yml +++ b/modules/backlogs/config/locales/crowdin/hr.yml @@ -25,6 +25,12 @@ hr: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ hr: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Period razvoja ne može završiti prije nego li je započeo." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "bilo koji" column_width: "Column width" definition_of_done: "Definicija učinjenog" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Teškoće" label_versions_default_fold_state: "Prikaži prikupljene verzije" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -77,12 +86,21 @@ hr: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -93,6 +111,14 @@ hr: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -120,14 +146,19 @@ hr: label_backlogs_unconfigured: "Niste još konfigurirali Backlog. Za konfiguraciju odaberite %{administration}>%{plugins}, zatim %{configure} Backlog dodatak. Nakon što ste uredili potrebna polja, vratite se na ovu stranicu da biste započeli s korištenjem ovog alata." label_blocks_ids: "ID blokiranih radnih paketa" label_column_in_backlog: "Stupac u backlogu" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Dolje" label_points_burn_up: "Gore" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Prepreke perioda razvoja" + label_sprint_new: "New sprint" label_task_board: "Upravitelj zadatcima" - permission_view_master_backlog: "Pogledaj glavni backlog" - permission_view_taskboards: "Pogledaj upravitelj zadatcima" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Ažuriraj periode razvoja" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/hu.yml b/modules/backlogs/config/locales/crowdin/hu.yml index 7aab8818879..2bbdd49a147 100644 --- a/modules/backlogs/config/locales/crowdin/hu.yml +++ b/modules/backlogs/config/locales/crowdin/hu.yml @@ -25,6 +25,12 @@ hu: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ hu: task_version_must_be_the_same_as_story_version: "a szülő story verziójával megegyezőnek kell lennie." sprint: cannot_end_before_it_starts: "Sprint nem érhet véget, mielőtt elindul." + models: + sprint: "Sprint" attributes: task_type: "Feladat típusa" backlogs: any: "bármely" column_width: "Column width" definition_of_done: "A \"Kész\" meghatározása" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Akadály" label_versions_default_fold_state: "Összecsukott verziók mutatása" caption_versions_default_fold_state: "A verziók alapértelmezés szerint nem lesznek kibontva a várólista (backlog) megtekintésekor. Mindegyiket manuálisan kell kibontani." @@ -75,12 +84,21 @@ hu: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ hu: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ hu: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Le" label_points_burn_up: "Fel" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint akadályai" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Elkészültek kiválasztása" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/id.yml b/modules/backlogs/config/locales/crowdin/id.yml index 39f4116d3c8..2f03ef740e4 100644 --- a/modules/backlogs/config/locales/crowdin/id.yml +++ b/modules/backlogs/config/locales/crowdin/id.yml @@ -25,6 +25,12 @@ id: description: "Modul ini menambahkan fitur yang memungkinkan tim yang gesit bekerja dengan OpenProject dalam proyek Scrum." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ id: task_version_must_be_the_same_as_story_version: "harus sama seperti versi kisah orang tua." sprint: cannot_end_before_it_starts: "Sprint tidak bisa berakhir sebelum mulai." + models: + sprint: "Sprint" attributes: task_type: "Jenis tugas" backlogs: any: "apapun" column_width: "Column width" definition_of_done: "Definisi Selesai" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Halangan" label_versions_default_fold_state: "Tampilkan versi terlipat" caption_versions_default_fold_state: "Versi tidak akan diperluas secara default saat melihat daftar tugas yang tertunda. Setiap versi harus diperluas secara manual." @@ -73,12 +82,21 @@ id: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -89,6 +107,14 @@ id: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -116,14 +142,19 @@ id: label_backlogs_unconfigured: "Anda belum mengkonfigurasi Backlogs. Silakan masuk ke %{administration}> %{plugins}, lalu klik pada link %{configure} untuk plugin ini. Setelah Anda menyetel bidang, kembali ke halaman ini untuk mulai menggunakan alat ini." label_blocks_ids: "ID dari paket pekerjaan yang diblokir" label_column_in_backlog: "Kolom di backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Menurun" label_points_burn_up: "Naik" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Hanbatan kekuatan" + label_sprint_new: "New sprint" label_task_board: "Papan tugas" - permission_view_master_backlog: "Lihat backlog master" - permission_view_taskboards: "Lihat papan tugas" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Pilih status selesai" - permission_update_sprints: "Perbarui sprint" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/it.yml b/modules/backlogs/config/locales/crowdin/it.yml index 4caea4de2d5..92bda374c47 100644 --- a/modules/backlogs/config/locales/crowdin/it.yml +++ b/modules/backlogs/config/locales/crowdin/it.yml @@ -25,6 +25,12 @@ it: description: "Questo modulo aggiunge funzionalità che consentono ai team agili di lavorare con i progetti OpenProject in Scrum." activerecord: attributes: + agile/sprint: + duration: "Durata" + finish_date: "Data di fine" + goal: "Obiettivo dello sprint" + name: "Nome dello sprint" + sharing: "Condivisione" sprint: duration: "Durata dello sprint" work_package: @@ -42,12 +48,15 @@ it: task_version_must_be_the_same_as_story_version: "deve essere uguale alla versione della storia del genitore." sprint: cannot_end_before_it_starts: "Uno sprint non può terminare prima che venga avviato." + models: + sprint: "Sprint" attributes: task_type: "Tipo di attività" backlogs: any: "qualsiasi" column_width: "Larghezza della colonna" definition_of_done: "Definizione di fatto" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impedimento" label_versions_default_fold_state: "Espandi le versioni" caption_versions_default_fold_state: "Le versioni non verranno espanse per impostazione predefinita durante la visualizzazione dei backlog. Ogni versione deve essere espansa manualmente." @@ -75,12 +84,21 @@ it: backlog_component: blankslate_title: "%{name} è vuoto" blankslate_description: "Nessun elemento pianificato ancora. Trascina gli elementi qui per aggiungerli." + sprint_component: + blankslate_title: "%{name} è vuoto" + blankslate_description: "Nessun elemento pianificato ancora. Trascina gli elementi qui per aggiungerli." backlog_header_component: label_toggle_backlog: "Comprimi/Espandi %{name}" label_story_count: zero: "Nessuna story nel backlog" one: "%{count} story nel backlog" other: "%{count} story nel backlog" + sprint_header_component: + label_toggle_backlog: "Comprimi/Espandi %{name}" + label_story_count: + zero: "Nessuna story nello sprint" + one: "%{count} story nello sprint" + other: "%{count} story nello sprint" backlog_menu_component: label_actions: "Azioni nel backlog" action_menu: @@ -91,6 +109,14 @@ it: burndown_chart: "Grafico Burndown" wiki: "Wiki" properties: "Proprietà" + sprint_menu_component: + label_actions: "Azioni dello sprint" + action_menu: + edit_sprint: "Modifica sprint" + new_story: "Nuova story" + stories_tasks: "Story/Task" + task_board: "Bacheca dei task" + burndown_chart: "Grafico Burndown" story_component: label_drag_story: "Sposta %{name}" story_menu_component: @@ -118,14 +144,19 @@ it: label_backlogs_unconfigured: "Non hai ancora configurato i Backlog. Vai su %{administration} > %{plugins}, quindi fai clic sul link %{configure} per il plugin. Dopo aver impostato i campi, torna su questa pagina per iniziare a utilizzare lo strumento." label_blocks_ids: "ID dei pacchetti di lavoro bloccati" label_column_in_backlog: "Colonna nel backlog" + label_used_as_backlog: "Usato come backlog" label_points_burn_down: "Verso il basso" label_points_burn_up: "Verso l'alto" + label_sprint_edit: "Modifica sprint" label_sprint_impediments: "Impedimenti allo sprint" + label_sprint_new: "Nuovo sprint" label_task_board: "Pannello delle attività" - permission_view_master_backlog: "Visualizza il master backlog" - permission_view_taskboards: "Visualizza i pannelli delle attività" + permission_create_sprints: "Crea gli sprint" + permission_manage_sprint_items: "Gestisci gli elementi dello sprint" permission_select_done_statuses: "Seleziona gli stati terminati" - permission_update_sprints: "Aggiorna gli sprint" + permission_share_sprint: "Condividi lo sprint" + permission_start_complete_sprint: "Iniziare/completa lo sprint" + permission_view_sprints: "Visualizza gli sprint" project_module_backlogs: "Backlog" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ja.yml b/modules/backlogs/config/locales/crowdin/ja.yml index 5af9e30d172..4eb545ca810 100644 --- a/modules/backlogs/config/locales/crowdin/ja.yml +++ b/modules/backlogs/config/locales/crowdin/ja.yml @@ -25,6 +25,12 @@ ja: description: "このモジュールには、アジャイルチームがスクラムプロジェクトでOpenProjectを使用できるようにする機能が追加されています。" activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ ja: task_version_must_be_the_same_as_story_version: "親ストーリーのバージョンと同じでなければなりません。" sprint: cannot_end_before_it_starts: "スプリントは開始する前に終了できません。" + models: + sprint: "Sprint" attributes: task_type: "タスクのタイプ" backlogs: any: "全て" column_width: "Column width" definition_of_done: "「終了」の定義" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "障害事項" label_versions_default_fold_state: "バージョンを折り畳んで表示" caption_versions_default_fold_state: "バックログを表示する場合、デフォルトではバージョンは展開されません。各バージョンは手動で展開する必要があります。" @@ -73,12 +82,21 @@ ja: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -89,6 +107,14 @@ ja: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -116,14 +142,19 @@ ja: label_backlogs_unconfigured: "バックログは未設定です。%{administration} > %{plugins}をアクセスして、このプラグインの%{configure}リンクをクリックしてください。フィールドを設定した後、このページに戻ってツールを使用開始してください。" label_blocks_ids: "ブロックされているワークパッケージのID" label_column_in_backlog: "バックログの列" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "ダウン" label_points_burn_up: "アップ" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "スプリント障害事項" + label_sprint_new: "New sprint" label_task_board: "かんばん" - permission_view_master_backlog: "マスター バックログの表示" - permission_view_taskboards: "かんばんの表示" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "完了ステータスを選択" - permission_update_sprints: "スプリントの更新" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "バックログ" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ka.yml b/modules/backlogs/config/locales/crowdin/ka.yml index 9939b839c08..800f0ec5014 100644 --- a/modules/backlogs/config/locales/crowdin/ka.yml +++ b/modules/backlogs/config/locales/crowdin/ka.yml @@ -25,6 +25,12 @@ ka: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ ka: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "ნებისმიერი" column_width: "Column width" definition_of_done: "დასრულების აღწერა" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "წინააღმდეგობა" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ ka: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ ka: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ ka: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "ქვემოთ" label_points_burn_up: "ზემოთ" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "ამოცანების დაფა" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "აირჩიეთ დასრულების სტატუსები" - permission_update_sprints: "სპრინტების განახლება" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "შეუსრულებელი ამოცანები" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/kk.yml b/modules/backlogs/config/locales/crowdin/kk.yml index 584b33393b7..8a82b88509c 100644 --- a/modules/backlogs/config/locales/crowdin/kk.yml +++ b/modules/backlogs/config/locales/crowdin/kk.yml @@ -25,6 +25,12 @@ kk: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ kk: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ kk: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ kk: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ kk: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ko.yml b/modules/backlogs/config/locales/crowdin/ko.yml index 3127941ab0c..4b2ee8833fb 100644 --- a/modules/backlogs/config/locales/crowdin/ko.yml +++ b/modules/backlogs/config/locales/crowdin/ko.yml @@ -25,6 +25,12 @@ ko: description: "이 모듈은 애자일 팀이 Scrum 프로젝트에서 OpenProject로 작업할 수 있도록 하는 기능을 추가합니다." activerecord: attributes: + agile/sprint: + duration: "기간" + finish_date: "완료 날짜" + goal: "스프린트 목표" + name: "스프린트 이름" + sharing: "공유" sprint: duration: "스프린트 기간" work_package: @@ -42,12 +48,15 @@ ko: task_version_must_be_the_same_as_story_version: "은(는) 부모 스토리의 버전과 동일해야 합니다." sprint: cannot_end_before_it_starts: "스프린트는 시작되기 전에 끝날 수 없습니다." + models: + sprint: "스프린트" attributes: task_type: "작업 유형" backlogs: any: "모두" column_width: "열 너비" definition_of_done: "완료 정의" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "제한" label_versions_default_fold_state: "접힌 버전 표시" caption_versions_default_fold_state: "백로그를 볼 때 버전은 기본적으로 확장되지 않습니다. 각 버전을 수동으로 확장해야 합니다." @@ -73,12 +82,21 @@ ko: backlog_component: blankslate_title: "%{name}이(가) 비어 있습니다" blankslate_description: "아직 계획된 항목이 없습니다. 항목을 추가하려면 여기로 드래그하세요." + sprint_component: + blankslate_title: "%{name}이(가) 비어 있습니다" + blankslate_description: "아직 계획된 항목이 없습니다. 항목을 추가하려면 여기로 드래그하세요." backlog_header_component: label_toggle_backlog: "%{name} 축소/확장" label_story_count: zero: "백로그에 스토리 없음" one: "백로그의 %{count}개 스토리" other: "백로그의 %{count}개 스토리" + sprint_header_component: + label_toggle_backlog: "%{name} 축소/확장" + label_story_count: + zero: "스프린트의 스토리 없음" + one: "스프린트의 %{count}개 스토리" + other: "스프린트의 %{count}개 스토리" backlog_menu_component: label_actions: "백로그 작업" action_menu: @@ -89,6 +107,14 @@ ko: burndown_chart: "번다운 차트" wiki: "위키" properties: "속성" + sprint_menu_component: + label_actions: "스프린트 작업" + action_menu: + edit_sprint: "스프린트 편집" + new_story: "새로운 스토리" + stories_tasks: "스토리/작업" + task_board: "작업 보드" + burndown_chart: "번다운 차트" story_component: label_drag_story: "%{name} 이동" story_menu_component: @@ -116,14 +142,19 @@ ko: label_backlogs_unconfigured: "백로그를 아직 구성하지 않았습니다. %{administration} > %{plugins}(으)로 이동한 다음 이 플러그인의 %{configure} 링크를 클릭하세요. 필드를 설정한 후 이 페이지로 돌아가서 해당 도구 사용을 시작하세요." label_blocks_ids: "차단된 작업 패키지의 ID" label_column_in_backlog: "백로그의 열" + label_used_as_backlog: "백로그로 사용됨" label_points_burn_down: "아래" label_points_burn_up: "위" + label_sprint_edit: "스프린트 편집" label_sprint_impediments: "스프린트 제한" + label_sprint_new: "새로운 스프린트" label_task_board: "작업 보드" - permission_view_master_backlog: "마스터 백로그 보기" - permission_view_taskboards: "작업 보드 보기" + permission_create_sprints: "스프린트 만들기" + permission_manage_sprint_items: "스프린트 항목 관리" permission_select_done_statuses: "완료 상태 선택" - permission_update_sprints: "스프린트 업데이트" + permission_share_sprint: "스프린트 공유" + permission_start_complete_sprint: "스프린트 시작/완료" + permission_view_sprints: "스프린트 보기" project_module_backlogs: "백로그" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/lt.yml b/modules/backlogs/config/locales/crowdin/lt.yml index 47f4923d387..af7ad730f97 100644 --- a/modules/backlogs/config/locales/crowdin/lt.yml +++ b/modules/backlogs/config/locales/crowdin/lt.yml @@ -25,6 +25,12 @@ lt: description: "Šis modulis prideda funkcionalumą, leidžianti agile komandoms dirbti su OpenProject Scrum projektuose." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ lt: task_version_must_be_the_same_as_story_version: "turi būti tokia pati kaip ir tėvinės istorijos versija." sprint: cannot_end_before_it_starts: "Sprintas negali baigtis prieš prasidėdamas." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "bet koks" column_width: "Column width" definition_of_done: "Pabaigimo apibrėžimas" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Kliūtis" label_versions_default_fold_state: "Rodyti suskleistas versijas" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -79,12 +88,21 @@ lt: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -95,6 +113,14 @@ lt: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -122,14 +148,19 @@ lt: label_backlogs_unconfigured: "Jūs dar nesukonfigūravote Darbų sąrašų. Prašome eiti į %{administration} > %{plugins}, tada nuspausti ant %{configure} nuorodos šiam įskiepiui. Kai nustatysite laukus, grįžkite čia ir pradėkite naudoti instrumentą." label_blocks_ids: "Blokuotų darbų paketų ID reikšmės" label_column_in_backlog: "Stulpelis darbų sąraše" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Žemyn" label_points_burn_up: "Aukštyn" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprinto trukdžiai" + label_sprint_new: "New sprint" label_task_board: "Užduočių lenta" - permission_view_master_backlog: "Peržiūrėti pagrindinį darbų sąrašą" - permission_view_taskboards: "Peržiūrėti užduočių lentas" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Parinkite atliktas būsenas" - permission_update_sprints: "Atnaujinti sprintus" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Darbų sąrašai" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/lv.yml b/modules/backlogs/config/locales/crowdin/lv.yml index 2adefaeaf7a..c4ae62c40ed 100644 --- a/modules/backlogs/config/locales/crowdin/lv.yml +++ b/modules/backlogs/config/locales/crowdin/lv.yml @@ -25,6 +25,12 @@ lv: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ lv: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprints nevar beigties, pirms tas ir sācies." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "visi" column_width: "Column width" definition_of_done: "Pabeigtības definīcija" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Šķēršļi" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -77,12 +86,21 @@ lv: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -93,6 +111,14 @@ lv: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -120,14 +146,19 @@ lv: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Atlikušo darbu backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Lejup" label_points_burn_up: "Augšup" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprinta šķēršļi" + label_sprint_new: "New sprint" label_task_board: "Pieteikumu tāfele" - permission_view_master_backlog: "Skatīt visus nepabeigtos darbus" - permission_view_taskboards: "Apskatīt uzdevumu dēļus" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Izvēlieties pabeigtības statusu" - permission_update_sprints: "Atjaunināt sprintus" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Darbu krātuve" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/mn.yml b/modules/backlogs/config/locales/crowdin/mn.yml index 3bc7baea066..4b9ae429fe0 100644 --- a/modules/backlogs/config/locales/crowdin/mn.yml +++ b/modules/backlogs/config/locales/crowdin/mn.yml @@ -25,6 +25,12 @@ mn: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ mn: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "ямар ч" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ mn: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ mn: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ mn: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ms.yml b/modules/backlogs/config/locales/crowdin/ms.yml index d3ea60be5c1..d93c45c1701 100644 --- a/modules/backlogs/config/locales/crowdin/ms.yml +++ b/modules/backlogs/config/locales/crowdin/ms.yml @@ -25,6 +25,12 @@ ms: description: "Modul ini menambahkan fitur-fitur yang membolehkan kumpulan-kumpulan yang tangkas untuk bekerja menggunakan OpenProject di projek Scrum." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ ms: task_version_must_be_the_same_as_story_version: "perlu sama dengan versi cerita induk." sprint: cannot_end_before_it_starts: "Pecutan tidak boleh berakhir lebih awal sebelum ia bermula." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "sebarang" column_width: "Column width" definition_of_done: "Definisi Selesai" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Halangan" label_versions_default_fold_state: "Paparkan versi dilipat" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -73,12 +82,21 @@ ms: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -89,6 +107,14 @@ ms: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -116,14 +142,19 @@ ms: label_backlogs_unconfigured: "Anda masih belum mengkonfigurasi tunggakan. Sila pergi ke %{administration} > %{plugins}, kemudian klik pautan %{configure} untuk plugin ini. Setelah anda menetapkan ruangan, kembali ke halaman ini untuk mula menggunakan alat ini." label_blocks_ids: "ID pakej kerja yang disekat" label_column_in_backlog: "Kolum dalam tunggakan" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Bawah" label_points_burn_up: "Atas" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Halangan Pecutan" + label_sprint_new: "New sprint" label_task_board: "Papan tugasan" - permission_view_master_backlog: "Paparkan tunggakan utama" - permission_view_taskboards: "Lihat papan tugasan" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Pilih status selesai" - permission_update_sprints: "Kemas kini pecutan" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Tunggakan" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ne.yml b/modules/backlogs/config/locales/crowdin/ne.yml index 5b907ae6b84..6e6a574c935 100644 --- a/modules/backlogs/config/locales/crowdin/ne.yml +++ b/modules/backlogs/config/locales/crowdin/ne.yml @@ -25,6 +25,12 @@ ne: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ ne: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ ne: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ ne: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ ne: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/nl.yml b/modules/backlogs/config/locales/crowdin/nl.yml index 50c125516ba..0fbb1ed4317 100644 --- a/modules/backlogs/config/locales/crowdin/nl.yml +++ b/modules/backlogs/config/locales/crowdin/nl.yml @@ -25,6 +25,12 @@ nl: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ nl: task_version_must_be_the_same_as_story_version: "moet hetzelfde zijn als de bovenliggende verhaalversie." sprint: cannot_end_before_it_starts: "De sprint kan niet eindigen voordat het start." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "elke" column_width: "Column width" definition_of_done: "Definitie van Klaar" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Belemmering" label_versions_default_fold_state: "Laat versies samengevouwen zien" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ nl: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ nl: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ nl: label_backlogs_unconfigured: "Je hebt Backlogs nog niet geconfigureerd. Ga naar %{administration} >%{plugins}, en klik op de %{configure} voor deze plug-in. Kom hier terug nadat u de velden hebt geconfigureerd." label_blocks_ids: "ID's van geblokkeerde werkpakketten" label_column_in_backlog: "Kolom in achterstand" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Omlaag" label_points_burn_up: "Omhoog" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Obstakels" + label_sprint_new: "New sprint" label_task_board: "Taakbord" - permission_view_master_backlog: "Toon Máster Backlog" - permission_view_taskboards: "Bekijk taakborden" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Selecteer voltooide statussen" - permission_update_sprints: "Sprints bijwerken" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/no.yml b/modules/backlogs/config/locales/crowdin/no.yml index d463b97f8e9..feb3aa9b1be 100644 --- a/modules/backlogs/config/locales/crowdin/no.yml +++ b/modules/backlogs/config/locales/crowdin/no.yml @@ -25,6 +25,12 @@ description: "Denne modulen legger til funksjoner som setter dynamiske team i stand til å arbeide med OpenProject i Scrum prosjekter." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ task_version_must_be_the_same_as_story_version: "må være det samme som den overordnete historiens versjon." sprint: cannot_end_before_it_starts: "Sprinten kan ikke avsluttes før den starter." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "hvilken som helst" column_width: "Column width" definition_of_done: "Definisjon av ferdig" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Hinder" label_versions_default_fold_state: "Vis versjoner kollapset" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ label_backlogs_unconfigured: "Du har ikke konfigurert Forsinkelser enda. Gå til %{administration} > %{plugins}og klikk deretter på %{configure} lenken for denne utvidelsen. Når du har angitt felter, går du tilbake til denne siden for å begynne å bruke verktøyet." label_blocks_ids: "ID'er for blokkerte arbeidspakker" label_column_in_backlog: "Kolonne i forsinkelse" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Ned" label_points_burn_up: "Opp" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Hindring i etappe" + label_sprint_new: "New sprint" label_task_board: "Oppgavetavle" - permission_view_master_backlog: "Vis master forsinkelse" - permission_view_taskboards: "Vis oppgavetavler" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Velg ferdige statuser" - permission_update_sprints: "Oppdater etapper" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Forsinkelser" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/pl.yml b/modules/backlogs/config/locales/crowdin/pl.yml index 8cab7c56f21..a389daa6760 100644 --- a/modules/backlogs/config/locales/crowdin/pl.yml +++ b/modules/backlogs/config/locales/crowdin/pl.yml @@ -25,6 +25,12 @@ pl: description: "Moduł ten dodaje funkcje umożliwiające zwinnym zespołom pracę z OpenProject w projektach Scrum." activerecord: attributes: + agile/sprint: + duration: "Czas trwania" + finish_date: "Data zakończenia" + goal: "Cel sprintu" + name: "Nazwa sprintu" + sharing: "Udostępnianie" sprint: duration: "Czas trwania sprintu" work_package: @@ -42,12 +48,15 @@ pl: task_version_must_be_the_same_as_story_version: "musi być taka sama jak wersja story nadrzędnej." sprint: cannot_end_before_it_starts: "Sprint nie może się skończyć przed swoim rozpoczęciem." + models: + sprint: "Sprint" attributes: task_type: "Typ zadania" backlogs: any: "którekolwiek" column_width: "Szerokość kolumny" definition_of_done: "Definicja Zrobione" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Przeszkoda" label_versions_default_fold_state: "Pokaż zwinięte wersje" caption_versions_default_fold_state: "Wersje nie będą domyślnie rozwijane podczas przeglądania backlogów. Każdą z nich należy rozwinąć ręcznie." @@ -79,12 +88,21 @@ pl: backlog_component: blankslate_title: "Element %{name} jest pusty" blankslate_description: "Nie zaplanowano jeszcze żadnych punktów. Przeciągnij punkty tutaj, aby je dodać." + sprint_component: + blankslate_title: "Element %{name} jest pusty" + blankslate_description: "Nie zaplanowano jeszcze żadnych punktów. Przeciągnij punkty tutaj, aby je dodać." backlog_header_component: label_toggle_backlog: "Zwiń/rozwiń %{name}" label_story_count: zero: "Brak historii w backlogu" one: "%{count} historia w backlogu" other: "Liczba historii w backlogu: %{count}" + sprint_header_component: + label_toggle_backlog: "Zwiń/rozwiń %{name}" + label_story_count: + zero: "Brak historii w sprincie" + one: "%{count} historia w sprincie" + other: "Liczba historii w sprincie: %{count}" backlog_menu_component: label_actions: "Działania backlogu" action_menu: @@ -95,6 +113,14 @@ pl: burndown_chart: "Wykres spalania (burndown)" wiki: "Wiki" properties: "Właściwości" + sprint_menu_component: + label_actions: "Działania w ramach sprintu" + action_menu: + edit_sprint: "Edytuj sprint" + new_story: "Nowa historia" + stories_tasks: "Historie/zadania" + task_board: "Panel zadań" + burndown_chart: "Wykres spalania (burndown)" story_component: label_drag_story: "Przenieś %{name}" story_menu_component: @@ -122,14 +148,19 @@ pl: label_backlogs_unconfigured: "Jeszcze nie skonfigurowałeś backlogów. Przejdź do %{administration} > %{plugins}, następnie kliknij link %{configure} dla otrzymania tego dodatku. Po ustawieniu pól, wróć na tę stronę, aby zacząć korzystanie z narzędzia." label_blocks_ids: "Identyfikatory zablokowanych pakietów prac" label_column_in_backlog: "Kolumna w backlogu" + label_used_as_backlog: "Służy jako backlog" label_points_burn_down: "W dół" label_points_burn_up: "W górę" + label_sprint_edit: "Edytuj sprint" label_sprint_impediments: "Przeszkody sprintu" + label_sprint_new: "Nowy sprint" label_task_board: "Panel zadań" - permission_view_master_backlog: "Wyświetl master backlog" - permission_view_taskboards: "Wyświetl panel zadań" + permission_create_sprints: "Utwórz sprinty" + permission_manage_sprint_items: "Zarządzaj elementami sprintu" permission_select_done_statuses: "Wybierz wykonane statusy" - permission_update_sprints: "Aktualizuj sprint" + permission_share_sprint: "Udostępnij sprint" + permission_start_complete_sprint: "Rozpocznij/ukończ sprint" + permission_view_sprints: "Wyświetl sprinty" project_module_backlogs: "Backlogi" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/pt-BR.yml b/modules/backlogs/config/locales/crowdin/pt-BR.yml index 941be3e4c9d..55fdce0fae2 100644 --- a/modules/backlogs/config/locales/crowdin/pt-BR.yml +++ b/modules/backlogs/config/locales/crowdin/pt-BR.yml @@ -25,6 +25,12 @@ pt-BR: description: "Este módulo acrescenta recursos que permitem que as equipes ágeis trabalhem com o OpenProject em projetos Scrum." activerecord: attributes: + agile/sprint: + duration: "Duração" + finish_date: "Data de conclusão" + goal: "Objetivo da sprint" + name: "Nome da sprint" + sharing: "Compartilhamento" sprint: duration: "Duração da sprint" work_package: @@ -42,12 +48,15 @@ pt-BR: task_version_must_be_the_same_as_story_version: "deve ser igual à versão da história dos pais." sprint: cannot_end_before_it_starts: "A sprint não pode terminar antes de começar." + models: + sprint: "Sprint" attributes: task_type: "Tipo de tarefa" backlogs: any: "qualquer" column_width: "Largura da coluna" definition_of_done: "Definição de pronto" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impedimento" label_versions_default_fold_state: "Mostrar versões em modo fechado" caption_versions_default_fold_state: "As versões não serão expandidas por padrão ao visualizar backlogs. Cada uma deve ser expandida manualmente." @@ -75,12 +84,21 @@ pt-BR: backlog_component: blankslate_title: "%{name} está vazio" blankslate_description: "Ainda não há itens planejados. Arraste os itens para este espaço para adicioná-los." + sprint_component: + blankslate_title: "%{name} está vazio" + blankslate_description: "Ainda não há itens planejados. Arraste os itens para este espaço para adicioná-los." backlog_header_component: label_toggle_backlog: "Recolher/Expandir %{name}" label_story_count: zero: "Nenhuma história no backlog" one: "%{count} história no backlog" other: "%{count} histórias no backlog" + sprint_header_component: + label_toggle_backlog: "Recolher/Expandir %{name}" + label_story_count: + zero: "Nenhuma história na sprint" + one: "%{count} história na sprint" + other: "%{count} histórias na sprint" backlog_menu_component: label_actions: "Ações do backlog" action_menu: @@ -91,6 +109,14 @@ pt-BR: burndown_chart: "Gráfico de burndown" wiki: "Wiki" properties: "Propriedades" + sprint_menu_component: + label_actions: "Ações da sprint" + action_menu: + edit_sprint: "Editar sprint" + new_story: "Nova história" + stories_tasks: "Histórias/tarefas" + task_board: "Quadro de tarefas" + burndown_chart: "Gráfico de burndown" story_component: label_drag_story: "Mover %{name}" story_menu_component: @@ -118,14 +144,19 @@ pt-BR: label_backlogs_unconfigured: "Você ainda não configurou o Backlog. Por favor, vá para %{administration} > %{plugins} e, em seguida, clique em %{configure} o link para este plugin. Uma vez que você definiu os campos, volte a esta página para começar a usar a ferramenta." label_blocks_ids: "IDs dos pacotes de trabalho bloqueados" label_column_in_backlog: "Coluna no backlog" + label_used_as_backlog: "Usado como backlog" label_points_burn_down: "Abaixo" label_points_burn_up: "Acima" + label_sprint_edit: "Editar sprint" label_sprint_impediments: "Impedimentos da Sprint" + label_sprint_new: "Nova sprint" label_task_board: "Quadro de tarefas" - permission_view_master_backlog: "Visualizar backlog principal" - permission_view_taskboards: "Visualizar quadro de tarefas" + permission_create_sprints: "Criar sprints" + permission_manage_sprint_items: "Gerenciar itens da sprint" permission_select_done_statuses: "Selecione situações concluídas" - permission_update_sprints: "Editar sprints" + permission_share_sprint: "Compartilhar sprint" + permission_start_complete_sprint: "Iniciar/concluir sprint" + permission_view_sprints: "Ver sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/pt-PT.yml b/modules/backlogs/config/locales/crowdin/pt-PT.yml index 5f50d045ed9..4f37f5ce94d 100644 --- a/modules/backlogs/config/locales/crowdin/pt-PT.yml +++ b/modules/backlogs/config/locales/crowdin/pt-PT.yml @@ -25,6 +25,12 @@ pt-PT: description: "Este módulo acrescenta funcionalidades que permitem às equipas Agile trabalhar com o OpenProject em projetos Scrum." activerecord: attributes: + agile/sprint: + duration: "Duração" + finish_date: "Data de término" + goal: "Objetivo do sprint" + name: "Nome do sprint" + sharing: "Partilhar" sprint: duration: "Duração do sprint" work_package: @@ -42,12 +48,15 @@ pt-PT: task_version_must_be_the_same_as_story_version: "tem ser o mesmo que a versão da história do pai." sprint: cannot_end_before_it_starts: "Sprint não pode terminar antes de começar." + models: + sprint: "Sprint" attributes: task_type: "Tipo de tarefa" backlogs: any: "qualquer" column_width: "Largura da coluna" definition_of_done: "Definição de feito" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impedimento" label_versions_default_fold_state: "Mostrar versões dobradas" caption_versions_default_fold_state: "Versões não serão expandidas por predefinição quando visualizar os backlogs. Cada uma tem de ser expandida manualmente." @@ -75,12 +84,21 @@ pt-PT: backlog_component: blankslate_title: "%{name} está vazio" blankslate_description: "Ainda não existem elementos planeados. Arraste elementos para aqui para os adicionar." + sprint_component: + blankslate_title: "%{name} está vazio" + blankslate_description: "Ainda não existem elementos planeados. Arraste elementos para aqui para os adicionar." backlog_header_component: label_toggle_backlog: "Recolher/expandir %{name}" label_story_count: zero: "Não há histórias no backlog" one: "%{count} história no backlog" other: "%{count} histórias no backlog" + sprint_header_component: + label_toggle_backlog: "Recolher/expandir %{name}" + label_story_count: + zero: "Não há histórias no sprint" + one: "%{count} história em sprint" + other: "%{count} histórias em sprint" backlog_menu_component: label_actions: "Ações de backlog" action_menu: @@ -91,6 +109,14 @@ pt-PT: burndown_chart: "Gráfico de burndown" wiki: "Wiki" properties: "Propriedades" + sprint_menu_component: + label_actions: "Ações do sprint" + action_menu: + edit_sprint: "Editar sprint" + new_story: "Nova história" + stories_tasks: "Histórias/tarefas" + task_board: "Quadro de tarefas" + burndown_chart: "Gráfico de burndown" story_component: label_drag_story: "Mover %{name}" story_menu_component: @@ -118,14 +144,19 @@ pt-PT: label_backlogs_unconfigured: "Ainda não configurou os backlogs. Vá a %{administration} > %{plugins}, e depois clique no link %{configure} para este plugin. Após definir os campos, volte a esta página para começar a utilizar a ferramenta." label_blocks_ids: "Identificações de pacotes de trabalho bloqueados" label_column_in_backlog: "Coluna no backlog" + label_used_as_backlog: "Utilizado como backlog" label_points_burn_down: "Abaixo" label_points_burn_up: "Acima" + label_sprint_edit: "Editar sprint" label_sprint_impediments: "Impedimentos de Sprint" + label_sprint_new: "Novo sprint" label_task_board: "Quadro de tarefas" - permission_view_master_backlog: "Ver o backlog principal" - permission_view_taskboards: "Ver quadros de tarefas" + permission_create_sprints: "Criar sprints" + permission_manage_sprint_items: "Gerir elementos de sprint" permission_select_done_statuses: "Selecione os estados concluídos" - permission_update_sprints: "Atualizar sprints" + permission_share_sprint: "Partilhar sprint" + permission_start_complete_sprint: "Iniciar/completar sprint" + permission_view_sprints: "Ver sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ro.yml b/modules/backlogs/config/locales/crowdin/ro.yml index 1a32c9370b0..22597abf6e8 100644 --- a/modules/backlogs/config/locales/crowdin/ro.yml +++ b/modules/backlogs/config/locales/crowdin/ro.yml @@ -25,12 +25,18 @@ ro: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: position: "Poziție" story_points: "Puncte" - backlogs_work_package_type: "Tipul de restante" + backlogs_work_package_type: "Tip restanță" errors: models: work_package: @@ -42,12 +48,15 @@ ro: task_version_must_be_the_same_as_story_version: "trebuie să fie aceeași cu versiunea cerinței părinte." sprint: cannot_end_before_it_starts: "Sprint nu se poate termina înainte de a începe." + models: + sprint: "Sprint" attributes: task_type: "Tip de sarcină" backlogs: any: "Oricare" column_width: "Column width" definition_of_done: "Procent realizat" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Afișare versiuni complete" caption_versions_default_fold_state: "Versiunile nu vor fi extinse în mod implicit la vizualizarea restanțelor. Fiecare versiune trebuie să fie extinsă manual." @@ -77,12 +86,21 @@ ro: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -93,6 +111,14 @@ ro: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -120,14 +146,19 @@ ro: label_backlogs_unconfigured: "Nu ai configurat încă Backlogs. Te rog să mergi la %{administration} > %{plugins}, apoi dă clic pe link-ul %{configure} pentru acest plugin. După ce ai configurat câmpurile, revino la această pagină pentru a începe să utilizezi instrumentul." label_blocks_ids: "ID-urile pachetelor de lucru blocate" label_column_in_backlog: "Coloană în backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Jos" label_points_burn_up: "Sus" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Impedimentele Sprint" + label_sprint_new: "New sprint" label_task_board: "Tablă de sarcini" - permission_view_master_backlog: "Vizualizare master backlog" - permission_view_taskboards: "Vizualizați tablourile de sarcini" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Selectează stările realizate" - permission_update_sprints: "Sprinturi" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Restanțe" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/ru.yml b/modules/backlogs/config/locales/crowdin/ru.yml index 87b53de1666..01134c1718d 100644 --- a/modules/backlogs/config/locales/crowdin/ru.yml +++ b/modules/backlogs/config/locales/crowdin/ru.yml @@ -25,6 +25,12 @@ ru: description: "Этот модуль добавляет функции, позволяющие agile-командам работать с OpenProject в Scrum проектах." activerecord: attributes: + agile/sprint: + duration: "Продолжительность" + finish_date: "Дата окончания" + goal: "Цель спринта" + name: "Название спринта" + sharing: "Совместное использование" sprint: duration: "Продолжительность спринта" work_package: @@ -42,12 +48,15 @@ ru: task_version_must_be_the_same_as_story_version: "должен совпадать с версией родительской истории." sprint: cannot_end_before_it_starts: "Спринт не может заканчиваться до того, как начнется." + models: + sprint: "Спринт" attributes: task_type: "Тип задачи" backlogs: any: "любой" column_width: "Ширина столбца" definition_of_done: "Определение термина \"Завершено\"" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Препятствие" label_versions_default_fold_state: "Показать свернутые версии" caption_versions_default_fold_state: "Версии не будут разворачиваться по умолчанию при просмотре бэклогов. Каждая версия должна быть развернута вручную." @@ -79,12 +88,21 @@ ru: backlog_component: blankslate_title: "%{name} пустой" blankslate_description: "Пока ничего не запланировано. Перетащите элементы сюда, чтобы добавить их." + sprint_component: + blankslate_title: "%{name} пустой" + blankslate_description: "Пока ничего не запланировано. Перетащите элементы сюда, чтобы добавить их." backlog_header_component: label_toggle_backlog: "Свернуть/Развернуть %{name}" label_story_count: zero: "Нет историй в бэклоге" one: "%{count} история в бэклоге" other: "%{count} историй в бэклоге" + sprint_header_component: + label_toggle_backlog: "Свернуть/Развернуть %{name}" + label_story_count: + zero: "Нет историй в спринте" + one: "%{count} история в спринте" + other: "%{count} историй в спринте" backlog_menu_component: label_actions: "Действия в рамках бэклога" action_menu: @@ -95,6 +113,14 @@ ru: burndown_chart: "Сводная таблица" wiki: "Wiki" properties: "Свойства" + sprint_menu_component: + label_actions: "Действия в спринте" + action_menu: + edit_sprint: "Редактировать спринт" + new_story: "Новая история" + stories_tasks: "Истории/задачи" + task_board: "Панель задач" + burndown_chart: "Сводная таблица" story_component: label_drag_story: "Переместить %{name}" story_menu_component: @@ -122,14 +148,19 @@ ru: label_backlogs_unconfigured: "Вы еще не настроили Невыполненные работы. Перейдите на страницу %{administration} > %{plugins}, а затем нажмите на ссылку %{configure} для получения этого дополнения. После того как вы настроите поля, возвратитесь на эту страницу, чтобы начать пользоваться инструментом." label_blocks_ids: "Идентификаторы заблокированных рабочих пакетов" label_column_in_backlog: "Колонка в бэклоге" + label_used_as_backlog: "Используется как бэклог" label_points_burn_down: "Вниз" label_points_burn_up: "Вверх" + label_sprint_edit: "Редактировать спринт" label_sprint_impediments: "Препятствия спринта" + label_sprint_new: "Новый спринт" label_task_board: "Панель задач" - permission_view_master_backlog: "Просмотреть главную невыполненную работу" - permission_view_taskboards: "Просмотреть панели задач" + permission_create_sprints: "Создание спринтов" + permission_manage_sprint_items: "Управление пунктами спринта" permission_select_done_statuses: "Выберите завершенные статусы" - permission_update_sprints: "Обновить спринты" + permission_share_sprint: "Поделиться спринтом" + permission_start_complete_sprint: "Начать/завершить спринт" + permission_view_sprints: "Просмотр спринтов" project_module_backlogs: "Бэклоги" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/rw.yml b/modules/backlogs/config/locales/crowdin/rw.yml index 83a8442ce8f..c2dbd4ef227 100644 --- a/modules/backlogs/config/locales/crowdin/rw.yml +++ b/modules/backlogs/config/locales/crowdin/rw.yml @@ -25,6 +25,12 @@ rw: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ rw: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ rw: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ rw: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ rw: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/si.yml b/modules/backlogs/config/locales/crowdin/si.yml index f2789c8f7b8..4af952f455c 100644 --- a/modules/backlogs/config/locales/crowdin/si.yml +++ b/modules/backlogs/config/locales/crowdin/si.yml @@ -25,6 +25,12 @@ si: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ si: task_version_must_be_the_same_as_story_version: "මව් කතාවේ අනුවාදයට සමාන විය යුතුය." sprint: cannot_end_before_it_starts: "ස්ප්රින්ට් ආරම්භ වීමට පෙර අවසන් කළ නොහැක." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "ඔනෑම" column_width: "Column width" definition_of_done: "සිදු කරන ලද අර්ථ දැක්වීම" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "බාධාවන්" label_versions_default_fold_state: "නවනු අනුවාද පෙන්වන්න" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ si: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ si: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ si: label_backlogs_unconfigured: "ඔබ තවමත් Backlogs වින්යාස කර නැත. කරුණාකර යන්න %{administration} > %{plugins}, ඉන්පසු මෙම ප්ලගිනය සඳහා %{configure} සබැඳිය ක්ලික් කරන්න. ඔබ ක්ෂේත්ර සකස් කළ පසු, මෙවලම භාවිතා කිරීම ආරම්භ කිරීමට මෙම පිටුවට නැවත එන්න." label_blocks_ids: "අවහිර කරන ලද වැඩ පැකේජ වල IDS" label_column_in_backlog: "පසුබිම තුළ තීරුව" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "පහළට" label_points_burn_up: "ඉහළට" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "ස්ප්රින්ට් බාධාවන්" + label_sprint_new: "New sprint" label_task_board: "කාර්ය මණ්ඩලය" - permission_view_master_backlog: "ස්වාමියා බැක්ලොග් දැක්ම" - permission_view_taskboards: "දැක්ම කාර්ය මණ්ඩල" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "ස්ප්රින්ට් යාවත්කාලීන" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "බැක්ලොග්ස්" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/sk.yml b/modules/backlogs/config/locales/crowdin/sk.yml index 2e9db70d37f..5e384c812fd 100644 --- a/modules/backlogs/config/locales/crowdin/sk.yml +++ b/modules/backlogs/config/locales/crowdin/sk.yml @@ -25,6 +25,12 @@ sk: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Trvanie šprintu" work_package: @@ -42,12 +48,15 @@ sk: task_version_must_be_the_same_as_story_version: "musí byť rovnaká ako verzia nadradeného článku." sprint: cannot_end_before_it_starts: "Šprint nemôže byť ukončený ešte pred jeho spustením." + models: + sprint: "Sprint" attributes: task_type: "Typ úlohy" backlogs: any: "akékoľvek" column_width: "Šírka stĺpca" definition_of_done: "Definícia pojmu Hotovo" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Prekážka" label_versions_default_fold_state: "Zobrazenie zložených verzií" caption_versions_default_fold_state: "Verzie sa pri prezeraní nevybavených dokumentov nebudú predvolene rozbaľovať. Každú z nich je potrebné rozbaliť manuálne." @@ -79,12 +88,21 @@ sk: backlog_component: blankslate_title: "%{name} je prázdne" blankslate_description: "Zatiaľ nie sú plánované žiadne položky. Ak chcete pridať položky, potiahnite ich sem." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Zbaliť/rozšíriť %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -95,6 +113,14 @@ sk: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -122,14 +148,19 @@ sk: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "Identifikátory blokovaných pracovných balíkov" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Nadol" label_points_burn_up: "Nahor" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "Zobrazenie tabúľ úloh" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Výber stavov hotovo" - permission_update_sprints: "Aktualizácia šprinty" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/sl.yml b/modules/backlogs/config/locales/crowdin/sl.yml index 75de0f0bad3..953cff09d89 100644 --- a/modules/backlogs/config/locales/crowdin/sl.yml +++ b/modules/backlogs/config/locales/crowdin/sl.yml @@ -25,6 +25,12 @@ sl: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ sl: task_version_must_be_the_same_as_story_version: "mora biti enako različici nadrejene zgodbe." sprint: cannot_end_before_it_starts: "Tek se ne mora končati preden se začne" + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "katerikoli" column_width: "Column width" definition_of_done: "Opredelitev opravljenega" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Ovira" label_versions_default_fold_state: "Pokaži različice zložene" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -79,12 +88,21 @@ sl: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -95,6 +113,14 @@ sl: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -122,14 +148,19 @@ sl: label_backlogs_unconfigured: "Zaostankov še niste konfigurirali. Pojdite na %{administration} > %{plugins}, nato kliknite povezavo %{configure} za ta vtičnik. Ko nastavite polja, se vrnite na to stran, da začnete uporabljati orodje." label_blocks_ids: "ID blokiranih delovnih paketov" label_column_in_backlog: "Stolpec v zaostanku" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Navzdol" label_points_burn_up: "Navzgor" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Motnje sprinta" + label_sprint_new: "New sprint" label_task_board: "Tabla opravil" - permission_view_master_backlog: "glavni zaostanek prejšnje poizvedbe" - permission_view_taskboards: "Prikaz delovne table" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Označi statuse narejeno" - permission_update_sprints: "Posodobi šprinte" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Zaostanki" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/sr.yml b/modules/backlogs/config/locales/crowdin/sr.yml index 58043dd441a..dc9391b8e13 100644 --- a/modules/backlogs/config/locales/crowdin/sr.yml +++ b/modules/backlogs/config/locales/crowdin/sr.yml @@ -25,6 +25,12 @@ sr: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ sr: task_version_must_be_the_same_as_story_version: "mora biti ista kao verzija priče roditelja." sprint: cannot_end_before_it_starts: "Sprint se ne može završiti pre nego što počne." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "bilo koji" column_width: "Column width" definition_of_done: "Definicija završetka" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Smetnja" label_versions_default_fold_state: "Prikaži verzije skupljene" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -77,12 +86,21 @@ sr: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -93,6 +111,14 @@ sr: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -120,14 +146,19 @@ sr: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/sv.yml b/modules/backlogs/config/locales/crowdin/sv.yml index 399d2b9b27f..afef125bbc1 100644 --- a/modules/backlogs/config/locales/crowdin/sv.yml +++ b/modules/backlogs/config/locales/crowdin/sv.yml @@ -25,6 +25,12 @@ sv: description: "Modulen lägger till funktioner som gör det möjligt för agila team att arbeta med OpenProject i Scrum-projekt." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ sv: task_version_must_be_the_same_as_story_version: "måste vara samma som föräldraberättelsens version." sprint: cannot_end_before_it_starts: "En sprint kan inte avslutas innan den startar." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "någon" column_width: "Column width" definition_of_done: "Definition av klart" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Hinder" label_versions_default_fold_state: "Visa ihopfällda versioner" caption_versions_default_fold_state: "Versioner kommer inte att utökas som standard när du visar backloggar. Var och en måste utökas manuellt." @@ -75,12 +84,21 @@ sv: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ sv: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ sv: label_backlogs_unconfigured: "Du har inte konfigurerat backloggar ännu. Gå till %{administration} > %{plugins}, klicka på %{configure} länken för denna plugin. När du har ställt in fälten, kan du komma tillbaka till denna sida för att börja använda verktyget." label_blocks_ids: "ID:n för blockerade arbetspaket" label_column_in_backlog: "Kolumn i backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Ner" label_points_burn_up: "Upp" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint hinder" + label_sprint_new: "New sprint" label_task_board: "Aktivitetstavla" - permission_view_master_backlog: "Visa master backlog" - permission_view_taskboards: "Visa aktivitetstavlor" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Välj klar status" - permission_update_sprints: "Uppdatera sprinter" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backloggar" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/th.yml b/modules/backlogs/config/locales/crowdin/th.yml index f438571c80c..e8c928d448f 100644 --- a/modules/backlogs/config/locales/crowdin/th.yml +++ b/modules/backlogs/config/locales/crowdin/th.yml @@ -25,6 +25,12 @@ th: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "ระยะเวลา" + finish_date: "วันที่เสร็จสิ้น" + goal: "เป้าหมายสปริ๊นต์" + name: "ชื่อสปรินต์" + sharing: "Sharing" sprint: duration: "ระยะเวลาการสปรินต์" work_package: @@ -42,12 +48,15 @@ th: task_version_must_be_the_same_as_story_version: "เวอร์ชันต้องตรงกับสตอรี่หลัก" sprint: cannot_end_before_it_starts: "กรุณาระบุวันสิ้นสุดให้หลังจากวันเริ่มต้นสปรินต์" + models: + sprint: "สปรินท์" attributes: task_type: "ประเภทงาน" backlogs: any: "ทั้งหมด" column_width: "ความกว้างของคอลัมน์" definition_of_done: "นิยามของคำว่าเสร็จสิ้น" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "รายการอุปสรรค" label_versions_default_fold_state: "แสดงเวอร์ชันที่พับอยู่" caption_versions_default_fold_state: "เวอร์ชันจะถูกพับไว้เป็นค่าเริ่มต้น กรุณาคลิกเพื่อขยายดูทีละรายการ" @@ -73,12 +82,21 @@ th: backlog_component: blankslate_title: "%{name} ว่างอยู่" blankslate_description: "ยังไม่มีรายการที่วางแผนไว้ ลากรายการมาวางที่นี่เพื่อเพิ่ม" + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "พับ/ขยาย %{name}" label_story_count: zero: "ไม่มีสตอรี่ในรายการงานคงค้าง" one: "%{count} สตอรี่ในรายการงานคงค้าง" other: "%{count} สตอรี่ในรายการงานคงค้าง" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "การจัดการรายการงานคงค้าง" action_menu: @@ -89,6 +107,14 @@ th: burndown_chart: "กราฟติดตามงาน (Burndown)" wiki: "วิกิ" properties: "คุณสมบัติ" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "ย้าย %{name}" story_menu_component: @@ -116,14 +142,19 @@ th: label_backlogs_unconfigured: "คุณยังไม่ได้ตั้งค่ารายการงานคงค้าง กรุณาไปที่ %{administration} > %{plugins} จากนั้นคลิกที่ลิงก์ %{configure} ของปลั๊กอินนี้ เมื่อคุณตั้งค่าฟิลด์ต่าง ๆ เรียบร้อยแล้ว ให้กลับมาที่หน้านี้เพื่อเริ่มใช้งานเครื่องมือ" label_blocks_ids: "Id ของแพคเกจการทำงานที่ถูกบล็อก" label_column_in_backlog: "คอลัมน์ในรายการงานคงค้าง" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "ลง" label_points_burn_up: "ขึ้น" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "อุปสรรคของสปรินท์" + label_sprint_new: "สปรินท์ใหม่" label_task_board: "กระดานงาน" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "สร้างสปรินท์" + permission_manage_sprint_items: "จัดการรายการในสปรินท์" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "แชร์สปรินท์" + permission_start_complete_sprint: "เริ่มต้น/เสร็จสิ้นสปรินท์" + permission_view_sprints: "ดูสปรินท์" project_module_backlogs: "รายการงานคงค้าง" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/tr.yml b/modules/backlogs/config/locales/crowdin/tr.yml index ebd436c906f..d0e5ff7103a 100644 --- a/modules/backlogs/config/locales/crowdin/tr.yml +++ b/modules/backlogs/config/locales/crowdin/tr.yml @@ -25,6 +25,12 @@ tr: description: "Bu modül, çevik ekiplerin Scrum projelerinde OpenProject ile çalışmasını sağlayan özellikler ekler." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Paylaşım" sprint: duration: "Sprint süresi" work_package: @@ -42,12 +48,15 @@ tr: task_version_must_be_the_same_as_story_version: "ana hikaye sürümüyle aynı olmalıdır." sprint: cannot_end_before_it_starts: "Sprint başlamadan önce bitemez." + models: + sprint: "Sprint" attributes: task_type: "Görev türü" backlogs: any: "herhangi bir" column_width: "Sütun genişliği" definition_of_done: "Bitti Tanımı" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Engel" label_versions_default_fold_state: "Katlanmış sürümleri göster" caption_versions_default_fold_state: "Birikmiş işler görüntülenirken sürümler öntanımlı olarak genişletilmeyecektir. Her birinin ayrı ayrı genişletilmesi gerekir." @@ -75,12 +84,21 @@ tr: backlog_component: blankslate_title: "%{name} boş" blankslate_description: "Henüz planlanan öğe yok. Eklemek için öğeleri buraya sürükleyin." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Daralt/Genişlet %{name}" label_story_count: zero: "Kuyrukta hikaye yok" one: "%{count} kuyruktaki hikaye" other: "%{count} kuyruktaki hikayeler" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Kuyruk eylemleri" action_menu: @@ -91,6 +109,14 @@ tr: burndown_chart: "Burndown grafiği" wiki: "Wiki" properties: "Özellikler" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Taşı %{name}" story_menu_component: @@ -118,14 +144,19 @@ tr: label_backlogs_unconfigured: "Backlog'ları henüz yapılandırmadınız. Lütfen% %{administration}> %{plugins} adresine gidin, ardından bu eklenti için %{configure} bağlantısını tıklayın. Alanları belirledikten sonra, aracı kullanmaya başlamak için bu sayfaya geri dönün." label_blocks_ids: "Bloke iş paketleri kimlikleri" label_column_in_backlog: "Sütununda birikim var" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Aşağı" label_points_burn_up: "Yukarı" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Engelleri" + label_sprint_new: "New sprint" label_task_board: "Görev panosu" - permission_view_master_backlog: "Ana bekleme günlüğünü görüntüleme" - permission_view_taskboards: "Görev tahtalarını görüntüleme" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Tamamlanan durumları seçiniz" - permission_update_sprints: "Süratleri güncelle" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "İş listesi" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/uk.yml b/modules/backlogs/config/locales/crowdin/uk.yml index 175b9af4e09..395ef98c388 100644 --- a/modules/backlogs/config/locales/crowdin/uk.yml +++ b/modules/backlogs/config/locales/crowdin/uk.yml @@ -25,6 +25,12 @@ uk: description: "Цей модуль додає функції, завдяки яким agile-команди можуть працювати над проєктами Scrum в OpenProject." activerecord: attributes: + agile/sprint: + duration: "Тривалість" + finish_date: "Дата закінчення" + goal: "Ціль спринту" + name: "Ім’я спринту" + sharing: "Надання доступу" sprint: duration: "Тривалість спринту" work_package: @@ -42,12 +48,15 @@ uk: task_version_must_be_the_same_as_story_version: "має збігатись із версією батьківської історії." sprint: cannot_end_before_it_starts: "Спринт не може закінчитися, перш ніж він почне працювати." + models: + sprint: "Спринт" attributes: task_type: "Тип завдання" backlogs: any: "будь-який" column_width: "Ширина стовпця" definition_of_done: "Визначення завершено" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Перешкода" label_versions_default_fold_state: "Показати складені версії" caption_versions_default_fold_state: "Версії не розгортатимуться за замовчуванням при перегляді невиконаних завдань. Кожну версію потрібно розгортати вручну." @@ -79,12 +88,21 @@ uk: backlog_component: blankslate_title: "%{name} нічого не містить" blankslate_description: "Ще нічого не заплановано. Перетягніть елементи сюди, щоб додати їх." + sprint_component: + blankslate_title: "%{name} нічого не містить" + blankslate_description: "Ще нічого не заплановано. Перетягніть елементи сюди, щоб додати їх." backlog_header_component: label_toggle_backlog: "Згорнути/розгорнути %{name}" label_story_count: zero: "У беклозі немає історій" one: "У беклозі %{count} історія" other: "У беклозі стільки історій: %{count}" + sprint_header_component: + label_toggle_backlog: "Згорнути/розгорнути %{name}" + label_story_count: + zero: "У спринті немає історій" + one: "У спринті %{count} історія" + other: "У спринті стільки історій: %{count}" backlog_menu_component: label_actions: "Дії беклогу" action_menu: @@ -95,6 +113,14 @@ uk: burndown_chart: "Діаграма згорання завдань" wiki: "Wiki" properties: "Властивості" + sprint_menu_component: + label_actions: "Дії спринту" + action_menu: + edit_sprint: "Редагувати спринт" + new_story: "Нова історія" + stories_tasks: "Історії/завдання" + task_board: "Дошка завдань" + burndown_chart: "Діаграма згорання завдань" story_component: label_drag_story: "Перемістити історію «%{name}»" story_menu_component: @@ -122,14 +148,19 @@ uk: label_backlogs_unconfigured: "Ви ще не настроїли Backlogs. Перейдіть на %{administration}>%{plugins}, потім натисніть посилання %{configure} для цього плагіна. Після того, як ви встановите поля, поверніться на цю сторінку, щоб розпочати використання інструмента." label_blocks_ids: "Ідентифікатори заблокованих робочих пакетів" label_column_in_backlog: "Стовпець у backlog-у" + label_used_as_backlog: "Використовується як беклог" label_points_burn_down: "Вниз" label_points_burn_up: "Вгору" + label_sprint_edit: "Редагувати спринт" label_sprint_impediments: "Перешкоди спринту" + label_sprint_new: "Новий спринт" label_task_board: "Дошка завдань" - permission_view_master_backlog: "Перегляд головного backlog-у" - permission_view_taskboards: "Перегляд панелі завдань" + permission_create_sprints: "Створення спринтів" + permission_manage_sprint_items: "Керування елементами спринтів" permission_select_done_statuses: "Виберіть завершені статуси" - permission_update_sprints: "Оновлення спринту" + permission_share_sprint: "Надання доступу до спринтів" + permission_start_complete_sprint: "Початок/завершення спринтів" + permission_view_sprints: "Перегляд спринтів" project_module_backlogs: "Невиконані завдання" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/uz.yml b/modules/backlogs/config/locales/crowdin/uz.yml index 1d784af9e97..90ca537e08a 100644 --- a/modules/backlogs/config/locales/crowdin/uz.yml +++ b/modules/backlogs/config/locales/crowdin/uz.yml @@ -25,6 +25,12 @@ uz: description: "This module adds features enabling agile teams to work with OpenProject in Scrum projects." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ uz: task_version_must_be_the_same_as_story_version: "must be the same as the parent story's version." sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" attributes: task_type: "Task type" backlogs: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -75,12 +84,21 @@ uz: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -91,6 +109,14 @@ uz: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -118,14 +144,19 @@ uz: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/vi.yml b/modules/backlogs/config/locales/crowdin/vi.yml index 4c8ca2e4f8f..55ee9ba1d3f 100644 --- a/modules/backlogs/config/locales/crowdin/vi.yml +++ b/modules/backlogs/config/locales/crowdin/vi.yml @@ -25,6 +25,12 @@ vi: description: "Mô-đun này bổ sung các tính năng cho phép các nhóm linh hoạt làm việc với OpenProject trong các dự án Scrum." activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -42,12 +48,15 @@ vi: task_version_must_be_the_same_as_story_version: "phải giống với phiên bản của bảng cha." sprint: cannot_end_before_it_starts: "Sprint không thể kết thúc trước khi nó bắt đầu." + models: + sprint: "Sprint" attributes: task_type: "Loại nhiệm vụ" backlogs: any: "bất kỳ" column_width: "Column width" definition_of_done: "Định nghĩa về Hoàn thành" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Trở ngại" label_versions_default_fold_state: "Hiển thị các phiên bản \n" caption_versions_default_fold_state: "Các phiên bản sẽ không được mở rộng theo mặc định khi xem hồ sơ tồn đọng. Mỗi cái phải được mở rộng bằng tay." @@ -73,12 +82,21 @@ vi: backlog_component: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: zero: "No stories in backlog" one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -89,6 +107,14 @@ vi: burndown_chart: "Burndown chart" wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "Move %{name}" story_menu_component: @@ -116,14 +142,19 @@ vi: label_backlogs_unconfigured: "Bạn chưa cấu hình Bảng nhiệm vụ tồn đọng. Vui lòng vào %{administration} > %{plugins}, sau đó nhấp vào liên kết %{configure} cho gắn thêm này. Khi bạn đã thiết lập các trường, quay lại trang này để bắt đầu sử dụng công cụ." label_blocks_ids: "ID của các work package bị chặn" label_column_in_backlog: "Cột tồn đọng" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Xuống" label_points_burn_up: "lên" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Trở ngại nước rút" + label_sprint_new: "New sprint" label_task_board: "Bảng nhiệm vụ" - permission_view_master_backlog: "Xem tồn đọng chính" - permission_view_taskboards: "Xem bảng tác vụ" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Chọn trạng thái hoàn thành" - permission_update_sprints: "Cập nhật các lần chạy nước rút" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "tồn đọng" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/zh-CN.yml b/modules/backlogs/config/locales/crowdin/zh-CN.yml index 148af750cd9..4e72f220e5c 100644 --- a/modules/backlogs/config/locales/crowdin/zh-CN.yml +++ b/modules/backlogs/config/locales/crowdin/zh-CN.yml @@ -25,6 +25,12 @@ zh-CN: description: "该模块为敏捷团队在敏捷项目中使用 OpenProject 添加了功能。" activerecord: attributes: + agile/sprint: + duration: "持续时间" + finish_date: "完成日期" + goal: "冲刺目标" + name: "冲刺名称" + sharing: "共享" sprint: duration: "冲刺持续时间" work_package: @@ -42,12 +48,15 @@ zh-CN: task_version_must_be_the_same_as_story_version: "必须与父级故事的版本相同。" sprint: cannot_end_before_it_starts: "比赛不可以在开始前结束。" + models: + sprint: "冲刺" attributes: task_type: "任务类型" backlogs: any: "任一" column_width: "列宽" definition_of_done: "完成的定义" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "障碍" label_versions_default_fold_state: "显示已折叠的版本" caption_versions_default_fold_state: "查看积压工作时,默认情况下不会展开版本。每个版本都必须手动展开。" @@ -73,12 +82,21 @@ zh-CN: backlog_component: blankslate_title: "%{name} 为空" blankslate_description: "尚未计划条目。请将条目拖到此处以添加。" + sprint_component: + blankslate_title: "%{name} 为空" + blankslate_description: "尚未计划条目。请将条目拖到此处以添加。" backlog_header_component: label_toggle_backlog: "折叠/展开 %{name}" label_story_count: zero: "积压工作中没有故事" one: "积压工作中有 %{count} 个故事" other: "积压工作中有 %{count} 个故事" + sprint_header_component: + label_toggle_backlog: "折叠/展开 %{name}" + label_story_count: + zero: "冲刺中没有故事" + one: "冲刺中有 %{count} 个故事" + other: "冲刺中有 %{count} 个故事" backlog_menu_component: label_actions: "积压工作操作" action_menu: @@ -89,6 +107,14 @@ zh-CN: burndown_chart: "燃尽图" wiki: "维基" properties: "属性" + sprint_menu_component: + label_actions: "冲刺操作" + action_menu: + edit_sprint: "编辑冲刺" + new_story: "新建故事" + stories_tasks: "故事/任务" + task_board: "任务面板" + burndown_chart: "燃尽图" story_component: label_drag_story: "移动 %{name}" story_menu_component: @@ -116,14 +142,19 @@ zh-CN: label_backlogs_unconfigured: "您尚未配置待办清单。请转到“%{administration} > %{plugins}”,然后单击此插件的 %{configure} 链接。设置字段后,返回到此页面开始使用工具。" label_blocks_ids: "被阻止的工作包 ID" label_column_in_backlog: "待办清单中的列" + label_used_as_backlog: "用作积压工作" label_points_burn_down: "减少" label_points_burn_up: "增加" + label_sprint_edit: "编辑冲刺" label_sprint_impediments: "冲刺 (sprint) 障碍" + label_sprint_new: "新冲刺" label_task_board: "任务板" - permission_view_master_backlog: "查看主待办清单" - permission_view_taskboards: "查看任务板" + permission_create_sprints: "创建冲刺" + permission_manage_sprint_items: "管理冲刺条目" permission_select_done_statuses: "选择完成状态" - permission_update_sprints: "更新迭代" + permission_share_sprint: "共享冲刺" + permission_start_complete_sprint: "开始/完成冲刺" + permission_view_sprints: "查看冲刺" project_module_backlogs: "待办清单" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/crowdin/zh-TW.yml b/modules/backlogs/config/locales/crowdin/zh-TW.yml index 6058a1249a7..f183a8b675f 100644 --- a/modules/backlogs/config/locales/crowdin/zh-TW.yml +++ b/modules/backlogs/config/locales/crowdin/zh-TW.yml @@ -21,10 +21,16 @@ #++ zh-TW: plugin_openproject_backlogs: - name: "OpenProject待辦事項" + name: "OpenProject代辦事項" description: "此模組新增了讓敏捷團隊能夠在 Scrum 專案中使用 OpenProject 的功能。" activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "分享" sprint: duration: "衝刺時間" work_package: @@ -42,12 +48,15 @@ zh-TW: task_version_must_be_the_same_as_story_version: "必須與上層「使用者需求」(User Story)的版本相同。" sprint: cannot_end_before_it_starts: "進度不可以在開始前結束" + models: + sprint: "Sprint" attributes: task_type: "任務類型" backlogs: any: "任何" column_width: "欄寬" definition_of_done: "定義" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "阻礙" label_versions_default_fold_state: "顯示精簡版本" caption_versions_default_fold_state: "檢視待辦清單時,版本預設不會展開,需手動逐一展開。" @@ -73,12 +82,21 @@ zh-TW: backlog_component: blankslate_title: "%{name} 為空" blankslate_description: "尚未規劃任何項目。將項目拖曳至此即可新增。" + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." backlog_header_component: label_toggle_backlog: "折疊/展開 %{name}" label_story_count: zero: "目前沒有待辦故事" one: "待辦清單中有 %{count} 個故事" other: "待辦清單中有 %{count} 個故事" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" backlog_menu_component: label_actions: "待辦清單操作" action_menu: @@ -89,6 +107,14 @@ zh-TW: burndown_chart: "燃盡圖" wiki: "維基" properties: "屬性" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" story_component: label_drag_story: "移動 %{name}" story_menu_component: @@ -116,14 +142,19 @@ zh-TW: label_backlogs_unconfigured: "您尚未設定待辦事項。請前往 '%{administration} -> %{plugins}' 然後在 %{configure} 連結上按一下。當你設定欄位後,再返回這個頁面使用這個工具。" label_blocks_ids: "被禁止的工作套件 IDs" label_column_in_backlog: "待辦事項的欄位" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "減少" label_points_burn_up: "增加" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "進度阻礙" + label_sprint_new: "New sprint" label_task_board: "任務看板" - permission_view_master_backlog: "檢視主待辦事項" - permission_view_taskboards: "檢視任務看板" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "選擇完成狀態" - permission_update_sprints: "更新進度" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "待辦事項" rb_burndown_charts: show: diff --git a/modules/backlogs/config/locales/en.yml b/modules/backlogs/config/locales/en.yml index c36d4b44210..3d26e704927 100644 --- a/modules/backlogs/config/locales/en.yml +++ b/modules/backlogs/config/locales/en.yml @@ -34,6 +34,12 @@ en: activerecord: attributes: + agile/sprint: + duration: "Duration" + finish_date: "Finish date" + goal: "Sprint goal" + name: "Sprint name" + sharing: "Sharing" sprint: duration: "Sprint duration" work_package: @@ -53,6 +59,9 @@ en: sprint: cannot_end_before_it_starts: "Sprint cannot end before it starts." + models: + sprint: "Sprint" + attributes: task_type: "Task type" @@ -60,6 +69,7 @@ en: any: "any" column_width: "Column width" definition_of_done: "Definition of Done" + definition_of_done_caption: "Work packages with these statuses are treated as completed in backlog views and reporting." impediment: "Impediment" label_versions_default_fold_state: "Show versions folded" caption_versions_default_fold_state: "Versions will not be expanded by default when viewing backlogs. Each one has to be manually expanded." @@ -89,6 +99,10 @@ en: blankslate_title: "%{name} is empty" blankslate_description: "No items planned yet. Drag items here to add them." + sprint_component: + blankslate_title: "%{name} is empty" + blankslate_description: "No items planned yet. Drag items here to add them." + backlog_header_component: label_toggle_backlog: "Collapse/Expand %{name}" label_story_count: @@ -96,6 +110,13 @@ en: one: "%{count} story in backlog" other: "%{count} stories in backlog" + sprint_header_component: + label_toggle_backlog: "Collapse/Expand %{name}" + label_story_count: + zero: "No stories in sprint" + one: "%{count} story in sprint" + other: "%{count} stories in sprint" + backlog_menu_component: label_actions: "Backlog actions" action_menu: @@ -107,6 +128,15 @@ en: wiki: "Wiki" properties: "Properties" + sprint_menu_component: + label_actions: "Sprint actions" + action_menu: + edit_sprint: "Edit sprint" + new_story: "New story" + stories_tasks: "Stories/Tasks" + task_board: "Task board" + burndown_chart: "Burndown chart" + story_component: label_drag_story: "Move %{name}" @@ -140,15 +170,20 @@ en: label_backlogs_unconfigured: "You have not configured Backlogs yet. Please go to %{administration} > %{plugins}, then click on the %{configure} link for this plugin. Once you have set the fields, come back to this page to start using the tool." label_blocks_ids: "IDs of blocked work packages" label_column_in_backlog: "Column in backlog" + label_used_as_backlog: "Used as backlog" label_points_burn_down: "Down" label_points_burn_up: "Up" + label_sprint_edit: "Edit sprint" label_sprint_impediments: "Sprint Impediments" + label_sprint_new: "New sprint" label_task_board: "Task board" - permission_view_master_backlog: "View master backlog" - permission_view_taskboards: "View taskboards" + permission_create_sprints: "Create sprints" + permission_manage_sprint_items: "Manage sprint items" permission_select_done_statuses: "Select done statuses" - permission_update_sprints: "Update sprints" + permission_share_sprint: "Share sprint" + permission_start_complete_sprint: "Start/complete sprint" + permission_view_sprints: "View sprints" project_module_backlogs: "Backlogs" diff --git a/modules/backlogs/config/routes.rb b/modules/backlogs/config/routes.rb index ee9d1aba906..25c9eb65019 100644 --- a/modules/backlogs/config/routes.rb +++ b/modules/backlogs/config/routes.rb @@ -27,6 +27,23 @@ #++ Rails.application.routes.draw do + # Routes for the new Agile::Sprint + # Scoped under projects for permissions: + resources :projects, only: [] do + resources :sprints, controller: :rb_sprints, only: %i[create] do + collection do + get :new_dialog + get :refresh_form + end + + member do + get :edit_dialog + put :update_agile_sprint + end + end + end + + # Legacy routes scope "", as: "backlogs" do scope "projects/:project_id", as: "project" do resources :backlogs, controller: :rb_master_backlogs, only: :index do diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb index 18210c6e1e0..f259da89eeb 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -59,33 +59,24 @@ module OpenProject::Backlogs end OpenProject::AccessControl.permission(:edit_work_packages).tap do |edit| - edit.controller_actions << "rb_stories/move" - edit.controller_actions << "rb_stories/reorder" edit.controller_actions << "rb_tasks/update" edit.controller_actions << "rb_impediments/update" end end project_module :backlogs, dependencies: :work_package_tracking do - # Master backlog permissions - permission :view_master_backlog, + permission :view_sprints, { rb_master_backlogs: %i[index details], rb_sprints: %i[index show show_name], rb_wikis: :show, rb_stories: %i[index show], rb_queries: :show, - rb_burndown_charts: :show }, - permissible_on: :project - - permission :view_taskboards, - { rb_taskboards: :show, - rb_sprints: :show, - rb_stories: :show, + rb_burndown_charts: :show, + rb_taskboards: :show, rb_tasks: %i[index show], - rb_impediments: %i[index show], - rb_wikis: :show, - rb_burndown_charts: :show }, - permissible_on: :project + rb_impediments: %i[index show] }, + permissible_on: :project, + dependencies: :view_work_packages permission :select_done_statuses, { @@ -94,15 +85,32 @@ module OpenProject::Backlogs permissible_on: :project, require: :member - # Sprint permissions - # :show_sprints and :list_sprints are implicit in :view_master_backlog permission - permission :update_sprints, - { - rb_sprints: %i[edit_name update], - rb_wikis: %i[edit update] - }, + permission :create_sprints, + { rb_sprints: %i[new_dialog refresh_form create edit_name update edit_dialog update_agile_sprint], + rb_wikis: %i[edit update] }, permissible_on: :project, - require: :member + require: :member, + dependencies: :view_sprints + + permission :start_complete_sprint, + {}, + permissible_on: :project, + require: :member, + dependencies: :view_sprints, + visible: -> { OpenProject::FeatureDecisions.scrum_projects_active? } + + permission :manage_sprint_items, + { rb_stories: %i[move reorder] }, + permissible_on: :project, + require: :member, + dependencies: :view_sprints + + permission :share_sprint, + {}, + permissible_on: :project, + require: :member, + dependencies: :create_sprints, + visible: -> { OpenProject::FeatureDecisions.scrum_projects_active? } end menu :project_menu, diff --git a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb index 1eebc30e9f0..4a9e1ccb2bb 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_component_spec.rb @@ -31,6 +31,8 @@ require "rails_helper" RSpec.describe Backlogs::BacklogComponent, type: :component do + include Rails.application.routes.url_helpers + shared_let(:type_feature) { create(:type_feature) } shared_let(:type_task) { create(:type_task) } shared_let(:default_status) { create(:default_status) } @@ -115,6 +117,8 @@ RSpec.describe Backlogs::BacklogComponent, type: :component do render_component box = page.find(".Box") + expect(box["data-generic-drag-and-drop-target"]).to eq("container") + expect(box["data-target-container-accessor"]).to eq(":scope > ul") expect(box["data-target-id"]).to eq(sprint.id.to_s) expect(box["data-target-allowed-drag-type"]).to eq("story") end @@ -125,7 +129,7 @@ RSpec.describe Backlogs::BacklogComponent, type: :component do story_row = page.find(".Box-row[id='story_#{story1.id}']") expect(story_row["data-draggable-id"]).to eq(story1.id.to_s) expect(story_row["data-draggable-type"]).to eq("story") - expect(story_row["data-drop-url"]).to include("move") + expect(story_row["data-drop-url"]).to end_with(move_backlogs_project_sprint_story_path(project, sprint, story1)) end it "renders story rows with proper classes" do diff --git a/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb index 310642d19d2..d9dfac1d79a 100644 --- a/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/backlog_menu_component_spec.rb @@ -59,8 +59,36 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do end describe "permission-based items" do - context "with :update_sprints permission" do - let(:permissions) { %i[view_master_backlog update_sprints] } + context "with :manage_sprint_items permission" do + let(:permissions) { %i[view_sprints manage_sprint_items] } + + it "shows Add new story item with compose icon" do + render_component + + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story")) + expect(page).to have_octicon(:compose) + end + end + + context "without :manage_sprint_items permission" do + let(:permissions) { [:view_sprints] } + + it "does not show Add new story item" do + render_component + + expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story")) + end + end + + context "with :create_sprints permission" do + let(:permissions) { %i[view_sprints create_sprints] } + + it "shows Properties item with gear icon" do + render_component + + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties")) + expect(page).to have_octicon(:gear) + end it "shows Edit item with pencil icon" do render_component @@ -71,8 +99,14 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do end end - context "without :update_sprints permission" do - let(:permissions) { [:view_master_backlog] } + context "without :create_sprints permission" do + let(:permissions) { [:view_sprints] } + + it "does not show Properties item" do + render_component + + expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties")) + end it "does not show Edit item" do render_component @@ -81,50 +115,8 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do end end - context "with :add_work_packages permission" do - let(:permissions) { %i[view_master_backlog add_work_packages] } - - it "shows Add new story item with compose icon" do - render_component - - expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story")) - expect(page).to have_octicon(:compose) - end - end - - context "without :add_work_packages permission" do - let(:permissions) { [:view_master_backlog] } - - it "does not show Add new story item" do - render_component - - expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story")) - end - end - - context "with :manage_versions permission" do - let(:permissions) { %i[view_master_backlog manage_versions] } - - it "shows Properties item with gear icon" do - render_component - - expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties")) - expect(page).to have_octicon(:gear) - end - end - - context "without :manage_versions permission" do - let(:permissions) { [:view_master_backlog] } - - it "does not show Properties item" do - render_component - - expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.properties")) - end - end - - context "with :view_taskboards permission" do - let(:permissions) { %i[view_master_backlog view_taskboards] } + context "with :view_sprints permission" do + let(:permissions) { %i[view_sprints] } it "shows Task board item" do render_component @@ -132,20 +124,10 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.task_board")) end end - - context "without :view_taskboards permission" do - let(:permissions) { [:view_master_backlog] } - - it "does not show Task board item" do - render_component - - expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.task_board")) - end - end end describe "always-visible items" do - let(:permissions) { [:view_master_backlog] } + let(:permissions) { [:view_sprints] } it "renders a stable id on the action menu and stories/tasks item" do render_component @@ -190,7 +172,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do describe "module-based items" do context "when wiki module is enabled" do - let(:permissions) { [:view_master_backlog] } + let(:permissions) { [:view_sprints] } let(:project) { create(:project, types: [type_feature, type_task], enabled_module_names: %w[backlogs wiki]) } it "shows Wiki item" do @@ -202,7 +184,7 @@ RSpec.describe Backlogs::BacklogMenuComponent, type: :component do end context "when wiki module is disabled" do - let(:permissions) { [:view_master_backlog] } + let(:permissions) { [:view_sprints] } let(:project) { create(:project, types: [type_feature, type_task], enabled_module_names: %w[backlogs]) } it "does not show Wiki item" do diff --git a/modules/backlogs/spec/components/backlogs/sprint_component_spec.rb b/modules/backlogs/spec/components/backlogs/sprint_component_spec.rb new file mode 100644 index 00000000000..9eb127060ed --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/sprint_component_spec.rb @@ -0,0 +1,148 @@ +# 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. +#++ + +require "rails_helper" + +RSpec.describe Backlogs::SprintComponent, type: :component do + include Rails.application.routes.url_helpers + + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:default_status) { create(:default_status) } + shared_let(:default_priority) { create(:default_priority) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:project) { create(:project, types: [type_feature, type_task]) } + let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date: Date.yesterday, finish_date: Date.tomorrow) } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s) + + allow(user).to receive(:backlogs_preference).with(:versions_default_fold_state).and_return("open") + end + + def render_component + render_inline(described_class.new(sprint:, current_user: user)) + end + + describe "rendering" do + context "with stories" do + let!(:story1) do + create(:work_package, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 5, + position: 1, + sprint: sprint) + end + let!(:story2) do + create(:work_package, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 3, + position: 2, + sprint: sprint) + end + + it "renders a Primer::Beta::BorderBox" do + render_component + + expect(page).to have_css(".Box") + end + + it "has the sprint ID in the DOM id" do + render_component + + expect(page).to have_css(".Box#agile_sprint_#{sprint.id}") + end + + it "renders BacklogHeaderComponent in header" do + render_component + + expect(page).to have_css(".Box-header h3", text: "Sprint 1") + end + + it "renders a stable id on the sprint header" do + render_component + + expect(page).to have_element(:div, class: "Box-header", id: /\Aagile_sprint_#{sprint.id}_header\z/) + end + + it "renders StoryComponent for each story" do + render_component + + expect(page).to have_css(".Box-row", count: 2) # 2 stories + expect(page).to have_text(story1.subject) + expect(page).to have_text(story2.subject) + end + + it "has drop target data attributes" do + render_component + + box = page.find(".Box") + expect(box["data-generic-drag-and-drop-target"]).to eq("container") + expect(box["data-target-container-accessor"]).to eq(":scope > ul") + expect(box["data-target-id"]).to eq(sprint.id.to_s) + expect(box["data-target-allowed-drag-type"]).to eq("story") + end + + it "has draggable data attributes on story rows" do + render_component + + story_row = page.find(".Box-row[id='work_package_#{story1.id}']") + expect(story_row["data-draggable-id"]).to eq(story1.id.to_s) + expect(story_row["data-draggable-type"]).to eq("story") + expect(story_row["data-drop-url"]).to end_with(move_backlogs_project_sprint_story_path(project, sprint, story1)) + end + + it "renders story rows with proper classes" do + render_component + + story_row = page.find(".Box-row[id='work_package_#{story1.id}']") + expect(story_row[:class]).to include("Box-row--hover-blue") + expect(story_row[:class]).to include("Box-row--focus-gray") + expect(story_row[:class]).to include("Box-row--clickable") + end + end + + context "without stories" do + let(:rendered_component) { render_component } + + it_behaves_like "rendering Blank Slate", heading: "Sprint 1 is empty" + end + end +end diff --git a/modules/backlogs/spec/components/backlogs/sprint_header_component_spec.rb b/modules/backlogs/spec/components/backlogs/sprint_header_component_spec.rb new file mode 100644 index 00000000000..2318fd41172 --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/sprint_header_component_spec.rb @@ -0,0 +1,184 @@ +# 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. +#++ + +require "rails_helper" + +RSpec.describe Backlogs::SprintHeaderComponent, type: :component do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:default_status) { create(:default_status) } + shared_let(:default_priority) { create(:default_priority) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:project) { create(:project, types: [type_feature, type_task]) } + let(:start_date) { Date.new(2024, 1, 15) } + let(:finish_date) { Date.new(2024, 1, 29) } + let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date:, finish_date:) } + let(:state) { :show } + let(:folded) { false } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s) + end + + def render_component(folded: false) + render_inline(described_class.new(sprint:, folded:, current_user: user)) + end + + describe "show state (default)" do + context "with stories" do + let!(:story1) do + create(:story, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 5, + sprint:) + end + let!(:story2) do + create(:story, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: 3, + sprint:) + end + let!(:story_with_nil_points) do + create(:story, + project:, + type: type_feature, + status: default_status, + priority: default_priority, + story_points: nil, + sprint:) + end + + it "displays sprint name in h4" do + render_component + + expect(page).to have_css("h3", text: "Sprint 1") + end + + it "shows story count via Primer::Beta::Counter" do + render_component + + expect(page).to have_css(".Counter", text: "3") + end + + it "shows formatted date range with time tags" do + render_component + + expect(page).to have_css("time[datetime='2024-01-15']") + expect(page).to have_css("time[datetime='2024-01-29']") + end + + it "shows story points total (nil treated as 0)" do + render_component + + # 5 + 3 + 0 = 8 points + expect(page).to have_text("8 points", normalize_ws: true) + end + + it "renders collapse/expand chevrons" do + render_component + + expect(page).to have_octicon(:"chevron-up", visible: :all) + expect(page).to have_octicon(:"chevron-down", visible: :all) + end + + it "renders BacklogMenuComponent" do + render_component + + expect(page).to have_css("action-menu") + end + + it "renders a stable id on the sprint menu trigger" do + render_component + + expect(page).to have_element(:button, id: /\Aagile_sprint_#{sprint.id}_menu-button\z/) + end + end + + context "with no stories" do + let(:stories) { [] } + + it "shows 0 story count" do + render_component + + expect(page).to have_css(".Counter", text: "0") + end + + it "shows 0 points" do + render_component + + expect(page).to have_text("0 points", normalize_ws: true) + end + end + + context "when sprint has no dates" do + let(:sprint) { build_stubbed(:agile_sprint, project:, name: "Sprint 1", start_date: nil, finish_date: nil) } + + it "renders without date range" do + render_component + + expect(page).to have_no_css("time") + end + end + end + + describe "folded state" do + context "when folded is true" do + it "renders chevron-up hidden and chevron-down visible" do + render_component(folded: true) + + # When folded, chevron-up is hidden (has hidden attribute on svg) + # and chevron-down is visible (for expanding) + expect(page).to have_css("svg[hidden][data-target='collapsible-header.arrowUp']", visible: :hidden) + expect(page).to have_css("svg[data-target='collapsible-header.arrowDown']:not([hidden])", visible: :all) + end + end + + context "when folded is false" do + it "renders chevron-down hidden and chevron-up visible" do + render_component(folded: false) + + # When expanded, chevron-down is hidden (has hidden attribute) + # and chevron-up is visible (for collapsing) + expect(page).to have_css("svg[hidden][data-target='collapsible-header.arrowDown']", visible: :hidden) + expect(page).to have_css("svg[data-target='collapsible-header.arrowUp']:not([hidden])", visible: :all) + end + end + end +end diff --git a/modules/backlogs/spec/components/backlogs/sprint_menu_component_spec.rb b/modules/backlogs/spec/components/backlogs/sprint_menu_component_spec.rb new file mode 100644 index 00000000000..c2c96a07263 --- /dev/null +++ b/modules/backlogs/spec/components/backlogs/sprint_menu_component_spec.rb @@ -0,0 +1,122 @@ +# 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. +#++ + +require "rails_helper" + +RSpec.describe Backlogs::SprintMenuComponent, type: :component do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + + let(:project) { create(:project, types: [type_feature, type_task]) } + let(:sprint) { create(:agile_sprint, project:, name: "Sprint 1", start_date: Date.yesterday, finish_date: Date.tomorrow) } + let(:stories) { [] } + let(:user) { create(:user) } + let(:permissions) { [] } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [type_feature.id.to_s], "task_type" => type_task.id.to_s) + + # Set up user with specific permissions + create(:member, + project:, + principal: user, + roles: [create(:project_role, permissions:)]) + login_as(user) + end + + def render_component + render_inline(described_class.new(sprint:, project:, current_user: user)) + end + + describe "permission-based items" do + context "with :manage_sprint_items permission" do + let(:permissions) { %i[view_sprints manage_sprint_items] } + + it "shows Add new story item with compose icon" do + render_component + + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story")) + expect(page).to have_octicon(:compose) + end + end + + context "without :manage_sprint_items permission" do + let(:permissions) { [:view_sprints] } + + it "does not show Add new story item" do + render_component + + expect(page).to have_no_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.new_story")) + end + end + + context "with :create_sprints permission" do + let(:permissions) { %i[view_sprints create_sprints] } + + it "shows Edit item with pencil icon" do + render_component + + expect(page).to have_css("action-menu") + expect(page).to have_text(I18n.t("backlogs.backlog_menu_component.action_menu.edit_sprint")) + expect(page).to have_octicon(:pencil) + end + end + + context "without :create_sprints permission" do + let(:permissions) { [:view_sprints] } + + it "does not show Edit item" do + render_component + + expect(page).to have_no_text(I18n.t("backlogs.backlog_menu_component.action_menu.edit_sprint")) + end + end + end + + describe "always-visible items" do + let(:permissions) { [:view_sprints] } + + it "renders stable ids on the action menu and stories/tasks item" do + render_component + + expect(page).to have_element(:button, id: /\Aagile_sprint_#{sprint.id}_menu-button\z/) + expect(page).to have_element(:ul, id: /\Aagile_sprint_#{sprint.id}_menu-list\z/) + expect(page).to have_element(:a, id: /\Aagile_sprint_#{sprint.id}_menu_stories_tasks\z/) + end + + it "shows Stories/Tasks link" do + render_component + + expect(page).to have_text(I18n.t(:"backlogs.backlog_menu_component.action_menu.stories_tasks")) + end + end +end diff --git a/modules/backlogs/spec/components/backlogs/story_component_spec.rb b/modules/backlogs/spec/components/backlogs/story_component_spec.rb index 5481e6bfcbf..8cd5e3207eb 100644 --- a/modules/backlogs/spec/components/backlogs/story_component_spec.rb +++ b/modules/backlogs/spec/components/backlogs/story_component_spec.rb @@ -97,6 +97,13 @@ RSpec.describe Backlogs::StoryComponent, type: :component do expect(page).to have_css("action-menu") end + + it "renders a drag handle compatible with GenericDragAndDropController" do + render_component + + expect(page).to have_css(".DragHandle[role='button'][tabindex='0']") + expect(page).to have_css(".DragHandle[aria-label='Move Test Story Subject']") + end end describe "story points handling" do diff --git a/modules/backlogs/spec/contracts/sprints/create_contract_spec.rb b/modules/backlogs/spec/contracts/sprints/create_contract_spec.rb new file mode 100644 index 00000000000..35828129040 --- /dev/null +++ b/modules/backlogs/spec/contracts/sprints/create_contract_spec.rb @@ -0,0 +1,117 @@ +# 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. +#++ + +require "spec_helper" +require "contracts/shared/model_contract_shared_context" + +RSpec.describe Sprints::CreateContract do + include_context "ModelContract shared context" + + let(:project) { build_stubbed(:project) } + let(:user) { build_stubbed(:user) } + let(:sprint) do + Agile::Sprint.new(name: sprint_name, + project:, + start_date: sprint_start_date, + finish_date: sprint_finish_date, + status: sprint_status, + sharing: sprint_sharing) + end + let(:sprint_name) { "Sprint 1" } + let(:sprint_start_date) { Time.zone.today } + let(:sprint_finish_date) { Time.zone.today + 14.days } + let(:sprint_status) { "in_planning" } + let(:sprint_sharing) { "none" } + let(:permissions) { [:create_sprints] } + + subject(:contract) { described_class.new(sprint, user) } + + before do + mock_permissions_for(user) do |mock| + mock.allow_in_project(*permissions, project:) if project + end + end + + describe "validation" do + context "with valid attributes and permissions" do + it_behaves_like "contract is valid" + end + + context "when project is nil" do + let(:project) { nil } + + it_behaves_like "contract is invalid", project: :blank + end + + context "when user does not have create_sprints permission" do + let(:permissions) { [:view_work_packages] } + + it_behaves_like "contract is invalid", base: :error_unauthorized + end + + context "when user has no permissions in project" do + let(:permissions) { [] } + + it_behaves_like "contract is invalid", base: :error_unauthorized + end + + context "when name is blank" do + let(:sprint_name) { "" } + + it_behaves_like "contract is invalid", name: :blank + end + + context "when start_date is blank" do + let(:sprint_start_date) { nil } + + it_behaves_like "contract is invalid", start_date: :blank + end + + context "when finish_date is blank" do + let(:sprint_finish_date) { nil } + + it_behaves_like "contract is invalid", finish_date: %i[blank blank] + end + + context "when finish_date is before start_date" do + let(:sprint_start_date) { Time.zone.today } + let(:sprint_finish_date) { Time.zone.today - 1.day } + + it_behaves_like "contract is invalid", finish_date: %i[greater_than_or_equal_to] + end + + context "when user is admin without project permission" do + let(:user) { build_stubbed(:admin) } + let(:permissions) { [] } + + it_behaves_like "contract is valid" + end + end +end diff --git a/modules/backlogs/spec/controllers/rb_sprints_controller_permissions_spec.rb b/modules/backlogs/spec/controllers/rb_sprints_controller_permissions_spec.rb index e0a372b0f3c..d415deb0221 100644 --- a/modules/backlogs/spec/controllers/rb_sprints_controller_permissions_spec.rb +++ b/modules/backlogs/spec/controllers/rb_sprints_controller_permissions_spec.rb @@ -39,7 +39,7 @@ RSpec.describe RbSprintsController, "permissions" do create(:project, enabled_module_names: %w[work_package_tracking backlogs]).tap do |p| create(:member, user: current_user, - roles: [create(:project_role, permissions: [:update_sprints])], + roles: [create(:project_role, permissions: [:create_sprints])], project: p) end end @@ -80,7 +80,7 @@ RSpec.describe RbSprintsController, "permissions" do before do create(:member, user: current_user, - roles: [create(:project_role, permissions: %i[view_work_packages view_versions update_sprints])], + roles: [create(:project_role, permissions: %i[view_work_packages view_versions create_sprints])], project: sprint_project) end diff --git a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb index 4219debbf88..396fb2b9e1b 100644 --- a/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb +++ b/modules/backlogs/spec/controllers/rb_sprints_controller_spec.rb @@ -31,123 +31,362 @@ require "rails_helper" RSpec.describe RbSprintsController do - shared_let(:type_feature) { create(:type_feature) } - shared_let(:type_task) { create(:type_task) } - shared_let(:user) { create(:admin) } - current_user { user } + describe "inline name actions" do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:user) { create(:admin) } + current_user { user } - let(:visible_projects_scope) { instance_double(ActiveRecord::Relation) } - let(:visible_sprints_scope) { instance_double(ActiveRecord::Relation) } - - before do - allow(Setting) - .to receive(:plugin_openproject_backlogs) - .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) - - allow(Project) - .to receive(:visible) - .and_return(visible_projects_scope) - - allow(visible_projects_scope) - .to receive(:find) - .with(project.identifier) - .and_return(project) - - allow(Sprint) - .to receive(:visible) - .and_return(visible_sprints_scope) - - allow(visible_sprints_scope) - .to receive(:find) - .with(sprint.id.to_s) - .and_return(sprint) - end - - describe "GET #edit_name" do - let(:project) { build_stubbed(:project) } - let(:sprint) { build_stubbed(:sprint) } - - it "responds with success", :aggregate_failures do - get :edit_name, params: { project_id: project.identifier, id: sprint.id }, format: :turbo_stream - - expect(response).to be_successful - expect(response).to have_http_status :ok - expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" - assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"]) - expect(assigns(:project)).to eq(project) - expect(assigns(:sprint)).to eq(sprint) - expect(assigns(:backlog)).to be_a(Backlog) - end - end - - describe "GET #show_name" do - let(:project) { build_stubbed(:project) } - let(:sprint) { build_stubbed(:sprint) } - - it "responds with success", :aggregate_failures do - get :show_name, params: { project_id: project.identifier, id: sprint.id }, format: :turbo_stream - - expect(response).to be_successful - expect(response).to have_http_status :ok - expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" - assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"]) - expect(assigns(:project)).to eq(project) - expect(assigns(:sprint)).to eq(sprint) - expect(assigns(:backlog)).to be_a(Backlog) - end - end - - describe "PATCH #update" do - let(:project) { build_stubbed(:project) } - let(:sprint) { build_stubbed(:sprint) } + let(:visible_projects_scope) { instance_double(ActiveRecord::Relation) } + let(:visible_sprints_scope) { instance_double(ActiveRecord::Relation) } before do - update_service = instance_double(Versions::UpdateService, call: service_result) + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) - allow(Versions::UpdateService) - .to receive(:new) - .with(user:, model: sprint) - .and_return(update_service) + allow(Project) + .to receive(:visible) + .and_return(visible_projects_scope) + + allow(visible_projects_scope) + .to receive(:find) + .with(project.identifier) + .and_return(project) + + allow(Sprint) + .to receive(:visible) + .and_return(visible_sprints_scope) + + allow(visible_sprints_scope) + .to receive(:find) + .with(sprint.id.to_s) + .and_return(sprint) end - context "when service call succeeds" do - let(:service_result) { ServiceResult.success(result: sprint) } + describe "GET #edit_name" do + let(:project) { build_stubbed(:project) } + let(:sprint) { build_stubbed(:sprint) } it "responds with success", :aggregate_failures do - patch :update, params: { project_id: project.identifier, id: sprint.id, sprint: { name: "Updated Sprint" } }, - format: :turbo_stream + get :edit_name, params: { project_id: project.identifier, id: sprint.id }, format: :turbo_stream expect(response).to be_successful expect(response).to have_http_status :ok expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"]) - expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" expect(assigns(:project)).to eq(project) expect(assigns(:sprint)).to eq(sprint) expect(assigns(:backlog)).to be_a(Backlog) end end - context "when service call fails" do - let(:service_result) { ServiceResult.failure(result: sprint) } + describe "GET #show_name" do + let(:project) { build_stubbed(:project) } + let(:sprint) { build_stubbed(:sprint) } - before do - project.name = "" - end + it "responds with success", :aggregate_failures do + get :show_name, params: { project_id: project.identifier, id: sprint.id }, format: :turbo_stream - it "responds with 422", :aggregate_failures do - patch :update, params: { project_id: project.identifier, id: sprint.id, sprint: { name: "" } }, - format: :turbo_stream - - expect(response).not_to be_successful - expect(response).to have_http_status :unprocessable_entity + expect(response).to be_successful + expect(response).to have_http_status :ok expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"]) - expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" expect(assigns(:project)).to eq(project) expect(assigns(:sprint)).to eq(sprint) expect(assigns(:backlog)).to be_a(Backlog) end end + + describe "PATCH #update" do + let(:project) { build_stubbed(:project) } + let(:sprint) { build_stubbed(:sprint) } + + before do + update_service = instance_double(Versions::UpdateService, call: service_result) + + allow(Versions::UpdateService) + .to receive(:new) + .with(user:, model: sprint) + .and_return(update_service) + end + + context "when service call succeeds" do + let(:service_result) { ServiceResult.success(result: sprint) } + + it "responds with success", :aggregate_failures do + patch :update, params: { project_id: project.identifier, id: sprint.id, sprint: { name: "Updated Sprint" } }, + format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" + assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"]) + expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + expect(assigns(:backlog)).to be_a(Backlog) + end + end + + context "when service call fails" do + let(:service_result) { ServiceResult.failure(result: sprint) } + + before do + project.name = "" + end + + it "responds with 422", :aggregate_failures do + patch :update, params: { project_id: project.identifier, id: sprint.id, sprint: { name: "" } }, + format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :unprocessable_entity + expect(response).to have_turbo_stream action: "update", target: "backlogs-backlog-header-component-#{sprint.id}" + assert_select %(turbo-stream[action="update"][target="backlogs-backlog-header-component-#{sprint.id}"][method="morph"]) + expect(response).to have_turbo_stream action: "flash", target: "op-primer-flash-component" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + expect(assigns(:backlog)).to be_a(Backlog) + end + end + end + end + + describe "new actions" do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + + let(:all_permissions) { %i[view_sprints view_work_packages create_sprints] } + let(:permissions) { all_permissions } + let(:user) do + create(:user, member_with_permissions: { project => permissions }) + end + let(:project) { create(:project) } + + current_user { user } + + before do + # Necessary to get the controller running due to check_if_plugin_is_configured + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) + end + + describe "GET #new_dialog" do + context "with the feature flag inactive" do + it "responds with forbidden" do + get :new_dialog, params: { project_id: project.id }, format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + + context "with the feature flag active", with_flag: { scrum_projects: true } do + it "responds with success", :aggregate_failures do + get :new_dialog, params: { project_id: project.id }, format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "dialog", target: "backlogs-new-sprint-dialog-component" + expect(assigns(:project)).to eq(project) + end + + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } + + it "responds with forbidden", :aggregate_failures do + get :new_dialog, params: { project_id: project.id }, format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + end + end + + describe "GET #edit_dialog" do + let!(:sprint) { create(:agile_sprint, project:) } + + context "with the feature flag inactive" do + it "responds with forbidden" do + get :edit_dialog, params: { project_id: project.id, id: sprint.id }, format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + + context "with the feature flag active", with_flag: { scrum_projects: true } do + it "responds with success", :aggregate_failures do + get :edit_dialog, params: { project_id: project.id, id: sprint.id }, format: :turbo_stream + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "dialog", target: "backlogs-new-sprint-dialog-component" + expect(assigns(:project)).to eq(project) + expect(assigns(:sprint)).to eq(sprint) + end + + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } + + it "responds with forbidden", :aggregate_failures do + get :edit_dialog, params: { project_id: project.id, id: sprint.id }, format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + end + end + + describe "POST #create" do + context "with the feature flag inactive" do + it "responds with forbidden" do + post :create, params: { project_id: project.id }, format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + + context "with the feature flag active", with_flag: { scrum_projects: true } do + let(:params) do + { + project_id: project.id, + sprint: { name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } + } + end + + it "responds with success, creates a sprint, and redirects to backlogs", :aggregate_failures do + post :create, format: :turbo_stream, params: params + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response.body).to include("turbo-stream") + expect(response.body).to include("action=\"redirect_to\"") + expect(response.body).to include(backlogs_project_backlogs_path(project)) + expect(project.reload.sprints.last.name).to eq("My Sprint") + expect(flash[:notice]).to eq(I18n.t(:notice_successful_create)) + end + + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } + + it "responds with forbidden", :aggregate_failures do + post :create, format: :turbo_stream, params: params + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + end + end + + describe "PUT #update_agile_sprint" do + let!(:sprint) { create(:agile_sprint, name: "Original sprint name", project:) } + + context "with the feature flag inactive" do + it "responds with forbidden" do + put :update_agile_sprint, params: { id: sprint.id, project_id: project.id }, format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + + context "with the feature flag active", with_flag: { scrum_projects: true } do + let(:params) do + { + id: sprint.id, + project_id: project.id, + sprint: { name: "Changed sprint name" } + } + end + + it "responds with success", :aggregate_failures do + put :update_agile_sprint, format: :turbo_stream, params: params + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response.body).to have_turbo_stream action: "flash" + expect(response.body).to have_turbo_stream action: "update", target: "backlogs-sprint-header-component-#{sprint.id}" + assert_select %(turbo-stream[action="update"][target="backlogs-sprint-header-component-#{sprint.id}"][method="morph"]) + expect(response.body).to include("Successful update.") + expect(sprint.reload.name).to eq("Changed sprint name") + end + + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } + + it "responds with forbidden", :aggregate_failures do + put :update_agile_sprint, format: :turbo_stream, params: params + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + end + end + + describe "GET #refresh_form" do + context "with the feature flag inactive" do + it "responds with forbidden" do + get :refresh_form, params: { project_id: project.id }, format: :turbo_stream + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + + context "with the feature flag active", with_flag: { scrum_projects: true } do + let(:params) do + { + project_id: project.id, + sprint: { name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } + } + end + + it "responds with success", :aggregate_failures do + get :refresh_form, format: :turbo_stream, params: params + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "update", target: "backlogs-new-sprint-form-component" + expect(assigns(:sprint)).to be_nil + end + + context "without the 'create_sprints' permission" do + let(:permissions) { all_permissions - [:create_sprints] } + + it "responds with forbidden", :aggregate_failures do + get :refresh_form, format: :turbo_stream, params: params + + expect(response).not_to be_successful + expect(response).to have_http_status :forbidden + end + end + + context "when refreshing the form in edit mode by passing a sprint id" do + let!(:sprint) { create(:agile_sprint, project:) } + let(:params) do + { + project_id: project.id, + sprint: { id: sprint.id, name: "My Sprint", start_date: "2025-10-05", finish_date: "2025-10-15" } + } + end + + it "responds with success", :aggregate_failures do + get :refresh_form, format: :turbo_stream, params: params + + expect(response).to be_successful + expect(response).to have_http_status :ok + expect(response).to have_turbo_stream action: "update", target: "backlogs-new-sprint-form-component" + end + end + end + end end end diff --git a/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb b/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb new file mode 100644 index 00000000000..b474ea826a8 --- /dev/null +++ b/modules/backlogs/spec/controllers/rb_taskboards_controller_spec.rb @@ -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. +#++ + +require "spec_helper" + +RSpec.describe RbTaskboardsController do + shared_let(:type_feature) { create(:type_feature) } + shared_let(:type_task) { create(:type_task) } + shared_let(:user) { create(:admin) } + current_user { user } + + let(:permissions) { [] } + let(:project) { create(:project, member_with_permissions: { user => permissions }) } + let(:status) { create(:status, name: "status 1", is_default: true) } + let(:sprint) { create(:sprint, project:) } + let(:story) { create(:story, status:, version: sprint, project:) } + + before do + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return({ "story_types" => [type_feature.id], "task_type" => type_task.id }) + end + + describe "GET show" do + before do + get :show, params: { project_id: project.identifier, sprint_id: sprint.id } + end + + it "performs that request" do + expect(response).to be_successful + expect(response).to render_template :show + end + + context "as a member with view_sprints permission" do + let(:user) { create(:user) } + let(:permissions) { %i[view_sprints view_work_packages] } + + it "grants access" do + expect(response).to be_successful + expect(response).to render_template :show + end + end + + context "as a member without view_sprints permission" do + let(:user) { create(:user) } + let(:permissions) { [:view_project] } + + it "denies access" do + expect(response).to have_http_status(:not_found) + end + end + + context "as a non-member" do + current_user { create(:user) } + + it "denies access" do + expect(response).to have_http_status(:not_found) + end + end + end +end diff --git a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb index 540699b5aea..5800e48d2ca 100644 --- a/modules/backlogs/spec/features/backlogs/context_menu_spec.rb +++ b/modules/backlogs/spec/features/backlogs/context_menu_spec.rb @@ -38,9 +38,9 @@ RSpec.describe "Backlogs context menu", :js do shared_let(:user) do create(:user, member_with_permissions: { project => %i[add_work_packages - view_master_backlog - view_taskboards - view_work_packages] }) + view_sprints + view_work_packages + manage_sprint_items] }) end shared_let(:sprint) do create(:version, @@ -134,9 +134,9 @@ RSpec.describe "Backlogs context menu", :js do end end - context "when the user does not have add_work_packages permission" do + context "when the user does not have manage_sprint_items permission" do before do - RolePermission.where(permission: "add_work_packages").delete_all + RolePermission.where(permission: "manage_sprint_items").delete_all end it 'does not display the "New story" menu entry' do @@ -146,18 +146,6 @@ RSpec.describe "Backlogs context menu", :js do end end - context "when the user does not have view_taskboards permission" do - before do - RolePermission.where(permission: "view_taskboards").delete_all - end - - it 'does not display the "Task board" menu entry' do - within_backlog_context_menu do |menu| - expect(menu).to have_no_selector :menuitem, "Task board" - end - end - end - context "when the wiki module is not enabled" do before do project.enabled_module_names -= ["wiki"] diff --git a/modules/backlogs/spec/features/backlogs/create_story_spec.rb b/modules/backlogs/spec/features/backlogs/create_story_spec.rb index 8563a3dd03b..37e3d888493 100644 --- a/modules/backlogs/spec/features/backlogs/create_story_spec.rb +++ b/modules/backlogs/spec/features/backlogs/create_story_spec.rb @@ -54,9 +54,9 @@ RSpec.describe "Backlogs", :js do let(:user) do create(:user, member_with_permissions: { project => %i(add_work_packages - view_master_backlog + view_sprints view_work_packages - assign_versions) }) + manage_sprint_items) }) end let(:project) { create(:project) } diff --git a/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb b/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb index 0cd6e4093eb..d79c85f67f6 100644 --- a/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb +++ b/modules/backlogs/spec/features/backlogs_in_backlog_view_spec.rb @@ -52,14 +52,14 @@ RSpec.describe "Backlogs in backlog view", :js do create(:project_role, permissions: %i( view_project - view_master_backlog + view_sprints + create_sprints + manage_sprint_items add_work_packages view_work_packages edit_work_packages manage_subtasks manage_versions - update_sprints - assign_versions )) end let!(:current_user) do @@ -123,13 +123,13 @@ RSpec.describe "Backlogs in backlog view", :js do # Versions can be folded backlogs_page - .expect_story_in_sprint(sprint_story1, sprint) + .expect_story_in_backlog(sprint_story1, sprint) backlogs_page .fold_backlog(sprint) backlogs_page - .expect_story_not_in_sprint(sprint_story1, sprint) + .expect_story_not_in_backlog(sprint_story1, sprint) # The backlogs can be folded by default visit my_interface_path @@ -142,13 +142,13 @@ RSpec.describe "Backlogs in backlog view", :js do backlogs_page.visit! backlogs_page - .expect_story_not_in_sprint(sprint_story1, sprint) + .expect_story_not_in_backlog(sprint_story1, sprint) backlogs_page .fold_backlog(sprint) backlogs_page - .expect_story_in_sprint(sprint_story1, sprint) + .expect_story_in_backlog(sprint_story1, sprint) # Alter the attributes of the sprint sleep(0.5) diff --git a/modules/backlogs/spec/features/empty_backlogs_spec.rb b/modules/backlogs/spec/features/empty_backlogs_spec.rb index b558b1914ac..9e4ba698e67 100644 --- a/modules/backlogs/spec/features/empty_backlogs_spec.rb +++ b/modules/backlogs/spec/features/empty_backlogs_spec.rb @@ -57,7 +57,7 @@ RSpec.describe "Empty backlogs project", end context "as regular member" do - let(:role) { create(:project_role, permissions: %i(view_master_backlog)) } + let(:role) { create(:project_role, permissions: %i(view_sprints)) } let(:current_user) { create(:user, member_with_roles: { project => role }) } it "shows a blankslate without description" do diff --git a/modules/backlogs/spec/features/impediments_spec.rb b/modules/backlogs/spec/features/impediments_spec.rb index 814071a1a3a..725bacfd8bb 100644 --- a/modules/backlogs/spec/features/impediments_spec.rb +++ b/modules/backlogs/spec/features/impediments_spec.rb @@ -54,12 +54,12 @@ RSpec.describe "Impediments on taskboard", :js, end let(:role) do create(:project_role, - permissions: %i(view_taskboards + permissions: %i(view_sprints add_work_packages view_work_packages edit_work_packages manage_subtasks - assign_versions + manage_sprint_items work_package_assigned)) end let!(:current_user) do diff --git a/modules/backlogs/spec/features/projects/backlogs_settings_spec.rb b/modules/backlogs/spec/features/projects/backlogs_settings_spec.rb new file mode 100644 index 00000000000..32e090f3137 --- /dev/null +++ b/modules/backlogs/spec/features/projects/backlogs_settings_spec.rb @@ -0,0 +1,96 @@ +# 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. +#++ + +require "spec_helper" +require_relative "../../support/pages/projects/settings/backlogs" + +RSpec.describe "Backlogs Project Settings", :js do + let!(:project) do + create(:project, + enabled_module_names: %w(backlogs)) + end + let!(:closed_status) { create(:status, name: "Closed", is_closed: true) } + let!(:closed_like_status) { create(:status, name: "Sorta kinda Finished", is_default: true) } + let(:role) do + create(:project_role, + permissions: %i[select_done_statuses]) + end + let!(:current_user) do + create(:user, + member_with_roles: { project => role }) + end + let(:settings_page) { Pages::Projects::Settings::Backlogs.new(project) } + let(:done_status_ids_autocompleter) { FormFields::Primerized::AutocompleteField.new("story_types", selector: "[data-test-selector='done_status_ids_autocomplete']") } + + before do + login_as current_user + end + + it "allows setting a status as done although it is not closed" do + settings_page.visit! + + expect(page).to have_heading "Backlogs" + + wait_for_network_idle + wait_for_autocompleter_options_to_be_loaded + + done_status_ids_autocompleter.expect_blank + done_status_ids_autocompleter.select_option "Closed" + done_status_ids_autocompleter.select_option "Sorta kinda Finished" + + done_status_ids_autocompleter.expect_selected "Closed" + done_status_ids_autocompleter.expect_selected "Sorta kinda Finished" + done_status_ids_autocompleter.expect_not_disabled "Definition of Done" + + done_status_ids_autocompleter.close_autocompleter + + click_button "Save" + + expect_flash(type: :success, message: "Successful update") + + wait_for_network_idle + wait_for_autocompleter_options_to_be_loaded + + done_status_ids_autocompleter.expect_selected "Closed" + done_status_ids_autocompleter.expect_selected "Sorta kinda Finished" + + done_status_ids_autocompleter.deselect_option "Sorta kinda Finished" + + click_button "Save" + + wait_for_network_idle + wait_for_autocompleter_options_to_be_loaded + + expect_flash(type: :success, message: "Successful update") + + done_status_ids_autocompleter.expect_selected "Closed" + done_status_ids_autocompleter.expect_not_selected "Sorta kinda Finished" + end +end diff --git a/modules/backlogs/spec/features/sprints/create_spec.rb b/modules/backlogs/spec/features/sprints/create_spec.rb new file mode 100644 index 00000000000..2a3b3a4de17 --- /dev/null +++ b/modules/backlogs/spec/features/sprints/create_spec.rb @@ -0,0 +1,208 @@ +# 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. +#++ + +require "spec_helper" +require_relative "../../support/pages/backlogs" + +RSpec.describe "Create", :js do + let(:project) { create(:project) } + let(:all_permissions) { %i[view_sprints view_work_packages create_sprints] } + let(:permissions) { all_permissions } + let(:user) do + create(:user, member_with_permissions: { project => permissions }) + end + let(:backlogs_page) { Pages::Backlogs.new(project) } + + let!(:initial_sprint) do + create(:agile_sprint, + project:, + name: "Initial sprint", + start_date: Date.new(2025, 9, 5), + finish_date: Date.new(2025, 9, 15)) + end + + let(:story_type) do + create(:type_feature) + end + let(:story_type2) do + type = create(:type) + + project.types << type + + type + end + let(:inactive_story_type) do + create(:type) + end + + let(:task_type) do + type = create(:type_task) + project.types << type + + type + end + + before do + login_as(user) + + # Legacy backlogs module requires type configuration + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [story_type.id.to_s, + story_type2.id.to_s, + inactive_story_type.id.to_s], + "task_type" => task_type.id.to_s) + + backlogs_page.visit! + end + + context "with the feature flag active", with_flag: { scrum_projects: true } do + context "with the 'create_sprints' permissions" do + let(:start_date) { Date.new(2025, 10, 5) } + let(:start_date_fmt) { start_date.strftime("%Y-%m-%d") } + let(:finish_date) { Date.new(2025, 10, 20) } + let(:finish_date_fmt) { finish_date.strftime("%Y-%m-%d") } + + it "allows creating a new sprint" do + backlogs_page.expect_sprint_names_in_order(initial_sprint.name) + + backlogs_page.open_create_sprint_dialog + + within_dialog "New sprint" do + page.fill_in "Sprint name", with: "Created sprint" + page.fill_in "Start date", with: start_date_fmt + page.fill_in "Finish date", with: finish_date_fmt + + click_on "Create" + end + + expect_and_dismiss_flash(message: "Successful creation.") + backlogs_page.expect_sprint_names_in_order(initial_sprint.name, "Created sprint") + + sprint = project.reload.sprints.last + expect(sprint).to be_present + expect(sprint.name).to eq "Created sprint" + expect(sprint.start_date).to eq start_date + expect(sprint.finish_date).to eq finish_date + end + + it "previews the sprint duration when changing the dates" do + backlogs_page.open_create_sprint_dialog + + within_dialog "New sprint" do + expect(page).to have_field "Duration", with: "", readonly: true + + page.fill_in "Start date", with: start_date_fmt + page.fill_in "Finish date", with: finish_date_fmt + + expect(page).to have_field "Duration", with: "16 days", readonly: true + end + end + + describe "validations" do + let(:too_early_finish_date) { start_date - 1.day } + + it "validates required fields are present" do + backlogs_page.open_create_sprint_dialog + + within_dialog "New sprint" do + page.fill_in "Sprint name", with: "" + + click_on "Create" + + expect(page).to have_field "Sprint name", validation_error: "can't be blank" + expect(page).to have_field "Start date", validation_error: "can't be blank" + expect(page).to have_field "Finish date", validation_error: "can't be blank" + end + end + + it "validates finish date is not before start date" do + backlogs_page.open_create_sprint_dialog + + within_dialog "New sprint" do + page.fill_in "Start date", with: start_date_fmt + page.fill_in "Finish date", with: too_early_finish_date.strftime("%Y-%m-%d") + + # Shows duration as zero if finish date is before start date: + expect(page).to have_field "Duration", with: "0 days", readonly: true + + click_on "Create" + + expect(page).to have_field("Finish date", + validation_error: "must be greater than or equal to #{start_date_fmt}") + end + end + end + + describe "proposed sprint names" do + let!(:initial_sprint) { nil } # override so that initial sprint is not present + + it "prefilled with 'Sprint 1' if there are no previous sprints" do + backlogs_page.open_create_sprint_dialog + + within_dialog "New sprint" do + expect(page).to have_field "Sprint name *", with: "Sprint 1", required: true, focused: true + end + end + + context "with a previous sprint" do + before do + create(:agile_sprint, name: "Be ambitious 42", project:) + + backlogs_page.visit! + backlogs_page.open_create_sprint_dialog + end + + it "offers the next sprint name with a number increment" do + within_dialog "New sprint" do + expect(page).to have_field "Sprint name *", with: "Be ambitious 43" + end + end + end + end + end + + context "without the necessary permissions" do + let(:permissions) { all_permissions - [:create_sprints] } + + it "is missing the 'new sprint' button" do + expect(page).to have_no_button "Create" + expect(page).not_to have_test_selector("op-sprints--new-sprint-button") + end + end + end + + context "with the feature flag inactive" do + it "is missing the 'new sprint' button" do + expect(page).to have_no_button "Create" + expect(page).not_to have_test_selector("op-sprints--new-sprint-button") + end + end +end diff --git a/modules/backlogs/spec/features/sprints/edit_spec.rb b/modules/backlogs/spec/features/sprints/edit_spec.rb new file mode 100644 index 00000000000..a41ba4f2ff3 --- /dev/null +++ b/modules/backlogs/spec/features/sprints/edit_spec.rb @@ -0,0 +1,192 @@ +# 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. +#++ + +require "spec_helper" +require_relative "../../support/pages/backlogs" + +RSpec.describe "Edit", :js do + let(:project) { create(:project) } + let(:all_permissions) { %i[view_sprints add_work_packages view_work_packages create_sprints manage_sprint_items] } + let(:permissions) { all_permissions } + let(:user) do + create(:user, member_with_permissions: { project => permissions }) + end + let(:backlogs_page) { Pages::Backlogs.new(project) } + + let(:story_type) do + create(:type_feature) + end + let(:story_type2) do + type = create(:type) + + project.types << type + + type + end + let(:inactive_story_type) do + create(:type) + end + + let(:task_type) do + type = create(:type_task) + project.types << type + + type + end + + let!(:closed_sprint) do + create(:agile_sprint, + project:, + status: "completed", + start_date: Date.new(2025, 8, 25), + finish_date: Date.new(2025, 9, 4)) + end + + let!(:first_sprint) do + create(:agile_sprint, + project:, + start_date: Date.new(2025, 9, 5), + finish_date: Date.new(2025, 9, 15)) + end + + let!(:second_sprint) do + create(:agile_sprint, + project:, + start_date: Date.new(2025, 9, 16), + finish_date: Date.new(2025, 9, 26)) + end + + let!(:work_package) do + create(:work_package, subject: "First work package", project:, sprint: first_sprint, type: story_type) + end + + # Necessary so that work packages can be created via dialog + shared_let(:default_status) { create(:default_status) } + shared_let(:default_priority) { create(:default_priority) } + + before do + login_as(user) + + # Legacy backlogs module requires type configuration + allow(Setting) + .to receive(:plugin_openproject_backlogs) + .and_return("story_types" => [story_type.id.to_s, + story_type2.id.to_s, + inactive_story_type.id.to_s], + "task_type" => task_type.id.to_s) + + backlogs_page.visit! + end + + context "with the feature flag active", with_flag: { scrum_projects: true } do + it "lists all open sprints" do + backlogs_page.expect_sprint_names_in_order(first_sprint.name, second_sprint.name) + + backlogs_page.expect_story_in_sprint(work_package, first_sprint) + backlogs_page.expect_story_not_in_sprint(work_package, second_sprint) + end + + it "adds a work package to a sprint" do + backlogs_page.click_in_sprint_menu(first_sprint, "New story") + backlogs_page.expect_create_work_package_dialog + + page.within("#create-work-package-dialog") do + page.fill_in "Subject", with: "Story created in sprint" + + click_on "Create" + end + + wait_for_reload + + expect_and_dismiss_flash type: :success, message: "New work package created and added as a child" + created_wp = first_sprint.reload.work_packages.last + expect(created_wp.subject).to eq("Story created in sprint") + backlogs_page.expect_story_in_sprint(created_wp, first_sprint) + end + + context "with the 'create_sprints' permissions" do + context "when editing a sprint" do + it "displays all menu entries" do + backlogs_page.within_sprint_menu(first_sprint) do |menu| + expect(menu).to have_selector :menuitem, count: 3 + expect(menu).to have_selector :menuitem, "Edit sprint" + expect(menu).to have_selector :menuitem, "New story" + expect(menu).to have_selector :menuitem, "Stories/Tasks" + end + end + + it "edits the sprint name" do + backlogs_page.expect_sprint_names_in_order(first_sprint.name, second_sprint.name) + + backlogs_page.click_in_sprint_menu(first_sprint, "Edit sprint") + backlogs_page.expect_sprint_dialog + + within_dialog "Edit sprint" do + page.fill_in "Sprint name", with: "Changed name" + page.click_button "Save" + end + + wait_for_reload + backlogs_page.expect_sprint_names_in_order("Changed name", second_sprint.name) + end + + context "when lacking the 'manage_sprint_items' permission" do + let(:permissions) { all_permissions - %i[manage_sprint_items] } + + it "has no menu entry for creating a new story" do + backlogs_page.within_sprint_menu(first_sprint) do |menu| + expect(menu).to have_selector :menuitem, count: 2 + expect(menu).to have_selector :menuitem, "Edit sprint" + expect(menu).to have_selector :menuitem, "Stories/Tasks" + + expect(menu).to have_no_selector :menuitem, "New story" + end + end + end + end + end + + context "without the necessary permissions" do + let(:permissions) { all_permissions - [:create_sprints] } + + it "is missing the 'new sprint' button" do + expect(page).to have_no_button "Create" + expect(page).not_to have_test_selector("op-sprints--new-sprint-button") + end + + it "has no menu entry for editing a sprint" do + backlogs_page.within_sprint_menu(first_sprint) do |menu| + expect(menu).to have_selector :menuitem, "Stories/Tasks" + expect(menu).to have_no_selector :menuitem, "Edit sprint" + end + end + end + end +end diff --git a/modules/backlogs/spec/features/stories_in_backlog_spec.rb b/modules/backlogs/spec/features/stories_in_backlog_spec.rb index 9c554cb6e21..37dc5efc407 100644 --- a/modules/backlogs/spec/features/stories_in_backlog_spec.rb +++ b/modules/backlogs/spec/features/stories_in_backlog_spec.rb @@ -52,12 +52,12 @@ RSpec.describe "Stories in backlog", :js, :settings_reset do end let(:role) do create(:project_role, - permissions: %i(view_master_backlog + permissions: %i(view_sprints + manage_sprint_items add_work_packages view_work_packages edit_work_packages - manage_subtasks - assign_versions)) + manage_subtasks)) end let!(:current_user) do create(:user, @@ -144,22 +144,22 @@ RSpec.describe "Stories in backlog", :js, :settings_reset do it "displays stories in correct order, calculates velocity, and allows editing story points" do backlogs_page - .expect_story_in_sprint(sprint_story1, sprint) + .expect_story_in_backlog(sprint_story1, sprint) backlogs_page - .expect_story_in_sprint(sprint_story2, sprint) + .expect_story_in_backlog(sprint_story2, sprint) backlogs_page - .expect_story_in_sprint(backlog_story1, backlog) + .expect_story_in_backlog(backlog_story1, backlog) backlogs_page - .expect_story_not_in_sprint(sprint_story2_parent, sprint) + .expect_story_not_in_backlog(sprint_story2_parent, sprint) backlogs_page - .expect_story_not_in_sprint(sprint_story1_task, sprint) + .expect_story_not_in_backlog(sprint_story1_task, sprint) backlogs_page - .expect_story_not_in_sprint(sprint_story_in_other_project, sprint) + .expect_story_not_in_backlog(sprint_story_in_other_project, sprint) backlogs_page .expect_stories_in_order(sprint, sprint_story1, sprint_story2) @@ -182,8 +182,8 @@ RSpec.describe "Stories in backlog", :js, :settings_reset do backlogs_page .edit_story_in_details_view(sprint_story1, version: backlog) - backlogs_page.expect_story_not_in_sprint(sprint_story1, sprint) - backlogs_page.expect_story_in_sprint(sprint_story1, backlog) + backlogs_page.expect_story_not_in_backlog(sprint_story1, sprint) + backlogs_page.expect_story_in_backlog(sprint_story1, backlog) end it "switches the details view from one story to another" do @@ -191,19 +191,19 @@ RSpec.describe "Stories in backlog", :js, :settings_reset do .click_in_story_menu(sprint_story1, "Open details view") backlogs_page.expect_details_view(sprint_story1) - backlogs_page.expect_story_in_sprint(sprint_story2, sprint) + backlogs_page.expect_story_in_backlog(sprint_story2, sprint) backlogs_page .click_in_story_menu(sprint_story2, "Open details view") backlogs_page.expect_details_view(sprint_story2) - backlogs_page.expect_story_in_sprint(sprint_story1, sprint) + backlogs_page.expect_story_in_backlog(sprint_story1, sprint) end it "removes story from sprint when type is changed to non-story type via details view" do backlogs_page .edit_story_in_details_view(sprint_story2, type: task.name) - backlogs_page.expect_story_not_in_sprint(sprint_story2, sprint) + backlogs_page.expect_story_not_in_backlog(sprint_story2, sprint) end end diff --git a/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb b/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb index 42af6abe007..f9539baf4c2 100644 --- a/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb +++ b/modules/backlogs/spec/features/tasks_on_taskboard_spec.rb @@ -51,12 +51,12 @@ RSpec.describe "Tasks on taskboard", :js, end let(:role) do create(:project_role, - permissions: %i(view_taskboards + permissions: %i(view_sprints add_work_packages view_work_packages edit_work_packages manage_subtasks - assign_versions + manage_sprint_items work_package_assigned)) end let!(:current_user) do diff --git a/modules/backlogs/spec/lib/open_project/backlogs/permissions_spec.rb b/modules/backlogs/spec/lib/open_project/backlogs/permissions_spec.rb new file mode 100644 index 00000000000..8d32adaaf88 --- /dev/null +++ b/modules/backlogs/spec/lib/open_project/backlogs/permissions_spec.rb @@ -0,0 +1,89 @@ +# 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. +#++ + +require "spec_helper" + +RSpec.describe OpenProject::AccessControl, "Backlogs module permissions" do # rubocop:disable RSpec/SpecFilePathFormat + describe "view_sprints" do + subject { described_class.permission(:view_sprints) } + + it "depends on view_work_packages" do + expect(subject.dependencies).to contain_exactly(:view_work_packages) + end + end + + describe "create_sprints" do + subject { described_class.permission(:create_sprints) } + + it "depends on view_sprints" do + expect(subject.dependencies).to contain_exactly(:view_sprints) + end + end + + describe "manage_sprint_items" do + subject { described_class.permission(:manage_sprint_items) } + + it "depends on view_sprints, add_work_packages, and edit_work_packages" do + expect(subject.dependencies).to contain_exactly(:view_sprints) + end + end + + describe "start_complete_sprint" do + subject { described_class.permission(:start_complete_sprint) } + + it "depends on view_sprints" do + expect(subject.dependencies).to contain_exactly(:view_sprints) + end + + context "when scrum_projects feature flag is active", with_flag: { scrum_projects: true } do + it { is_expected.to be_visible } + end + + context "when scrum_projects feature flag is inactive", with_flag: { scrum_projects: false } do + it { is_expected.to be_hidden } + end + end + + describe "share_sprint" do + subject { described_class.permission(:share_sprint) } + + it "depends on create_sprints" do + expect(subject.dependencies).to contain_exactly(:create_sprints) + end + + context "when scrum_projects feature flag is active", with_flag: { scrum_projects: true } do + it { is_expected.to be_visible } + end + + context "when scrum_projects feature flag is inactive", with_flag: { scrum_projects: false } do + it { is_expected.to be_hidden } + end + end +end diff --git a/modules/backlogs/spec/models/agile/sprint_spec.rb b/modules/backlogs/spec/models/agile/sprint_spec.rb index ed1fc3b5628..8aef9d36833 100644 --- a/modules/backlogs/spec/models/agile/sprint_spec.rb +++ b/modules/backlogs/spec/models/agile/sprint_spec.rb @@ -54,6 +54,21 @@ RSpec.describe Agile::Sprint do expect(sprint.errors[:finish_date]).to include(/must be greater than or equal to/) end + it "does not validate finish_date comparison when start_date is nil" do + sprint.start_date = nil + sprint.finish_date = Time.zone.today + expect(sprint).not_to be_valid + expect(sprint.errors[:start_date]).to be_present + expect(sprint.errors[:finish_date]).not_to include(/must be greater than or equal to/) + end + + it "still validates finish_date presence even when start_date is nil" do + sprint.start_date = nil + sprint.finish_date = nil + expect(sprint).not_to be_valid + expect(sprint.errors[:finish_date]).to be_present + end + context "with active sprint validation" do it "allows one active sprint per project" do sprint.status = "active" diff --git a/modules/backlogs/spec/services/impediments/create_services_spec.rb b/modules/backlogs/spec/services/impediments/create_services_spec.rb index 21012fa323a..da7317501a4 100644 --- a/modules/backlogs/spec/services/impediments/create_services_spec.rb +++ b/modules/backlogs/spec/services/impediments/create_services_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Impediments::CreateService do let(:impediment_subject) { "Impediment A" } let(:user) { create(:user) } - let(:role) { create(:project_role, permissions: %i(add_work_packages assign_versions work_package_assigned)) } + let(:role) { create(:project_role, permissions: %i(add_work_packages manage_sprint_items work_package_assigned)) } let(:type_feature) { create(:type_feature) } let(:type_task) { create(:type_task) } let(:priority) { create(:priority, is_default: true) } diff --git a/modules/backlogs/spec/features/resolved_status_spec.rb b/modules/backlogs/spec/services/sprints/create_service_spec.rb similarity index 59% rename from modules/backlogs/spec/features/resolved_status_spec.rb rename to modules/backlogs/spec/services/sprints/create_service_spec.rb index 3e094570a96..0971ecf565d 100644 --- a/modules/backlogs/spec/features/resolved_status_spec.rb +++ b/modules/backlogs/spec/services/sprints/create_service_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -27,37 +29,11 @@ #++ require "spec_helper" -require_relative "../support/pages/projects/settings/backlogs" +require "services/base_services/behaves_like_create_service" -RSpec.describe "Resolved status" do - let!(:project) do - create(:project, - enabled_module_names: %w(backlogs)) - end - let!(:status) { create(:status, is_default: true) } - let(:role) do - create(:project_role, - permissions: %i[select_done_statuses]) - end - let!(:current_user) do - create(:user, - member_with_roles: { project => role }) - end - let(:settings_page) { Pages::Projects::Settings::Backlogs.new(project) } - - before do - login_as current_user - end - - it "allows setting a status as done although it is not closed" do - settings_page.visit! - - check status.name - click_button "Save" - - expect_flash(type: :success, message: "Successful update") - - expect(page) - .to have_checked_field(status.name) +RSpec.describe Sprints::CreateService, type: :model do + it_behaves_like "BaseServices create service" do + let(:model_class) { Agile::Sprint } + let(:factory) { :agile_sprint } end end diff --git a/modules/backlogs/spec/services/sprints/set_attributes_service_spec.rb b/modules/backlogs/spec/services/sprints/set_attributes_service_spec.rb new file mode 100644 index 00000000000..f08152ad0e8 --- /dev/null +++ b/modules/backlogs/spec/services/sprints/set_attributes_service_spec.rb @@ -0,0 +1,297 @@ +# 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. +#++ + +require "spec_helper" + +RSpec.describe Sprints::SetAttributesService, type: :model do + let(:user) { build_stubbed(:user) } + let(:contract_class) do + contract = class_double(Sprints::CreateContract) + + allow(contract) + .to receive(:new) + .with(sprint, user, options: {}) + .and_return(contract_instance) + + contract + end + let(:contract_instance) do + instance_double(ModelContract, validate: contract_valid, errors: contract_errors) + end + let(:contract_valid) { true } + let(:contract_errors) do + instance_double(ActiveModel::Errors) + end + let(:sprint_valid) { true } + let(:instance) do + described_class.new(user:, + model: sprint, + contract_class:, + contract_options: {}) + end + let(:project) { create(:project) } + let(:sprint) { Agile::Sprint.new } + let(:params) { { project: } } + + subject(:service_call) { instance.call(params) } + + describe "call" do + before do + allow(sprint) + .to receive(:valid?) + .and_return(sprint_valid) + + allow(sprint).to receive(:save) + end + + context "when contract validates and sprint is valid" do + it "is successful" do + expect(service_call).to be_success + end + + it "sets the attributes on the sprint" do + service_call + + # Default attributes set include: + # * the project, since passed as argument + # * a generated sprint sequence name if applicable, see `sprint_name_from_predecessor` specs. + expect(sprint.changed_attributes).to include("project_id", "name") + + expect(sprint.project_id).to eq(project.id) + expect(sprint.name).to eq("Sprint 1") + end + + it "does not persist the sprint" do + expect(sprint).not_to have_received(:save) + + service_call + end + end + + context "when contract does not validate" do + let(:contract_valid) { false } + + it "is not successful" do + expect(service_call).not_to be_success + end + end + + context "with params" do + let(:params) do + { + name: "New Sprint Name", + start_date: Time.zone.today, + finish_date: Time.zone.today + 21.days, + status: "active" + } + end + + before do + allow(contract_instance) + .to receive(:validate) + .and_return(true) + end + + it "passes the params to the sprint" do + service_call + + expect(sprint.name).to eq("New Sprint Name") + expect(sprint.start_date).to eq(Time.zone.today) + expect(sprint.finish_date).to eq(Time.zone.today + 21.days) + expect(sprint.status).to eq("active") + end + end + + describe "default attributes" do + let(:sprint) { Agile::Sprint.new } + + it "sets default status to in_planning" do + service_call + + expect(sprint.status).to eq("in_planning") + end + + it "sets default sharing to none" do + service_call + + expect(sprint.sharing).to eq("none") + end + + context "when status is already set" do + let(:sprint) { Agile::Sprint.new(status: "active") } + + it "does not override the existing status" do + service_call + + expect(sprint.status).to eq("active") + end + end + + context "when sharing is already set" do + let(:sprint) { Agile::Sprint.new(sharing: "descendants") } + + it "does not override the existing sharing" do + service_call + + expect(sprint.sharing).to eq("descendants") + end + end + end + end + + describe "assigning a name" do + context "when sprint is not a new record" do + let(:sprint) { create(:agile_sprint, project:, name: "Existing Sprint") } + + it "assigns the current name" do + service_call + + expect(sprint.name).to eq("Existing Sprint") + end + end + + context "when sprint is a new record" do + let(:sprint) { Agile::Sprint.new(project:) } + + context "when there is no predecessor sprint" do + it "assigns a default name for the first sprint" do + service_call + + expected_name = "#{I18n.t('activerecord.models.sprint')} 1" + expect(sprint.name).to eq(expected_name) + end + end + + context "when there is a predecessor sprint with a name ending in a number" do + it "increments the number for single-digit numbers" do + create(:agile_sprint, project:, name: "Sprint 1") + + service_call + expect(sprint.name).to eq("Sprint 2") + end + + it "increments the number for multi-digit numbers" do + create(:agile_sprint, project:, name: "Sprint 42") + + service_call + expect(sprint.name).to eq("Sprint 43") + end + + it "increments the number for custom names ending in numbers" do + create(:agile_sprint, project:, name: "Be ambitious 42") + + service_call + expect(sprint.name).to eq("Be ambitious 43") + end + + it "handles names with multiple spaces before the number" do + create(:agile_sprint, project:, name: "Release 99") + + service_call + expect(sprint.name).to eq("Release 100") + end + + it "increments from 9 to 10" do + create(:agile_sprint, project:, name: "Sprint 9") + + service_call + expect(sprint.name).to eq("Sprint 10") + end + + it "increments from 99 to 100" do + create(:agile_sprint, project:, name: "Sprint 99") + + service_call + expect(sprint.name).to eq("Sprint 100") + end + end + + context "when there is a predecessor sprint with a custom name not ending in a number" do + it "assigns an empty string" do + create(:agile_sprint, project:, name: "Custom Sprint Name") + + service_call + expect(sprint.name).to eq("") + end + + it "assigns an empty string for names with numbers in the middle" do + create(:agile_sprint, project:, name: "Sprint 2023 Planning") + + service_call + expect(sprint.name).to eq("") + end + + it "assigns an empty string for names ending with non-numeric characters" do + create(:agile_sprint, project:, name: "Sprint Alpha") + + service_call + expect(sprint.name).to eq("") + end + end + + context "when there are multiple predecessor sprints" do + it "uses the most recent sprint" do + create(:agile_sprint, project:, name: "Sprint 1", created_at: 2.days.ago) + create(:agile_sprint, project:, name: "Sprint 2", created_at: 1.day.ago) + + service_call + expect(sprint.name).to eq("Sprint 3") + end + + it "handles mixed naming patterns by using the most recent" do + create(:agile_sprint, project:, name: "Sprint 1", created_at: 2.days.ago) + create(:agile_sprint, project:, name: "Custom Name", created_at: 1.day.ago) + + service_call + expect(sprint.name).to eq("") + end + end + + context "when there are sprints in other projects" do + let(:other_project) { create(:project) } + + it "ignores sprints from other projects" do + create(:agile_sprint, project: other_project, name: "Other Sprint 5") + + service_call + expect(sprint.name).to eq("#{I18n.t('activerecord.models.sprint')} 1") + end + + it "only considers sprints from the same project" do + create(:agile_sprint, project: other_project, name: "Other Sprint 5") + create(:agile_sprint, project:, name: "Sprint 3") + + service_call + expect(sprint.name).to eq("Sprint 4") + end + end + end + end +end diff --git a/modules/backlogs/spec/services/stories/create_service_spec.rb b/modules/backlogs/spec/services/stories/create_service_spec.rb index 0ee06ac9d8f..87483c4a055 100644 --- a/modules/backlogs/spec/services/stories/create_service_spec.rb +++ b/modules/backlogs/spec/services/stories/create_service_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Stories::CreateService, type: :model do let(:type_feature) { create(:type_feature) } let(:user) do - create(:user, member_with_permissions: { project => %i(add_work_packages manage_subtasks assign_versions) }) + create(:user, member_with_permissions: { project => %i(add_work_packages manage_subtasks manage_sprint_items) }) end let(:instance) do diff --git a/modules/backlogs/spec/support/pages/backlogs.rb b/modules/backlogs/spec/support/pages/backlogs.rb index d3c88f85406..2006e49fb4b 100644 --- a/modules/backlogs/spec/support/pages/backlogs.rb +++ b/modules/backlogs/spec/support/pages/backlogs.rb @@ -100,6 +100,12 @@ module Pages end end + def click_in_sprint_menu(sprint, item_name) + within_sprint_menu(sprint) do |menu| + menu.find(:menuitem, text: item_name).click + end + end + def click_in_story_menu(story, item_name) within_story_menu(story) do |menu| menu.find(:menuitem, text: item_name).click @@ -119,6 +125,14 @@ module Pages end end + def sprint_names_in_order + page.find_all("#sprint_backlogs_container > section .CollapsibleHeader-title").map(&:text) + end + + def expect_sprint_names_in_order(*sprint_names) + expect(sprint_names_in_order).to eq(sprint_names) + end + def expect_sprint(sprint) expect(page) .to have_css("#sprint_backlogs_container #{backlog_selector(sprint)}") @@ -130,14 +144,28 @@ module Pages end def expect_story_in_sprint(story, sprint) - within_backlog(sprint) do + within_sprint(sprint) do + expect(page) + .to have_selector(work_package_selector(story).to_s) + end + end + + def expect_story_in_backlog(story, backlog) + within_backlog(backlog) do expect(page) .to have_selector(story_selector(story).to_s) end end def expect_story_not_in_sprint(story, sprint) - within_backlog(sprint) do + within_sprint(sprint) do + expect(page) + .to have_no_selector(work_package_selector(story).to_s) + end + end + + def expect_story_not_in_backlog(story, backlog) + within_backlog(backlog) do expect(page) .to have_no_selector(story_selector(story).to_s) end @@ -205,6 +233,30 @@ module Pages details_view end + def open_create_sprint_dialog + find(:button, accessible_name: "Create").click + + within(:menu) do |menu| + menu.find(:menuitem, "Sprint").click + end + end + + def expect_sprint_dialog + expect(page).to have_css("#new-sprint-dialog") + end + + def expect_create_work_package_dialog + expect(page).to have_css("#create-work-package-dialog") + end + + def within_sprint_menu(backlog, &) + within_sprint(backlog) do + find(:button, accessible_name: "Sprint actions").click + + within(:menu, &) + end + end + private def within_story(story, &) @@ -215,6 +267,14 @@ module Pages within(backlog_selector(backlog), &) end + def within_sprint(sprint, &) + within(sprint_selector(sprint), &) + end + + def sprint_selector(sprint) + "#agile_sprint_#{sprint.id}" + end + def backlog_selector(backlog) "#backlog_#{backlog.id}" end @@ -223,6 +283,10 @@ module Pages "#story_#{story.id}" end + def work_package_selector(story) + "#work_package_#{story.id}" + end + def within_menu_controlled_by(button) menu_id = button[:controls] || button["aria-controls"] diff --git a/modules/backlogs/spec/views/rb_master_backlogs/index.html.erb_spec.rb b/modules/backlogs/spec/views/rb_master_backlogs/index.html.erb_spec.rb index 251ffd4eb36..ae8002731e9 100644 --- a/modules/backlogs/spec/views/rb_master_backlogs/index.html.erb_spec.rb +++ b/modules/backlogs/spec/views/rb_master_backlogs/index.html.erb_spec.rb @@ -32,7 +32,7 @@ RSpec.describe "rb_master_backlogs/index" do let(:user) { create(:user) } let(:role_allowed) do create(:project_role, - permissions: %i[view_master_backlog view_taskboards]) + permissions: %i[view_sprints]) end let(:statuses) do [create(:status, is_default: true), diff --git a/modules/bim/config/locales/crowdin/fr.yml b/modules/bim/config/locales/crowdin/fr.yml index 717608ff597..288904f98e5 100644 --- a/modules/bim/config/locales/crowdin/fr.yml +++ b/modules/bim/config/locales/crowdin/fr.yml @@ -59,7 +59,7 @@ fr: perform_description: "Voulez-vous importer ou mettre à jour les problèmes repris ci-dessus ?" replace_with_system_user: 'Les remplacer par l''utilisateur "Système"' import_as_system_user: 'Les importer comme utilisateur "Système".' - what_to_do: "Que voulez-vous faire ?" + what_to_do: "Que voulez-vous faire?" work_package_has_newer_changes: "Obsolète ! Ce sujet n'a pas été mis à jour, car les derniers changements sur le serveur étaient plus récents que la \"ModifiedDate\" du sujet importé. Toutefois, les commentaires sur le sujet ont été importés." bcf_file_not_found: "Impossible de localiser le fichier BCF. Veuillez recommencer le processus de téléversement." export: diff --git a/modules/budgets/config/locales/crowdin/cs.yml b/modules/budgets/config/locales/crowdin/cs.yml index 04414639a22..765bb62a1c2 100644 --- a/modules/budgets/config/locales/crowdin/cs.yml +++ b/modules/budgets/config/locales/crowdin/cs.yml @@ -27,7 +27,7 @@ cs: budget: author: "Autor" available: "Dostupné" - budget: "Plánované" + budget: "Rozpočet" budget_ratio: "Stráveno (poměr)" description: "Popis" spent: "Strávený čas" diff --git a/modules/costs/app/views/admin/costs_settings/show.html.erb b/modules/costs/app/views/admin/costs_settings/show.html.erb index dab2f65b85f..bce17b94489 100644 --- a/modules/costs/app/views/admin/costs_settings/show.html.erb +++ b/modules/costs/app/views/admin/costs_settings/show.html.erb @@ -42,8 +42,19 @@ See COPYRIGHT and LICENSE files for more details. subhead.with_description { I18n.t(:description_costs_settings) } end end - form.text_field(name: :costs_currency, input_width: :small) - form.text_field(name: :costs_currency_format, input_width: :small) + form.text_field( + name: :costs_currency, + input_width: :xsmall, + caption: I18n.t(:setting_costs_currency_caption) + ) + form.select_list( + name: :costs_currency_format, + values: [ + [I18n.t(:setting_costs_currency_format_prefix), "%u %n"], + [I18n.t(:setting_costs_currency_format_suffix), "%n %u"] + ], + input_width: :small + ) form.submit end diff --git a/modules/costs/config/locales/crowdin/af.yml b/modules/costs/config/locales/crowdin/af.yml index 47f433ce4a2..751d3217cb5 100644 --- a/modules/costs/config/locales/crowdin/af.yml +++ b/modules/costs/config/locales/crowdin/af.yml @@ -205,7 +205,10 @@ af: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/ar.yml b/modules/costs/config/locales/crowdin/ar.yml index 4ed12ceec3f..da16cf9e48b 100644 --- a/modules/costs/config/locales/crowdin/ar.yml +++ b/modules/costs/config/locales/crowdin/ar.yml @@ -221,7 +221,10 @@ ar: project_module_costs: "الوقت والتكاليف" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "العملة" - setting_costs_currency_format: "شكل العملة" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/az.yml b/modules/costs/config/locales/crowdin/az.yml index 8dd172a1778..65ed5f3dc8d 100644 --- a/modules/costs/config/locales/crowdin/az.yml +++ b/modules/costs/config/locales/crowdin/az.yml @@ -205,7 +205,10 @@ az: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/be.yml b/modules/costs/config/locales/crowdin/be.yml index 1d3ed4a73f5..81fb4101aa6 100644 --- a/modules/costs/config/locales/crowdin/be.yml +++ b/modules/costs/config/locales/crowdin/be.yml @@ -213,7 +213,10 @@ be: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/bg.yml b/modules/costs/config/locales/crowdin/bg.yml index 04d8a0d1140..6333adb7007 100644 --- a/modules/costs/config/locales/crowdin/bg.yml +++ b/modules/costs/config/locales/crowdin/bg.yml @@ -205,7 +205,10 @@ bg: project_module_costs: "Време и разходи" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Валута" - setting_costs_currency_format: "Формат на валутата" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/ca.yml b/modules/costs/config/locales/crowdin/ca.yml index 4cc7f217213..d821577627d 100644 --- a/modules/costs/config/locales/crowdin/ca.yml +++ b/modules/costs/config/locales/crowdin/ca.yml @@ -205,7 +205,10 @@ ca: project_module_costs: "Temps i costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Divisa" - setting_costs_currency_format: "Format de divisa" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/ckb-IR.yml b/modules/costs/config/locales/crowdin/ckb-IR.yml index eec8fb8766c..7edece35005 100644 --- a/modules/costs/config/locales/crowdin/ckb-IR.yml +++ b/modules/costs/config/locales/crowdin/ckb-IR.yml @@ -205,7 +205,10 @@ ckb-IR: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/cs.yml b/modules/costs/config/locales/crowdin/cs.yml index 44770e3f145..85d249a63ce 100644 --- a/modules/costs/config/locales/crowdin/cs.yml +++ b/modules/costs/config/locales/crowdin/cs.yml @@ -213,7 +213,10 @@ cs: project_module_costs: "Čas a náklady" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Měna" - setting_costs_currency_format: "Formát měny" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/da.yml b/modules/costs/config/locales/crowdin/da.yml index f7259e00f2c..7b576f85061 100644 --- a/modules/costs/config/locales/crowdin/da.yml +++ b/modules/costs/config/locales/crowdin/da.yml @@ -205,7 +205,10 @@ da: project_module_costs: "Tid og omkostninger" setting_allow_tracking_start_and_end_times: "Tillad start- og sluttider" setting_costs_currency: "Valuta" - setting_costs_currency_format: "Valutaformat" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Kræv start- og sluttider" setting_enforce_without_allow: "Det er ikke muligt at kræve start- og sluttider uden at tillade dem" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/de.yml b/modules/costs/config/locales/crowdin/de.yml index 67123a735df..f4ab9eb9320 100644 --- a/modules/costs/config/locales/crowdin/de.yml +++ b/modules/costs/config/locales/crowdin/de.yml @@ -205,7 +205,10 @@ de: project_module_costs: "Zeit und Kosten" setting_allow_tracking_start_and_end_times: "Start- und Endzeiten erlaubt" setting_costs_currency: "Währung" + setting_costs_currency_caption: "Dies ist die Einheit der Währung. Es kann ein ISO-Code mit drei Buchstaben wie EUR, USD oder JPY sein oder ein Symbol wie €, $ oder ¥." setting_costs_currency_format: "Format der Währung" + setting_costs_currency_format_prefix: "Vor der Zahl (z.B. EUR 100)" + setting_costs_currency_format_suffix: "Nach der Zahl (z.B. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Start- und Endzeiten erforderlich" setting_enforce_without_allow: "Start und Endzeiten können nur erforderlich sein, wenn ihre Angabe erlaubt ist" setting_allow_tracking_start_and_end_times_caption: "Erlaubt es bei der Zeitbuchung die genaue Start- und Endzeiten der Buchung zu erfassen." diff --git a/modules/costs/config/locales/crowdin/el.yml b/modules/costs/config/locales/crowdin/el.yml index 32a780bbc69..efef01b9b87 100644 --- a/modules/costs/config/locales/crowdin/el.yml +++ b/modules/costs/config/locales/crowdin/el.yml @@ -205,7 +205,10 @@ el: project_module_costs: "Χρόνος και κόστος" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Νόμισμα" - setting_costs_currency_format: "Μορφοποίηση του νομίσματος" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/eo.yml b/modules/costs/config/locales/crowdin/eo.yml index aa4d5f4f3db..841aa1a8e71 100644 --- a/modules/costs/config/locales/crowdin/eo.yml +++ b/modules/costs/config/locales/crowdin/eo.yml @@ -205,7 +205,10 @@ eo: project_module_costs: "Tempo kaj kostoj" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Valuto" - setting_costs_currency_format: "Formato de valuto" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/es.yml b/modules/costs/config/locales/crowdin/es.yml index 2760820cf51..26f864fe58c 100644 --- a/modules/costs/config/locales/crowdin/es.yml +++ b/modules/costs/config/locales/crowdin/es.yml @@ -205,7 +205,10 @@ es: project_module_costs: "Tiempo y costos" setting_allow_tracking_start_and_end_times: "Permitir horas de inicio y finalización" setting_costs_currency: "Moneda" - setting_costs_currency_format: "Formato de moneda" + setting_costs_currency_caption: "Se refiere a la unidad monetaria. Puede ser un código ISO de tres letras, como EUR, USD o JPY, o un símbolo, como €, $ o ¥." + setting_costs_currency_format: "Formato de la moneda" + setting_costs_currency_format_prefix: "Antes del importe (por ejemplo, EUR 100)" + setting_costs_currency_format_suffix: "Después del importe (por ejemplo, 100 EUR)" setting_enforce_tracking_start_and_end_times: "Requerir horas de inicio y finalización" setting_enforce_without_allow: "No es posible requerir horas de inicio y finalización sin permitirlas antes" setting_allow_tracking_start_and_end_times_caption: "Habilita la introducción de las horas de inicio y finalización cuando se registra tiempo." diff --git a/modules/costs/config/locales/crowdin/et.yml b/modules/costs/config/locales/crowdin/et.yml index 7048db1c4f0..a16f0a3a120 100644 --- a/modules/costs/config/locales/crowdin/et.yml +++ b/modules/costs/config/locales/crowdin/et.yml @@ -205,7 +205,10 @@ et: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Valuuta" - setting_costs_currency_format: "Valuuta vorming" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/eu.yml b/modules/costs/config/locales/crowdin/eu.yml index 85fa6da2032..b98c75d8e89 100644 --- a/modules/costs/config/locales/crowdin/eu.yml +++ b/modules/costs/config/locales/crowdin/eu.yml @@ -205,7 +205,10 @@ eu: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/fa.yml b/modules/costs/config/locales/crowdin/fa.yml index e34f625945c..887fb6e5159 100644 --- a/modules/costs/config/locales/crowdin/fa.yml +++ b/modules/costs/config/locales/crowdin/fa.yml @@ -205,7 +205,10 @@ fa: project_module_costs: "زمان و هزینه‌ها" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "واحد پول" - setting_costs_currency_format: "فرمت واحد پول" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "درخواست زمان های شروع و پایان" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/fi.yml b/modules/costs/config/locales/crowdin/fi.yml index 015e9ba4d91..2a157dd17f8 100644 --- a/modules/costs/config/locales/crowdin/fi.yml +++ b/modules/costs/config/locales/crowdin/fi.yml @@ -205,7 +205,10 @@ fi: project_module_costs: "Työaika ja kustannukset" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Valuutta" - setting_costs_currency_format: "Valuuttamuotoilu" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/fil.yml b/modules/costs/config/locales/crowdin/fil.yml index db83689193b..85d811e3543 100644 --- a/modules/costs/config/locales/crowdin/fil.yml +++ b/modules/costs/config/locales/crowdin/fil.yml @@ -205,7 +205,10 @@ fil: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/fr.yml b/modules/costs/config/locales/crowdin/fr.yml index 0164a551c16..74712cab846 100644 --- a/modules/costs/config/locales/crowdin/fr.yml +++ b/modules/costs/config/locales/crowdin/fr.yml @@ -205,7 +205,10 @@ fr: project_module_costs: "Temps et coûts" setting_allow_tracking_start_and_end_times: "Autoriser les heures de début et de fin" setting_costs_currency: "Devise" + setting_costs_currency_caption: "Il s'agit de l'unité monétaire. Il peut s'agir d'un code ISO à trois lettres comme EUR, USD ou JPY ou d'un symbole comme €, $ ou ¥." setting_costs_currency_format: "Format de devise" + setting_costs_currency_format_prefix: "Avant le nombre (par exemple 100 EUR)" + setting_costs_currency_format_suffix: "Après le nombre (par exemple 100 EUR)" setting_enforce_tracking_start_and_end_times: "Exiger les heures de début et de fin" setting_enforce_without_allow: "Il est nécessaire d'autoriser les heures de début et de fin pour pouvoir les exiger" setting_allow_tracking_start_and_end_times_caption: "Active la saisie des heures de début et de fin lors de la journalisation." diff --git a/modules/costs/config/locales/crowdin/he.yml b/modules/costs/config/locales/crowdin/he.yml index 53257a7a46e..b4369f9a43c 100644 --- a/modules/costs/config/locales/crowdin/he.yml +++ b/modules/costs/config/locales/crowdin/he.yml @@ -213,7 +213,10 @@ he: project_module_costs: "עלויות" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/hi.yml b/modules/costs/config/locales/crowdin/hi.yml index 53b1f3f0ecf..19a1e050859 100644 --- a/modules/costs/config/locales/crowdin/hi.yml +++ b/modules/costs/config/locales/crowdin/hi.yml @@ -205,7 +205,10 @@ hi: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/hr.yml b/modules/costs/config/locales/crowdin/hr.yml index af8cb7a88c7..06377b288c7 100644 --- a/modules/costs/config/locales/crowdin/hr.yml +++ b/modules/costs/config/locales/crowdin/hr.yml @@ -209,7 +209,10 @@ hr: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Valuta" - setting_costs_currency_format: "Oblik valute" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/hu.yml b/modules/costs/config/locales/crowdin/hu.yml index b6c633e2a7e..ab70582879a 100644 --- a/modules/costs/config/locales/crowdin/hu.yml +++ b/modules/costs/config/locales/crowdin/hu.yml @@ -205,7 +205,10 @@ hu: project_module_costs: "Idő és költség" setting_allow_tracking_start_and_end_times: "Engedélyezze a kezdési és befejezési időpontokat" setting_costs_currency: "Pénznem" - setting_costs_currency_format: "Pénznem formátuma" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Kezdési és befejezési idő megadása kötelező" setting_enforce_without_allow: "A kezdési és befejezési idő megkövetelése nem lehetséges anélkül, hogy engedélyeznénk azok megadását" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/id.yml b/modules/costs/config/locales/crowdin/id.yml index 05efe611b10..fa8b2ca0329 100644 --- a/modules/costs/config/locales/crowdin/id.yml +++ b/modules/costs/config/locales/crowdin/id.yml @@ -201,7 +201,10 @@ id: project_module_costs: "Waktu dan biaya" setting_allow_tracking_start_and_end_times: "Izinkan waktu mulai dan waktu selesai" setting_costs_currency: "Mata Uang" - setting_costs_currency_format: "Format mata uang" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Butuh waktu mulai dan selesai" setting_enforce_without_allow: "Membutuhkan waktu mulai dan selesai tidak mungkin dilakukan tanpa mengizinkannya" setting_allow_tracking_start_and_end_times_caption: "Memungkinkan untuk memasukkan waktu mulai dan selesai saat mencatat waktu." diff --git a/modules/costs/config/locales/crowdin/it.yml b/modules/costs/config/locales/crowdin/it.yml index f7c0cf849d7..bd91938ee28 100644 --- a/modules/costs/config/locales/crowdin/it.yml +++ b/modules/costs/config/locales/crowdin/it.yml @@ -205,7 +205,10 @@ it: project_module_costs: "Tempi e costi" setting_allow_tracking_start_and_end_times: "Consenti orari di inizio e fine" setting_costs_currency: "Valuta" - setting_costs_currency_format: "Formato della valuta" + setting_costs_currency_caption: "Questa è l'unità di valuta. Può essere un codice ISO a tre lettere come EUR, USD o JPY o un simbolo come €, $ o 한." + setting_costs_currency_format: "Formato valuta" + setting_costs_currency_format_prefix: "Prima del numero (ad esempio, EUR 100)" + setting_costs_currency_format_suffix: "Dopo il numero (ad esempio, 100 EUR)" setting_enforce_tracking_start_and_end_times: "Richiede orari di inizio e di fine" setting_enforce_without_allow: "Non è possibile richiedere orari di inizio e fine senza consentirli" setting_allow_tracking_start_and_end_times_caption: "Consente di inserire gli orari di inizio e di fine durante la registrazione dell'orario." diff --git a/modules/costs/config/locales/crowdin/ja.yml b/modules/costs/config/locales/crowdin/ja.yml index a5c5f462667..20532a2611f 100644 --- a/modules/costs/config/locales/crowdin/ja.yml +++ b/modules/costs/config/locales/crowdin/ja.yml @@ -201,11 +201,14 @@ ja: project_module_costs: "時間とコスト" setting_allow_tracking_start_and_end_times: "開始時間と終了時間を許可する" setting_costs_currency: "通貨" - setting_costs_currency_format: "通貨の形式" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "開始/終了時間を必須とする" setting_enforce_without_allow: "開始時間と終了時間を要求することは許可されていないとできません" setting_allow_tracking_start_and_end_times_caption: "時間を記録する際に、開始時間と終了時間を入力できるようにする。" - setting_enforce_tracking_start_and_end_times_caption: "時間を記録する際、開始時間と終了時間の入力を必須にします。" + setting_enforce_tracking_start_and_end_times_caption: "時間を記録する際、開始時間と終了時間の入力が必須となる。" text_assign_time_and_cost_entries_to_project: "報告された時間とコストをプロジェクトに割り当てる" text_destroy_cost_entries_question: "削除しようとしているワークパッケージが%{cost_entries} 件報告されました。どうしますか?" text_destroy_time_and_cost_entries: "報告された時間とコストを削除する" diff --git a/modules/costs/config/locales/crowdin/ka.yml b/modules/costs/config/locales/crowdin/ka.yml index 3db3c1d8f2a..82f1853414e 100644 --- a/modules/costs/config/locales/crowdin/ka.yml +++ b/modules/costs/config/locales/crowdin/ka.yml @@ -205,7 +205,10 @@ ka: project_module_costs: "დრო და ღირებულებები" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "ფული" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/kk.yml b/modules/costs/config/locales/crowdin/kk.yml index 57d9130f893..ad2aa157d7b 100644 --- a/modules/costs/config/locales/crowdin/kk.yml +++ b/modules/costs/config/locales/crowdin/kk.yml @@ -205,7 +205,10 @@ kk: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/ko.yml b/modules/costs/config/locales/crowdin/ko.yml index 9d67d328248..3d1a75f7ab3 100644 --- a/modules/costs/config/locales/crowdin/ko.yml +++ b/modules/costs/config/locales/crowdin/ko.yml @@ -201,7 +201,10 @@ ko: project_module_costs: "시간 및 비용" setting_allow_tracking_start_and_end_times: "시작 및 완료 시간 허용" setting_costs_currency: "통화" + setting_costs_currency_caption: "통화 단위입니다. EUR, USD, JPY 같은 문자 세 개의 ISO 코드 또는 €, $, ¥ 같은 기호일 수 있습니다." setting_costs_currency_format: "통화 형식" + setting_costs_currency_format_prefix: "숫자 앞(예: EUR 100)" + setting_costs_currency_format_suffix: "숫자 뒤(예: 100 EUR)" setting_enforce_tracking_start_and_end_times: "시작 및 완료 시간 필요" setting_enforce_without_allow: "이 옵션을 허용하지 않으면 시작 및 완료 시간 요구가 가능하지 않습니다" setting_allow_tracking_start_and_end_times_caption: "시간을 기록할 때 시작 및 완료 시간 입력을 활성화합니다." diff --git a/modules/costs/config/locales/crowdin/lt.yml b/modules/costs/config/locales/crowdin/lt.yml index 31165c017ae..e8fac6161b9 100644 --- a/modules/costs/config/locales/crowdin/lt.yml +++ b/modules/costs/config/locales/crowdin/lt.yml @@ -213,7 +213,10 @@ lt: project_module_costs: "Laikas ir išlaidos" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Valiuta" - setting_costs_currency_format: "Valiutos formatas" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/lv.yml b/modules/costs/config/locales/crowdin/lv.yml index 09984b6795f..c9acdae640b 100644 --- a/modules/costs/config/locales/crowdin/lv.yml +++ b/modules/costs/config/locales/crowdin/lv.yml @@ -209,7 +209,10 @@ lv: project_module_costs: "Laiks un izmaksas" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Valūta" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/mn.yml b/modules/costs/config/locales/crowdin/mn.yml index c3962b2b876..f5ba49c5f68 100644 --- a/modules/costs/config/locales/crowdin/mn.yml +++ b/modules/costs/config/locales/crowdin/mn.yml @@ -205,7 +205,10 @@ mn: project_module_costs: "Цаг хугацаа ба зардал" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/ms.yml b/modules/costs/config/locales/crowdin/ms.yml index 328e1d78d0c..17eb61b6ee3 100644 --- a/modules/costs/config/locales/crowdin/ms.yml +++ b/modules/costs/config/locales/crowdin/ms.yml @@ -201,7 +201,10 @@ ms: project_module_costs: "Masa dan kos" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Mata Wang" - setting_costs_currency_format: "Format mata wang" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/ne.yml b/modules/costs/config/locales/crowdin/ne.yml index 29448b1bb88..3ec82ad2b7f 100644 --- a/modules/costs/config/locales/crowdin/ne.yml +++ b/modules/costs/config/locales/crowdin/ne.yml @@ -205,7 +205,10 @@ ne: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/nl.yml b/modules/costs/config/locales/crowdin/nl.yml index b8b072b92b7..657b6b1045c 100644 --- a/modules/costs/config/locales/crowdin/nl.yml +++ b/modules/costs/config/locales/crowdin/nl.yml @@ -205,7 +205,10 @@ nl: project_module_costs: "Tijd en kosten" setting_allow_tracking_start_and_end_times: "Begin- en eindtijden toestaan" setting_costs_currency: "Valuta" - setting_costs_currency_format: "Formaat van valuta" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Begin- en eindtijden vereisen" setting_enforce_without_allow: "Het is niet mogelijk om begin- en eindtijden te vereisen zonder ze toe te staan" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/no.yml b/modules/costs/config/locales/crowdin/no.yml index 9b230900978..04be7016cc3 100644 --- a/modules/costs/config/locales/crowdin/no.yml +++ b/modules/costs/config/locales/crowdin/no.yml @@ -205,7 +205,10 @@ project_module_costs: "Tid og kostnader" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Valuta" - setting_costs_currency_format: "Formatet på valuta" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/pl.yml b/modules/costs/config/locales/crowdin/pl.yml index 21fdaf3ba2b..b5baf20cc3e 100644 --- a/modules/costs/config/locales/crowdin/pl.yml +++ b/modules/costs/config/locales/crowdin/pl.yml @@ -213,7 +213,10 @@ pl: project_module_costs: "Czas i koszty" setting_allow_tracking_start_and_end_times: "Zezwól na czasy rozpoczęcia i zakończenia" setting_costs_currency: "Waluta" + setting_costs_currency_caption: "Jest to jednostka waluty. Może to być trzyliterowy kod ISO, taki jak EUR, USD lub JPY albo symbol, taki jak €, $ lub ¥." setting_costs_currency_format: "Format waluty" + setting_costs_currency_format_prefix: "Przed liczbą (np. EUR 100)" + setting_costs_currency_format_suffix: "Po liczbie (np. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Wymagaj czasów rozpoczęcia i zakończenia" setting_enforce_without_allow: "Nie można wymagać czasów rozpoczęcia i zakończenia bez zezwolenia na nie" setting_allow_tracking_start_and_end_times_caption: "Umożliwia wprowadzenie czasów rozpoczęcia i zakończenia w razie rejestrowania czasu." diff --git a/modules/costs/config/locales/crowdin/pt-BR.yml b/modules/costs/config/locales/crowdin/pt-BR.yml index a80308e2a7b..85049ad6152 100644 --- a/modules/costs/config/locales/crowdin/pt-BR.yml +++ b/modules/costs/config/locales/crowdin/pt-BR.yml @@ -205,7 +205,10 @@ pt-BR: project_module_costs: "Tempo e custos" setting_allow_tracking_start_and_end_times: "Permitir horários de início e de término" setting_costs_currency: "Moeda" - setting_costs_currency_format: "Formato de moeda" + setting_costs_currency_caption: "Esta é a moeda utilizada. Pode ser representada por um código ISO de três letras, como EUR, USD ou JPY, ou por um símbolo, como €, $ ou ¥." + setting_costs_currency_format: "Formato da moeda" + setting_costs_currency_format_prefix: "Antes do valor (por exemplo: EUR 100)" + setting_costs_currency_format_suffix: "Após o valor (por exemplo: 100 EUR)" setting_enforce_tracking_start_and_end_times: "Exigir horários de início e término" setting_enforce_without_allow: "Exigir os horários de início e término não é possível sem habilitá-los" setting_allow_tracking_start_and_end_times_caption: "Ativa a inserção de horários de início e término ao registrar o tempo." diff --git a/modules/costs/config/locales/crowdin/pt-PT.yml b/modules/costs/config/locales/crowdin/pt-PT.yml index f54963fc1f2..80651578281 100644 --- a/modules/costs/config/locales/crowdin/pt-PT.yml +++ b/modules/costs/config/locales/crowdin/pt-PT.yml @@ -205,7 +205,10 @@ pt-PT: project_module_costs: "Tempo e custos" setting_allow_tracking_start_and_end_times: "Permitir horário de início e término" setting_costs_currency: "Moeda" - setting_costs_currency_format: "Formato de moeda" + setting_costs_currency_caption: "Esta é a unidade monetária. Pode ser um código ISO de três letras como EUR, USD ou JPY ou um símbolo como €, $ ou ¥." + setting_costs_currency_format: "Formato da moeda" + setting_costs_currency_format_prefix: "Antes do número (por exemplo, 100 euros)" + setting_costs_currency_format_suffix: "Após o número (por exemplo, 100 EUR)" setting_enforce_tracking_start_and_end_times: "Exigir hora de início e término" setting_enforce_without_allow: "Não é possível exigir horas de início e de fim sem as autorizar" setting_allow_tracking_start_and_end_times_caption: "Permite introduzir as horas de início e de término ao registar o tempo." diff --git a/modules/costs/config/locales/crowdin/ro.yml b/modules/costs/config/locales/crowdin/ro.yml index 3cce081e02b..e1e0ace856c 100644 --- a/modules/costs/config/locales/crowdin/ro.yml +++ b/modules/costs/config/locales/crowdin/ro.yml @@ -209,7 +209,10 @@ ro: project_module_costs: "Timp și costuri" setting_allow_tracking_start_and_end_times: "Permite orele de început și de sfârșit" setting_costs_currency: "Monedă" - setting_costs_currency_format: "Format monedă" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Cere orele de început și de sfârșit" setting_enforce_without_allow: "Solicitarea orelor de început și de sfârșit nu este posibilă fără a le permite" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/ru.yml b/modules/costs/config/locales/crowdin/ru.yml index 244a4596166..69f9ca319be 100644 --- a/modules/costs/config/locales/crowdin/ru.yml +++ b/modules/costs/config/locales/crowdin/ru.yml @@ -213,7 +213,10 @@ ru: project_module_costs: "Время и затраты" setting_allow_tracking_start_and_end_times: "Разрешить время начала и окончания" setting_costs_currency: "Валюта" + setting_costs_currency_caption: "Это единица валюты. Это может быть трехбуквенный код ISO, например, EUR, USD или JPY, или символ, например, €, $ или ¥." setting_costs_currency_format: "Формат валюты" + setting_costs_currency_format_prefix: "Перед числом (например, EUR 100)" + setting_costs_currency_format_suffix: "После числа (например, 100 EUR)" setting_enforce_tracking_start_and_end_times: "Требуется время начала и окончания" setting_enforce_without_allow: "Требование точного времени невозможно без соответствующего разрешения" setting_allow_tracking_start_and_end_times_caption: "Включает время начала и окончания учета." diff --git a/modules/costs/config/locales/crowdin/rw.yml b/modules/costs/config/locales/crowdin/rw.yml index 5e4e44e7a8e..20e2547d233 100644 --- a/modules/costs/config/locales/crowdin/rw.yml +++ b/modules/costs/config/locales/crowdin/rw.yml @@ -205,7 +205,10 @@ rw: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/si.yml b/modules/costs/config/locales/crowdin/si.yml index 2308be376ca..208b3042c6e 100644 --- a/modules/costs/config/locales/crowdin/si.yml +++ b/modules/costs/config/locales/crowdin/si.yml @@ -205,7 +205,10 @@ si: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/sk.yml b/modules/costs/config/locales/crowdin/sk.yml index 18439ac2f68..6c8bc919c75 100644 --- a/modules/costs/config/locales/crowdin/sk.yml +++ b/modules/costs/config/locales/crowdin/sk.yml @@ -213,7 +213,10 @@ sk: project_module_costs: "Čas a náklady" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Mena" - setting_costs_currency_format: "Formát meny" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/sl.yml b/modules/costs/config/locales/crowdin/sl.yml index 40c9a106483..304ccd44bb6 100644 --- a/modules/costs/config/locales/crowdin/sl.yml +++ b/modules/costs/config/locales/crowdin/sl.yml @@ -213,7 +213,10 @@ sl: project_module_costs: "Čas in stroški" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Valuta" - setting_costs_currency_format: "Oblika valute" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/sr.yml b/modules/costs/config/locales/crowdin/sr.yml index 5b4d2d2aca4..6638b4d33a3 100644 --- a/modules/costs/config/locales/crowdin/sr.yml +++ b/modules/costs/config/locales/crowdin/sr.yml @@ -209,7 +209,10 @@ sr: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/sv.yml b/modules/costs/config/locales/crowdin/sv.yml index bf37e762df9..bd40b4b80c9 100644 --- a/modules/costs/config/locales/crowdin/sv.yml +++ b/modules/costs/config/locales/crowdin/sv.yml @@ -205,7 +205,10 @@ sv: project_module_costs: "Tid och kostnader" setting_allow_tracking_start_and_end_times: "Tillåt start- och sluttider" setting_costs_currency: "Valuta" - setting_costs_currency_format: "Formatet för valuta" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Kräv start- och sluttider" setting_enforce_without_allow: "Det är inte möjligt att begära start- och sluttider utan att låta dem" setting_allow_tracking_start_and_end_times_caption: "Gör det möjligt att ange start- och sluttider vid tidsloggning." diff --git a/modules/costs/config/locales/crowdin/th.yml b/modules/costs/config/locales/crowdin/th.yml index 772a41d8e0c..9f04b5cf116 100644 --- a/modules/costs/config/locales/crowdin/th.yml +++ b/modules/costs/config/locales/crowdin/th.yml @@ -201,7 +201,10 @@ th: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/tr.yml b/modules/costs/config/locales/crowdin/tr.yml index c959bb4b05b..2b758d6a338 100644 --- a/modules/costs/config/locales/crowdin/tr.yml +++ b/modules/costs/config/locales/crowdin/tr.yml @@ -205,7 +205,10 @@ tr: project_module_costs: "Zaman ve maliyetler" setting_allow_tracking_start_and_end_times: "Başlangıç ve bitiş saatlerine izin verin" setting_costs_currency: "Para birimi" - setting_costs_currency_format: "Para biriminin biçimi" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Başlangıç ve bitiş saatleri gerektir" setting_enforce_without_allow: "Başlangıç ve bitiş saatlerini zorunlu tutmak, bunlara izin vermeden mümkün değildir" setting_allow_tracking_start_and_end_times_caption: "Zaman kaydı yaparken başlangıç ve bitiş zamanlarının girilmesini sağlar." diff --git a/modules/costs/config/locales/crowdin/uk.yml b/modules/costs/config/locales/crowdin/uk.yml index 95cb3cbd8dd..d0ca340d910 100644 --- a/modules/costs/config/locales/crowdin/uk.yml +++ b/modules/costs/config/locales/crowdin/uk.yml @@ -213,7 +213,10 @@ uk: project_module_costs: "Час і витрати" setting_allow_tracking_start_and_end_times: "Дозволити час початку й закінчення" setting_costs_currency: "Валюта" + setting_costs_currency_caption: "Це одиниця валюти. На її позначення може використовуватися трилітерний код ISO (наприклад, EUR, USD чи JPY) або символ (€, $, ¥ тощо)." setting_costs_currency_format: "Формат валюти" + setting_costs_currency_format_prefix: "Перед числом (наприклад, EUR 100)" + setting_costs_currency_format_suffix: "Після числа (наприклад, 100 EUR)" setting_enforce_tracking_start_and_end_times: "Вимагати час початку й закінчення" setting_enforce_without_allow: "Вимога часу початку й закінчення неможлива без відповідного налаштування" setting_allow_tracking_start_and_end_times_caption: "Дає змогу вводити час початку й закінчення в журнал обліку часу." diff --git a/modules/costs/config/locales/crowdin/uz.yml b/modules/costs/config/locales/crowdin/uz.yml index a75e486fe67..b245e161da9 100644 --- a/modules/costs/config/locales/crowdin/uz.yml +++ b/modules/costs/config/locales/crowdin/uz.yml @@ -205,7 +205,10 @@ uz: project_module_costs: "Time and costs" setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/config/locales/crowdin/vi.yml b/modules/costs/config/locales/crowdin/vi.yml index e7c0268d25b..bac89a48180 100644 --- a/modules/costs/config/locales/crowdin/vi.yml +++ b/modules/costs/config/locales/crowdin/vi.yml @@ -201,7 +201,10 @@ vi: project_module_costs: "Thời gian và chi phí" setting_allow_tracking_start_and_end_times: "Cho phép thời gian bắt đầu và kết thúc" setting_costs_currency: "tiền tệ" - setting_costs_currency_format: "Định dạng tiền tệ" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Yêu cầu thời gian bắt đầu và kết thúc" setting_enforce_without_allow: "Không thể yêu cầu thời gian bắt đầu và kết thúc nếu không cho phép chúng" setting_allow_tracking_start_and_end_times_caption: "Cho phép nhập thời gian bắt đầu và kết thúc khi ghi thời gian." diff --git a/modules/costs/config/locales/crowdin/zh-CN.yml b/modules/costs/config/locales/crowdin/zh-CN.yml index 8c5dff2879a..f221761a2e3 100644 --- a/modules/costs/config/locales/crowdin/zh-CN.yml +++ b/modules/costs/config/locales/crowdin/zh-CN.yml @@ -201,7 +201,10 @@ zh-CN: project_module_costs: "工时和成本" setting_allow_tracking_start_and_end_times: "允许开始和完成时间" setting_costs_currency: "货币" + setting_costs_currency_caption: "这是货币单位。可以是 EUR、USD 或 JPY 等三个字母的 ISO 代码,也可以是 €、$ 或 ¥ 等符号。" setting_costs_currency_format: "货币格式" + setting_costs_currency_format_prefix: "在数字前(例如 EUR 100)" + setting_costs_currency_format_suffix: "在数字后(例如 100 EUR)" setting_enforce_tracking_start_and_end_times: "需要开始和完成时间" setting_enforce_without_allow: "如果不允许,则无法要求开始和结束时间" setting_allow_tracking_start_and_end_times_caption: "启用在记录时间时输入开始和结束时间。" diff --git a/modules/costs/config/locales/crowdin/zh-TW.yml b/modules/costs/config/locales/crowdin/zh-TW.yml index 2209da088d6..d55463b7e1a 100644 --- a/modules/costs/config/locales/crowdin/zh-TW.yml +++ b/modules/costs/config/locales/crowdin/zh-TW.yml @@ -201,7 +201,10 @@ zh-TW: project_module_costs: "時間與費用" setting_allow_tracking_start_and_end_times: "允許起迄時間" setting_costs_currency: "貨幣" - setting_costs_currency_format: " 貨幣格式" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "需要起迄時間" setting_enforce_without_allow: "在不允許的情況下,要求開始和結束時間是不可能的" setting_allow_tracking_start_and_end_times_caption: "啟用記錄工時時輸入開始與結束時間的功能。" diff --git a/modules/costs/config/locales/en.yml b/modules/costs/config/locales/en.yml index f6d3a09f635..1ebae1bde1a 100644 --- a/modules/costs/config/locales/en.yml +++ b/modules/costs/config/locales/en.yml @@ -224,7 +224,10 @@ en: setting_allow_tracking_start_and_end_times: "Allow start and finish times" setting_costs_currency: "Currency" - setting_costs_currency_format: "Format of currency" + setting_costs_currency_caption: "This is the unit of currency. It can be a three-letter ISO code like EUR, USD or JPY or a symbol like €, $ or ¥." + setting_costs_currency_format: "Currency format" + setting_costs_currency_format_prefix: "Before the number (e.g. EUR 100)" + setting_costs_currency_format_suffix: "After the number (e.g. 100 EUR)" setting_enforce_tracking_start_and_end_times: "Require start and finish times" setting_enforce_without_allow: "Requiring start and finish times is not possible without allowing them" setting_allow_tracking_start_and_end_times_caption: "Enables entering start and finish times when logging time." diff --git a/modules/costs/db/migrate/20260303172517_suffix_or_prefix_currency_format.rb b/modules/costs/db/migrate/20260303172517_suffix_or_prefix_currency_format.rb new file mode 100644 index 00000000000..ee9ac1560b6 --- /dev/null +++ b/modules/costs/db/migrate/20260303172517_suffix_or_prefix_currency_format.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SuffixOrPrefixCurrencyFormat < ActiveRecord::Migration[8.1] + def up + execute <<~SQL.squish + UPDATE settings + SET value = '%n %u' + WHERE name = 'costs_currency_format' + AND value NOT IN ('%u %n', '%n %u'); + SQL + end +end diff --git a/modules/costs/lib/costs/engine.rb b/modules/costs/lib/costs/engine.rb index 698b425fba4..1fe57a590fd 100644 --- a/modules/costs/lib/costs/engine.rb +++ b/modules/costs/lib/costs/engine.rb @@ -167,7 +167,7 @@ module Costs initializer "costs.settings" do ::Settings::Definition.add "costs_currency", default: "€", format: :string - ::Settings::Definition.add "costs_currency_format", default: "%n %u", format: :string + ::Settings::Definition.add "costs_currency_format", default: "%n %u", format: :string, allowed: ["%u %n", "%n %u"] ::Settings::Definition.add "allow_tracking_start_and_end_times", default: false, format: :boolean ::Settings::Definition.add "enforce_tracking_start_and_end_times", default: false, format: :boolean end diff --git a/modules/costs/spec/features/my_time_tracking_spec.rb b/modules/costs/spec/features/my_time_tracking_spec.rb index b85bbab5108..bc18a135024 100644 --- a/modules/costs/spec/features/my_time_tracking_spec.rb +++ b/modules/costs/spec/features/my_time_tracking_spec.rb @@ -74,7 +74,7 @@ RSpec.describe "my time tracking", :js do end around do |example| - travel_to "2025-04-09T12:00:00Z" do # rubocop:disable RSpecRails/TravelAround + travel_to "2025-04-09T12:00:00Z" do example.run end end diff --git a/modules/github_integration/app/workers/cron/check_deploy_status_job.rb b/modules/github_integration/app/workers/cron/check_deploy_status_job.rb index 6a27e3e5bd6..f8ea9bae7a0 100644 --- a/modules/github_integration/app/workers/cron/check_deploy_status_job.rb +++ b/modules/github_integration/app/workers/cron/check_deploy_status_job.rb @@ -157,7 +157,7 @@ module Cron end def introspection_request(host, api_token) - OpenProject.httpx.basic_auth("apikey", api_token).get("https://#{host}/api/v3") + OpenProject.httpx.plugin(:basic_auth).basic_auth("apikey", api_token).get("https://#{host}/api/v3") end ## @@ -188,7 +188,7 @@ module Cron ahead_by = data["ahead_by"].presence behind_by = data["behind_by"].presence - status == "behind" && ahead_by == 0 && (behind_by.present? && behind_by > 0) + status == "behind" && ahead_by == 0 && behind_by.present? && behind_by > 0 end def compare_commits(sha_a, sha_b) diff --git a/modules/gitlab_integration/config/locales/crowdin/rw.yml b/modules/gitlab_integration/config/locales/crowdin/rw.yml index 4b58a358650..a5ae5154287 100644 --- a/modules/gitlab_integration/config/locales/crowdin/rw.yml +++ b/modules/gitlab_integration/config/locales/crowdin/rw.yml @@ -60,17 +60,23 @@ rw: merge_request_reopened_comment: > **MR Reopened:** Merge request %{mr_number} [%{mr_title}](%{mr_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). note_commit_referenced_comment: > - **Referenced in Commit:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in a Commit Note [%{commit_id}](%{commit_url}) on [%{repository}](%{repository_url}): %{commit_note} + **Referenced in Commit:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in a Commit Note [%{commit_id}](%{commit_url}) on [%{repository}](%{repository_url}): + %{commit_note} note_mr_referenced_comment: > - **Referenced in MR:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): %{mr_note} + **Referenced in MR:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): + %{mr_note} note_mr_commented_comment: > - **Commented in MR:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): %{mr_note} + **Commented in MR:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): + %{mr_note} note_issue_referenced_comment: > - **Referenced in Issue:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): %{issue_note} + **Referenced in Issue:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): + %{issue_note} note_issue_commented_comment: > - **Commented in Issue:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): %{issue_note} + **Commented in Issue:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): + %{issue_note} note_snippet_referenced_comment: > - **Referenced in Snippet:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Snippet %{snippet_number} [%{snippet_title}](%{snippet_url}) on [%{repository}](%{repository_url}): %{snippet_note} + **Referenced in Snippet:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Snippet %{snippet_number} [%{snippet_title}](%{snippet_url}) on [%{repository}](%{repository_url}): + %{snippet_note} issue_opened_referenced_comment: > **Issue Opened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been opened by [%{gitlab_user}](%{gitlab_user_url}). issue_closed_referenced_comment: > @@ -78,8 +84,11 @@ rw: issue_reopened_referenced_comment: > **Issue Reopened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). push_single_commit_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} push_single_commit_comment_with_ref: > - **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} push_multiple_commits_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} diff --git a/modules/gitlab_integration/config/locales/crowdin/uz.yml b/modules/gitlab_integration/config/locales/crowdin/uz.yml index 8159949474f..26da96002b9 100644 --- a/modules/gitlab_integration/config/locales/crowdin/uz.yml +++ b/modules/gitlab_integration/config/locales/crowdin/uz.yml @@ -60,17 +60,23 @@ uz: merge_request_reopened_comment: > **MR Reopened:** Merge request %{mr_number} [%{mr_title}](%{mr_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). note_commit_referenced_comment: > - **Referenced in Commit:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in a Commit Note [%{commit_id}](%{commit_url}) on [%{repository}](%{repository_url}): %{commit_note} + **Referenced in Commit:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in a Commit Note [%{commit_id}](%{commit_url}) on [%{repository}](%{repository_url}): + %{commit_note} note_mr_referenced_comment: > - **Referenced in MR:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): %{mr_note} + **Referenced in MR:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): + %{mr_note} note_mr_commented_comment: > - **Commented in MR:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): %{mr_note} + **Commented in MR:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Merge Request %{mr_number} [%{mr_title}](%{mr_url}) on [%{repository}](%{repository_url}): + %{mr_note} note_issue_referenced_comment: > - **Referenced in Issue:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): %{issue_note} + **Referenced in Issue:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): + %{issue_note} note_issue_commented_comment: > - **Commented in Issue:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): %{issue_note} + **Commented in Issue:** [%{gitlab_user}](%{gitlab_user_url}) commented this WP in Issue %{issue_number} [%{issue_title}](%{issue_url}) on [%{repository}](%{repository_url}): + %{issue_note} note_snippet_referenced_comment: > - **Referenced in Snippet:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Snippet %{snippet_number} [%{snippet_title}](%{snippet_url}) on [%{repository}](%{repository_url}): %{snippet_note} + **Referenced in Snippet:** [%{gitlab_user}](%{gitlab_user_url}) referenced this WP in Snippet %{snippet_number} [%{snippet_title}](%{snippet_url}) on [%{repository}](%{repository_url}): + %{snippet_note} issue_opened_referenced_comment: > **Issue Opened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been opened by [%{gitlab_user}](%{gitlab_user_url}). issue_closed_referenced_comment: > @@ -78,8 +84,11 @@ uz: issue_reopened_referenced_comment: > **Issue Reopened:** Issue %{issue_number} [%{issue_title}](%{issue_url}) for [%{repository}](%{repository_url}) has been reopened by [%{gitlab_user}](%{gitlab_user_url}). push_single_commit_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} push_single_commit_comment_with_ref: > - **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in %{reference}:** [%{gitlab_user}](%{gitlab_user_url}) pushed [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} push_multiple_commits_comment: > - **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: %{commit_note} + **Pushed in MR:** [%{gitlab_user}](%{gitlab_user_url}) pushed multiple commits [%{commit_number}](%{commit_url}) to [%{repository}](%{repository_url}) at %{commit_timestamp}: + %{commit_note} diff --git a/modules/grids/app/components/grids/widgets/members.rb b/modules/grids/app/components/grids/widgets/members.rb index 7d5d07acc7b..8366b4ae2ed 100644 --- a/modules/grids/app/components/grids/widgets/members.rb +++ b/modules/grids/app/components/grids/widgets/members.rb @@ -49,7 +49,7 @@ module Grids .map do |role| members_query = members_for_role(role.id) total_count = members_query.count - members = members_query.limit(limit).to_a.map(&:user) + members = members_query.limit(limit).to_a.map(&:principal) { role:, diff --git a/modules/ldap_groups/config/locales/crowdin/zh-CN.yml b/modules/ldap_groups/config/locales/crowdin/zh-CN.yml index cec35d2ee7b..de9f185509c 100644 --- a/modules/ldap_groups/config/locales/crowdin/zh-CN.yml +++ b/modules/ldap_groups/config/locales/crowdin/zh-CN.yml @@ -6,7 +6,7 @@ zh-CN: description: '与 OpenProject 组同步 LDAP 组以管理用户,更改他们的权限以便不同组的用户管理。' plugin_openproject_ldap_groups: name: "OpenProject LDAP 组" - description: "LDAP组成员同步。" + description: "LDAP 组成员同步。" activerecord: attributes: ldap_groups/synchronized_group: diff --git a/modules/ldap_groups/config/locales/crowdin/zh-TW.yml b/modules/ldap_groups/config/locales/crowdin/zh-TW.yml index 1a4720ebcaf..15ce71c0dc6 100644 --- a/modules/ldap_groups/config/locales/crowdin/zh-TW.yml +++ b/modules/ldap_groups/config/locales/crowdin/zh-TW.yml @@ -15,7 +15,7 @@ zh-TW: ldap_auth_source: 'LDAP 連線' sync_users: '同步使用者' ldap_groups/synchronized_filter: - filter_string: 'LDAP篩選條件' + filter_string: '簡約登入目錄制約(LDAP)篩選' auth_source: '驗證來源' ldap_auth_source: 'LDAP 連線' group_name_attribute: "群組名字屬性" diff --git a/modules/meeting/app/components/meetings/index/form_component.rb b/modules/meeting/app/components/meetings/index/form_component.rb index 119128c8f43..6e3e1dfe2ed 100644 --- a/modules/meeting/app/components/meetings/index/form_component.rb +++ b/modules/meeting/app/components/meetings/index/form_component.rb @@ -83,7 +83,7 @@ module Meetings def available_templates return [] unless @project - @available_templates ||= Meeting.onetime_templates.where(project: @project).visible + @available_templates ||= Meeting.templates_visible_in_project(@project) end end end diff --git a/modules/meeting/app/components/meetings/side_panel/sharing_component.html.erb b/modules/meeting/app/components/meetings/side_panel/sharing_component.html.erb new file mode 100644 index 00000000000..b1364f077a5 --- /dev/null +++ b/modules/meeting/app/components/meetings/side_panel/sharing_component.html.erb @@ -0,0 +1,66 @@ +<%#-- 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 do + render(Primer::OpenProject::SidePanel::Section.new) do |section| + section.with_title { t(:label_meeting_template_sharing) } + + flex_layout do |content| + content.with_row do + render(Primer::Beta::Text.new(color: :subtle)) do + t(:text_meeting_template_sharing_description) + end + end + + content.with_row(mt: 3) do + render(Primer::Alpha::ActionMenu.new) do |menu| + menu.with_show_button(scheme: :secondary) do |button| + button.with_trailing_visual_icon(icon: :"triangle-down") + sharing_label(@meeting.sharing) + end + + Meeting.sharings.each_key do |sharing| + menu.with_item( + label: sharing_label(sharing), + tag: :a, + href: change_sharing_path(sharing), + content_arguments: { data: { + action: "click->meetings--submit#intercept", + href: change_sharing_path(sharing), + method: "PUT" + } } + ) + end + end + end + end + end + end +%> diff --git a/modules/meeting/app/components/meetings/side_panel/sharing_component.rb b/modules/meeting/app/components/meetings/side_panel/sharing_component.rb new file mode 100644 index 00000000000..89547b5620a --- /dev/null +++ b/modules/meeting/app/components/meetings/side_panel/sharing_component.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Meetings + class SidePanel::SharingComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(meeting:) + super + + @meeting = meeting + @project = meeting.project + end + + def render? + User.current.allowed_in_project?(:edit_meetings, @project) + end + + private + + def sharing_label(sharing) + I18n.t("label_meeting_template_sharing_#{sharing}") + end + + def change_sharing_path(sharing) + change_sharing_project_meeting_path(@project, @meeting, sharing:) + end + end +end diff --git a/modules/meeting/app/components/meetings/side_panel_component.html.erb b/modules/meeting/app/components/meetings/side_panel_component.html.erb index c8ceb86e01f..1280f39c3b7 100644 --- a/modules/meeting/app/components/meetings/side_panel_component.html.erb +++ b/modules/meeting/app/components/meetings/side_panel_component.html.erb @@ -20,6 +20,10 @@ ) end + if @meeting.onetime_template? + panel.with_section(Meetings::SidePanel::SharingComponent.new(meeting: @meeting)) + end + panel.with_section( Meetings::SidePanel::AttachmentsComponent.new(meeting: @meeting), grid_row_arguments: desktop_grid_row_arguments diff --git a/modules/meeting/app/contracts/meetings/base_contract.rb b/modules/meeting/app/contracts/meetings/base_contract.rb index 8e51766fad3..25400c061fb 100644 --- a/modules/meeting/app/contracts/meetings/base_contract.rb +++ b/modules/meeting/app/contracts/meetings/base_contract.rb @@ -44,5 +44,16 @@ module Meetings attribute :start_time_hour attribute :template attribute :notify + attribute :sharing do + validate_sharing_only_on_onetime_templates + end + + private + + def validate_sharing_only_on_onetime_templates + return if model.onetime_template? + + errors.add :sharing, :not_allowed if model.sharing.present? + end end end diff --git a/modules/meeting/app/controllers/concerns/meetings/agenda_component_streams.rb b/modules/meeting/app/controllers/concerns/meetings/agenda_component_streams.rb index 4f6792e8d26..b38ec1790b5 100644 --- a/modules/meeting/app/controllers/concerns/meetings/agenda_component_streams.rb +++ b/modules/meeting/app/controllers/concerns/meetings/agenda_component_streams.rb @@ -89,6 +89,16 @@ module Meetings ) end + def update_sidebar_sharing_component_via_turbo_stream(meeting: @meeting) + return unless meeting.onetime_template? + + update_via_turbo_stream( + component: Meetings::SidePanel::SharingComponent.new( + meeting: + ) + ) + end + def update_add_user_form_component_via_turbo_stream(meeting: @meeting) update_via_turbo_stream( component: Meetings::Participants::AddUserFormComponent.new(meeting:) diff --git a/modules/meeting/app/controllers/meeting_templates_controller.rb b/modules/meeting/app/controllers/meeting_templates_controller.rb index 0e8b706fda4..0e6bb7074f4 100644 --- a/modules/meeting/app/controllers/meeting_templates_controller.rb +++ b/modules/meeting/app/controllers/meeting_templates_controller.rb @@ -40,11 +40,13 @@ class MeetingTemplatesController < ApplicationController menu_item :meetings def index - @templates = Meeting.onetime_templates - .visible - .order(:title) - - @templates = @templates.where(project_id: @project.id) if @project + @templates = if @project + Meeting.available_onetime_templates.where(project_id: @project.id).order(:title) + else + accessible_ids = Project.allowed_to(User.current, :view_meetings).select(:id) + base = Meeting.available_onetime_templates + base.where(project_id: accessible_ids).or(base.where(sharing: :system)).order(:title) + end render "meeting_templates/index", locals: { menu_name: project_or_global_menu } diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb index e14950d30d3..3180d9a62f5 100644 --- a/modules/meeting/app/controllers/meetings_controller.rb +++ b/modules/meeting/app/controllers/meetings_controller.rb @@ -294,6 +294,23 @@ class MeetingsController < ApplicationController respond_with_turbo_streams end + def change_sharing + sharing = params[:sharing] + + if Meeting.sharings.key?(sharing) + call = ::Meetings::UpdateService + .new(user: current_user, model: @meeting) + .call(sharing:) + + render_base_error_in_flash_message_via_turbo_stream(call.errors) unless call.success? + end + + update_header_component_via_turbo_stream + update_sidebar_sharing_component_via_turbo_stream + + respond_with_turbo_streams + end + def download_ics ::Meetings::ICalService .new(user: current_user, meeting: @meeting) @@ -469,7 +486,7 @@ class MeetingsController < ApplicationController @meeting = call.result # When coming from the "Create from template" button, load the template to hide the form field - @copy_from = Meeting.onetime_templates.visible.find_by(id: params[:template_id]) if params[:template_id].present? + @copy_from = Meeting.templates_visible_in_project(@project).find_by(id: params[:template_id]) if params[:template_id].present? end def global_upcoming_meetings @@ -563,7 +580,7 @@ class MeetingsController < ApplicationController # Check for template selection from form submission template_id = params[:meeting][:template_id] if template_id.present? - @copy_from = Meeting.onetime_templates.visible.find_by(id: template_id) + @copy_from = Meeting.templates_visible_in_project(@project).find_by(id: template_id) return end diff --git a/modules/meeting/app/forms/meeting/template_autocompleter.rb b/modules/meeting/app/forms/meeting/template_autocompleter.rb index e236a3d1ec5..3a4f94ea3a0 100644 --- a/modules/meeting/app/forms/meeting/template_autocompleter.rb +++ b/modules/meeting/app/forms/meeting/template_autocompleter.rb @@ -61,6 +61,6 @@ class Meeting::TemplateAutocompleter < ApplicationForm private def templates - Meeting.onetime_templates.where(project: @project).visible.order(:title) + Meeting.templates_visible_in_project(@project).order(:title) end end diff --git a/modules/meeting/app/models/meeting.rb b/modules/meeting/app/models/meeting.rb index d5af7813fbf..8bc03bce7b7 100644 --- a/modules/meeting/app/models/meeting.rb +++ b/modules/meeting/app/models/meeting.rb @@ -91,6 +91,10 @@ class Meeting < ApplicationRecord joins(:participants).where(meeting_participants: { user_id: user.id }) } + scope :available_onetime_templates, -> { + onetime_templates.where(project_id: Project.active.select(:id)) + } + acts_as_attachable( after_remove: :attachments_changed, order: "#{Attachment.table_name}.file", @@ -118,6 +122,7 @@ class Meeting < ApplicationRecord accepts_nested_attributes_for :participants, allow_destroy: true validates :title, :project_id, presence: true + validates :sharing, absence: true, unless: :onetime_template? validates :duration, numericality: { greater_than: 0 } @@ -135,6 +140,21 @@ class Meeting < ApplicationRecord closed: 5 } + enum :sharing, { + none: "none", + descendants: "descendants", + system: "system" + }, prefix: :sharing, validate: { allow_nil: true } + + def self.templates_visible_in_project(project, user = User.current) + accessible_ids = Project.allowed_to(user, :view_meetings).select(:id) + + available_onetime_templates + .where(project_id: project.id).where(project_id: accessible_ids) + .or(available_onetime_templates.where(sharing: :descendants, project_id: project.ancestors.select(:id))) + .or(available_onetime_templates.where(sharing: :system)) + end + def recurring? recurring_meeting_id.present? end diff --git a/modules/meeting/app/services/meetings/copy_service.rb b/modules/meeting/app/services/meetings/copy_service.rb index 2d39db05136..eaa751e1e10 100644 --- a/modules/meeting/app/services/meetings/copy_service.rb +++ b/modules/meeting/app/services/meetings/copy_service.rb @@ -94,7 +94,7 @@ module Meetings end def writable_meeting_attributes(meeting) - instantiate_contract(meeting, user).writable_attributes - %w[start_date start_time_hour uid] + instantiate_contract(meeting, user).writable_attributes - %w[start_date start_time_hour uid sharing] end def copy_meeting_attachment(copy) diff --git a/modules/meeting/app/services/meetings/set_attributes_service.rb b/modules/meeting/app/services/meetings/set_attributes_service.rb index b8cd8b93a4b..dd5784d8586 100644 --- a/modules/meeting/app/services/meetings/set_attributes_service.rb +++ b/modules/meeting/app/services/meetings/set_attributes_service.rb @@ -41,12 +41,13 @@ module Meetings end end - def set_default_attributes(_params) + def set_default_attributes(_params) # rubocop:disable Metrics/AbcSize model.change_by_system do model.author = user model.duration ||= 1 model.state = "draft" if !model.recurring? || model.template? model.notify = false + model.sharing = "none" if model.onetime_template? end end diff --git a/modules/meeting/app/services/recurring_meetings/update_service.rb b/modules/meeting/app/services/recurring_meetings/update_service.rb index ea2dd48f60e..9f8db456784 100644 --- a/modules/meeting/app/services/recurring_meetings/update_service.rb +++ b/modules/meeting/app/services/recurring_meetings/update_service.rb @@ -162,10 +162,10 @@ module RecurringMeetings return if new_title == @old_title recurring_meeting - .scheduled_instances(upcoming: true) - .instantiated - .each do |scheduled| - scheduled.meeting.update_column(:title, new_title) + .scheduled_instances(upcoming: true) + .instantiated + .each do |scheduled| + scheduled.meeting.update_column(:title, new_title) end end diff --git a/modules/meeting/config/locales/crowdin/af.yml b/modules/meeting/config/locales/crowdin/af.yml index 2280c7bc4e4..b9944dd98a2 100644 --- a/modules/meeting/config/locales/crowdin/af.yml +++ b/modules/meeting/config/locales/crowdin/af.yml @@ -42,6 +42,7 @@ af: start_date: "Datum" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Titel" author: "Outeur" @@ -622,6 +623,11 @@ af: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/ar.yml b/modules/meeting/config/locales/crowdin/ar.yml index 91d10268cfa..f5649e72c52 100644 --- a/modules/meeting/config/locales/crowdin/ar.yml +++ b/modules/meeting/config/locales/crowdin/ar.yml @@ -46,6 +46,7 @@ ar: start_date: "التاريخ" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "العنوان" author: "المؤلف" @@ -650,6 +651,11 @@ ar: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/az.yml b/modules/meeting/config/locales/crowdin/az.yml index 116e3ba1ccd..0080a00552b 100644 --- a/modules/meeting/config/locales/crowdin/az.yml +++ b/modules/meeting/config/locales/crowdin/az.yml @@ -42,6 +42,7 @@ az: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -622,6 +623,11 @@ az: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/be.yml b/modules/meeting/config/locales/crowdin/be.yml index 7a57c008f84..716ad1c6f06 100644 --- a/modules/meeting/config/locales/crowdin/be.yml +++ b/modules/meeting/config/locales/crowdin/be.yml @@ -44,6 +44,7 @@ be: start_date: "Дата" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -636,6 +637,11 @@ be: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/bg.yml b/modules/meeting/config/locales/crowdin/bg.yml index c5ce3462081..19becde207a 100644 --- a/modules/meeting/config/locales/crowdin/bg.yml +++ b/modules/meeting/config/locales/crowdin/bg.yml @@ -42,6 +42,7 @@ bg: start_date: "Дата" start_time: "Начален час" start_time_hour: "Начален час" + sharing: "Sharing" meeting_agenda_item: title: "Заглавие" author: "Автор" @@ -622,6 +623,11 @@ bg: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Тази среща вече не може да се редактира." text_meeting_not_present_anymore: "Тази среща е заличена. Моля, изберете друга среща." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/ca.yml b/modules/meeting/config/locales/crowdin/ca.yml index 498d0dba6b6..bb9dfaa504e 100644 --- a/modules/meeting/config/locales/crowdin/ca.yml +++ b/modules/meeting/config/locales/crowdin/ca.yml @@ -42,6 +42,7 @@ ca: start_date: "Data" start_time: "Hora d'inici" start_time_hour: "Hora d'inici" + sharing: "Sharing" meeting_agenda_item: title: "Títol" author: "Autor/a" @@ -622,6 +623,11 @@ ca: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Aquesta reunió ja no es pot editar." text_meeting_not_present_anymore: "Aquesta reunió es va eliminar. Si us plau, selecciona una altra reunió." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/ckb-IR.yml b/modules/meeting/config/locales/crowdin/ckb-IR.yml index c3db5261d4a..8bd93fc2b45 100644 --- a/modules/meeting/config/locales/crowdin/ckb-IR.yml +++ b/modules/meeting/config/locales/crowdin/ckb-IR.yml @@ -42,6 +42,7 @@ ckb-IR: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -622,6 +623,11 @@ ckb-IR: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/cs.yml b/modules/meeting/config/locales/crowdin/cs.yml index 2dc71cf04e3..a335c7f279e 100644 --- a/modules/meeting/config/locales/crowdin/cs.yml +++ b/modules/meeting/config/locales/crowdin/cs.yml @@ -44,6 +44,7 @@ cs: start_date: "Datum" start_time: "Čas zahájení" start_time_hour: "Čas zahájení" + sharing: "Sharing" meeting_agenda_item: title: "Název" author: "Autor" @@ -512,7 +513,7 @@ cs: notice_meeting_updated: "Tato stránka byla aktualizována někým jiným. Pro zobrazení změn znovu načtena." permission_create_meetings: "Vytvořit schůzku\n" permission_edit_meetings: "Upravit schůzku" - permission_delete_meetings: "Odstranit schůzky" + permission_delete_meetings: "Smazat schůzku" permission_view_meetings: "Zobrazit schůzky" permission_manage_agendas: "Správa zápisů" permission_manage_agendas_explanation: "Allows creating, editing and removing agenda items" @@ -636,6 +637,11 @@ cs: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Tato schůzka již není upravitelná." text_meeting_not_present_anymore: "Tato schůzka byla odstraněna. Vyberte prosím jinou schůzku." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/da.yml b/modules/meeting/config/locales/crowdin/da.yml index d478adfcc8d..21ebd450c36 100644 --- a/modules/meeting/config/locales/crowdin/da.yml +++ b/modules/meeting/config/locales/crowdin/da.yml @@ -42,6 +42,7 @@ da: start_date: "Dato" start_time: "Starttidspunkt" start_time_hour: "Starttidspunkt" + sharing: "Sharing" meeting_agenda_item: title: "Titel" author: "Forfatter" @@ -622,6 +623,11 @@ da: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/de.yml b/modules/meeting/config/locales/crowdin/de.yml index 74f623fe432..f47e26c306b 100644 --- a/modules/meeting/config/locales/crowdin/de.yml +++ b/modules/meeting/config/locales/crowdin/de.yml @@ -22,9 +22,9 @@ #English strings go here for Rails i18n de: plugin_openproject_meeting: - name: "OpenProject Besprechung" + name: "OpenProject Besprechungen" description: >- - Besprechungen für OpenProject + Dieses Modul fügt Funktionen zur Unterstützung von Besprechungen zu OpenProject hinzu. Sie können Besprechungen planen und dabei Teilnehmer aus demselben Projekt auswählen, die an der Besprechung teilnehmen sollen. Es kann eine Tagesordnung erstellt und an die Eingeladenen gesendet werden. Nach der Besprechung können die Teilnehmer ausgewählt und Protokolle auf der Grundlage der Tagesordnung erstellt werden. Schließlich kann das Protokoll an alle Teilnehmer und Eingeladenen gesendet werden. activerecord: attributes: meeting: @@ -42,6 +42,7 @@ de: start_date: "Datum" start_time: "Startzeit" start_time_hour: "Startzeit" + sharing: "Teilen" meeting_agenda_item: title: "Titel" author: "Autor" @@ -73,7 +74,7 @@ de: section_not_belong_to_meeting: "Die Sektion gehört nicht zur gleichen Besprechung." user_invalid: "ist kein gültiger Teilnehmer." recurring_meeting_interim_response: - not_an_occurrence: "ist keine gültige Uhrzeit für diese wiederkehrende Besprechung" + not_an_occurrence: "ist keine gültige Uhrzeit für dieses wiederkehrende Treffen" recurring_meeting: must_cover_existing_meetings: one: "Es gibt eine offene Besprechung in der Terminserie, die nicht durch den neuen Zeitplan abgedeckt ist. Passen Sie den Zeitplan an, um alle bestehenden Meetings einzuschließen." @@ -622,6 +623,11 @@ de: text_exit_draft_mode_dialog_subtitle: "Sie können nicht mehr zum Entwurfsmodus zurückkehren, sobald Sie die Besprechung eröffnen." text_exit_draft_mode_dialog_template_title: "Das erste Vorkommen dieser Terminserie öffnen?" text_exit_draft_mode_dialog_template_subtitle: "Sie können danach nicht mehr in den Entwurfsmodus zurückkehren." + label_meeting_template_sharing: "Teilen" + label_meeting_template_sharing_none: "Nur dieses Projekt" + label_meeting_template_sharing_descendants: "Unterprojekte" + label_meeting_template_sharing_system: "Alle Projekte" + text_meeting_template_sharing_description: "Diese Vorlage kann mit Unterprojekten oder anderen Projekten geteilt werden. Es werden nur die Tagesordnungspunkte und Anhänge kopiert." text_meeting_not_editable_anymore: "Diese Besprechung ist nicht mehr bearbeitbar." text_meeting_not_present_anymore: "Dieses Meeting wurde gelöscht. Bitte wählen Sie eine andere Besprechung." label_add_work_package_to_meeting_dialog_title: "Besprechung auswählen" diff --git a/modules/meeting/config/locales/crowdin/el.yml b/modules/meeting/config/locales/crowdin/el.yml index 35d2c1451a3..cceed748c90 100644 --- a/modules/meeting/config/locales/crowdin/el.yml +++ b/modules/meeting/config/locales/crowdin/el.yml @@ -42,6 +42,7 @@ el: start_date: "Ημερομηνία" start_time: "Ώρα έναρξης" start_time_hour: "Ώρα έναρξης" + sharing: "Sharing" meeting_agenda_item: title: "Τίτλος" author: "Συγγραφέας" @@ -622,6 +623,11 @@ el: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/eo.yml b/modules/meeting/config/locales/crowdin/eo.yml index df83d8804ef..9788d9f8586 100644 --- a/modules/meeting/config/locales/crowdin/eo.yml +++ b/modules/meeting/config/locales/crowdin/eo.yml @@ -42,6 +42,7 @@ eo: start_date: "Dato" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Titolo" author: "Aŭtoro" @@ -622,6 +623,11 @@ eo: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/es.yml b/modules/meeting/config/locales/crowdin/es.yml index 385a537fdad..f3445f3f7b1 100644 --- a/modules/meeting/config/locales/crowdin/es.yml +++ b/modules/meeting/config/locales/crowdin/es.yml @@ -42,6 +42,7 @@ es: start_date: "Fecha" start_time: "Hora de inicio" start_time_hour: "Hora de inicio" + sharing: "Compartiendo" meeting_agenda_item: title: "Título" author: "Autor" @@ -622,6 +623,11 @@ es: text_exit_draft_mode_dialog_subtitle: "Una vez programada una reunión, no puedes volver al modo Borrador." text_exit_draft_mode_dialog_template_title: "¿Abrir la primera repetición de esta serie de reuniones?" text_exit_draft_mode_dialog_template_subtitle: "No podrás volver al modo Borrador después de esto." + label_meeting_template_sharing: "Compartiendo" + label_meeting_template_sharing_none: "Solo este proyecto" + label_meeting_template_sharing_descendants: "Subproyectos" + label_meeting_template_sharing_system: "Todos los proyectos" + text_meeting_template_sharing_description: "Esta plantilla se puede compartir con subproyectos u otros proyectos en esta instancia. Solo se copiarán los elementos del orden del día y los archivos adjuntos." text_meeting_not_editable_anymore: "Esta reunión ya no es editable." text_meeting_not_present_anymore: "Esta reunión ha sido eliminada. Por favor, seleccione otra reunión." label_add_work_package_to_meeting_dialog_title: "Seleccionar reunión" diff --git a/modules/meeting/config/locales/crowdin/et.yml b/modules/meeting/config/locales/crowdin/et.yml index 13937747e2c..5a9aa0e2fd4 100644 --- a/modules/meeting/config/locales/crowdin/et.yml +++ b/modules/meeting/config/locales/crowdin/et.yml @@ -42,6 +42,7 @@ et: start_date: "Kuupäev" start_time: "Algusaeg" start_time_hour: "Algusaeg" + sharing: "Sharing" meeting_agenda_item: title: "Pealkiri" author: "Autor" @@ -622,6 +623,11 @@ et: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/eu.yml b/modules/meeting/config/locales/crowdin/eu.yml index 0ac3ac3193c..be9cf5b754a 100644 --- a/modules/meeting/config/locales/crowdin/eu.yml +++ b/modules/meeting/config/locales/crowdin/eu.yml @@ -42,6 +42,7 @@ eu: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -622,6 +623,11 @@ eu: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/fa.yml b/modules/meeting/config/locales/crowdin/fa.yml index 4ea7f27f7d7..7be582e9779 100644 --- a/modules/meeting/config/locales/crowdin/fa.yml +++ b/modules/meeting/config/locales/crowdin/fa.yml @@ -42,6 +42,7 @@ fa: start_date: "تاریخ" start_time: "زمان شروع" start_time_hour: "زمان شروع" + sharing: "Sharing" meeting_agenda_item: title: "عنوان" author: "نویسنده" @@ -622,6 +623,11 @@ fa: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/fi.yml b/modules/meeting/config/locales/crowdin/fi.yml index 747db12dc1e..33f17c593a8 100644 --- a/modules/meeting/config/locales/crowdin/fi.yml +++ b/modules/meeting/config/locales/crowdin/fi.yml @@ -42,6 +42,7 @@ fi: start_date: "Päivämäärä" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Otsikko" author: "Tekijä" @@ -622,6 +623,11 @@ fi: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/fil.yml b/modules/meeting/config/locales/crowdin/fil.yml index f2636920aa4..a94dda552b5 100644 --- a/modules/meeting/config/locales/crowdin/fil.yml +++ b/modules/meeting/config/locales/crowdin/fil.yml @@ -42,6 +42,7 @@ fil: start_date: "Petsa" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Pamagat" author: "May-akda" @@ -622,6 +623,11 @@ fil: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/fr.yml b/modules/meeting/config/locales/crowdin/fr.yml index a0351465412..3409978e1d7 100644 --- a/modules/meeting/config/locales/crowdin/fr.yml +++ b/modules/meeting/config/locales/crowdin/fr.yml @@ -42,6 +42,7 @@ fr: start_date: "Date" start_time: "Heure de début" start_time_hour: "Heure de début" + sharing: "Partage" meeting_agenda_item: title: "Titre" author: "Auteur" @@ -622,6 +623,11 @@ fr: text_exit_draft_mode_dialog_subtitle: "Vous ne pouvez pas revenir au mode brouillon une fois que vous avez planifié une réunion." text_exit_draft_mode_dialog_template_title: "Ouvrez la première occurrence de cette série de réunions ?" text_exit_draft_mode_dialog_template_subtitle: "Vous ne pouvez plus revenir au mode brouillon après cette opération." + label_meeting_template_sharing: "Partage" + label_meeting_template_sharing_none: "Seulement ce projet" + label_meeting_template_sharing_descendants: "Sous-projets" + label_meeting_template_sharing_system: "Tous les projets" + text_meeting_template_sharing_description: "Ce modèle peut être partagé avec des sous-projets ou d'autres projets dans cette instance. Seuls les points de l'ordre du jour et les pièces jointes seront copiés." text_meeting_not_editable_anymore: "Cette réunion n'est plus modifiable." text_meeting_not_present_anymore: "Cette réunion a été supprimée. Veuillez sélectionner une autre réunion." label_add_work_package_to_meeting_dialog_title: "Sélectionner la réunion" diff --git a/modules/meeting/config/locales/crowdin/he.yml b/modules/meeting/config/locales/crowdin/he.yml index 2a5c9078b99..77c964875b9 100644 --- a/modules/meeting/config/locales/crowdin/he.yml +++ b/modules/meeting/config/locales/crowdin/he.yml @@ -44,6 +44,7 @@ he: start_date: "תאריך" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "כותרת" author: "מחבר" @@ -636,6 +637,11 @@ he: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/hi.yml b/modules/meeting/config/locales/crowdin/hi.yml index 074ce5eb6b0..4ee59debe29 100644 --- a/modules/meeting/config/locales/crowdin/hi.yml +++ b/modules/meeting/config/locales/crowdin/hi.yml @@ -42,6 +42,7 @@ hi: start_date: "तिथि" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "शीर्षक" author: "Author" @@ -622,6 +623,11 @@ hi: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/hr.yml b/modules/meeting/config/locales/crowdin/hr.yml index 95c8d1347fd..8d4fc4b7392 100644 --- a/modules/meeting/config/locales/crowdin/hr.yml +++ b/modules/meeting/config/locales/crowdin/hr.yml @@ -43,6 +43,7 @@ hr: start_date: "Datum" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Naziv" author: "Autor" @@ -629,6 +630,11 @@ hr: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/hu.yml b/modules/meeting/config/locales/crowdin/hu.yml index 2a5fe60004c..5f01ebfc10e 100644 --- a/modules/meeting/config/locales/crowdin/hu.yml +++ b/modules/meeting/config/locales/crowdin/hu.yml @@ -42,6 +42,7 @@ hu: start_date: "dátum" start_time: "Kezdés" start_time_hour: "Kezdés" + sharing: "Sharing" meeting_agenda_item: title: "Cím" author: "Szerző" @@ -622,6 +623,11 @@ hu: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Ez a megbeszélés már nem szerkeszthető." text_meeting_not_present_anymore: "Ez a megbeszélés törlésre került. Kérjük, válasszon ki egy másik megbeszélést." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/id.yml b/modules/meeting/config/locales/crowdin/id.yml index 67981c91aee..2fcfd63659e 100644 --- a/modules/meeting/config/locales/crowdin/id.yml +++ b/modules/meeting/config/locales/crowdin/id.yml @@ -41,6 +41,7 @@ id: start_date: "Tanggal" start_time: "Waktu mulai" start_time_hour: "Waktu mulai" + sharing: "Sharing" meeting_agenda_item: title: "Judul" author: "Penulis" @@ -615,6 +616,11 @@ id: text_exit_draft_mode_dialog_subtitle: "Anda tidak dapat kembali ke mode draf setellah Anda menjadwalkan rapat." text_exit_draft_mode_dialog_template_title: "Buka acara yang pertama dalam seri rapat ini?" text_exit_draft_mode_dialog_template_subtitle: "Anda tidak dapat kembali ke mode draf setelah ini." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Rapat ini tidak dapat disunting kembali." text_meeting_not_present_anymore: "Rapat ini telah dihapus. Silakan pilih rapat yang lain." label_add_work_package_to_meeting_dialog_title: "Pilih rapat" diff --git a/modules/meeting/config/locales/crowdin/it.yml b/modules/meeting/config/locales/crowdin/it.yml index 53522226fc6..6c324b54246 100644 --- a/modules/meeting/config/locales/crowdin/it.yml +++ b/modules/meeting/config/locales/crowdin/it.yml @@ -42,6 +42,7 @@ it: start_date: "Data" start_time: "Ora di inizio" start_time_hour: "Ora di inizio" + sharing: "Condivisione" meeting_agenda_item: title: "Titolo" author: "Autore" @@ -622,6 +623,11 @@ it: text_exit_draft_mode_dialog_subtitle: "Non può tornare alla modalità bozza una volta programmata una riunione." text_exit_draft_mode_dialog_template_title: "Aprire la prima occorrenza di questa serie di riunioni?" text_exit_draft_mode_dialog_template_subtitle: "Non puoi tornare alla modalità bozza dopo averlo fatto." + label_meeting_template_sharing: "Condivisione" + label_meeting_template_sharing_none: "Solo questo progetto" + label_meeting_template_sharing_descendants: "Sottoprogetti" + label_meeting_template_sharing_system: "Tutti i progetti" + text_meeting_template_sharing_description: "In questa istanza, questo modello può essere condiviso con sottoprogetti o altri progetti. Verranno copiati solo gli elementi dell'ordine del giorno e gli allegati." text_meeting_not_editable_anymore: "Questa riunione non è più modificabile." text_meeting_not_present_anymore: "Questa riunione è stata eliminata. Seleziona un'altra riunione." label_add_work_package_to_meeting_dialog_title: "Seleziona riunione" diff --git a/modules/meeting/config/locales/crowdin/ja.yml b/modules/meeting/config/locales/crowdin/ja.yml index 1e454b00896..4c390388639 100644 --- a/modules/meeting/config/locales/crowdin/ja.yml +++ b/modules/meeting/config/locales/crowdin/ja.yml @@ -41,6 +41,7 @@ ja: start_date: "日付" start_time: "開始時間" start_time_hour: "開始時間" + sharing: "Sharing" meeting_agenda_item: title: "タイトル" author: "作成者" @@ -246,7 +247,7 @@ ja: summary_occurrence: "An occurrence of '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" summary_series: "Meeting series '%{title}' has been cancelled by %{actor}, or you have been removed as a participant" summary: "'%{title}' has been cancelled by %{actor}, or you have been removed as a participant" - date_time: "予定日時" + date_time: "スケジュールされた日時" participant_added: header: "Meeting '%{title}' - Participant added" header_series: "Meeting series '%{title}' - Participant added" @@ -293,7 +294,7 @@ ja: title: "会議のキャンセル" heading: "この会議をキャンセルしますか?" confirmation_message_html: > - テンプレートにない会議情報は失われます。 続行しますか? + テンプレートにない会議情報は失われます。 続けますか? confirm_button: "発生をキャンセル" blankslate: title: "表示する会議がありません" @@ -485,7 +486,7 @@ ja: confirm_button: "この予定をキャンセル" end_series_dialog: title: "一連の会議を終了" - notice_successful_notification: "参加者全員にカレンダー更新の電子メールを送信" + notice_successful_notification: "すべての出席者にカレンダーの更新をメールしました" notice_meeting_template_created: "Template successfully created" notice_timezone_missing: タイムゾーンが設定されていない場合、%{zone} が使用されます。タイムゾーンを選択するには、ここをクリックしてください。 notice_meeting_updated: "このページは他の誰かによって更新されました。変更を表示するには再読み込みしてください。" @@ -573,7 +574,7 @@ ja: このバックログは、このワンタイム会議に固有のものです.アイテムをドラッグして追加または会議の議題から削除することができます. label_agenda_backlog_clear_title: "議題のバックログをクリアしますか?" text_agenda_backlog_clear_description: > - 現在アジェンダバックログにあるすべての項目を削除してもよろしいですか?このアクションは元に戻せません。 + 議題のバックログ内のすべての項目を削除してもよろしいですか?この操作は取り消せません。 label_series_backlog: "シリーズバックログ" text_series_backlog: > バックログはこのシリーズのすべての出現と共有されます。 項目をドラッグして、特定の会議から項目を追加または削除できます。 @@ -605,7 +606,7 @@ ja: text_meeting_closed_description: "この会議は終了しています。これ以上、議題項目の追加/削除はできません。" text_meeting_in_progress_description: "議題を変更したり、各項目のアウトカムを記録したり、参加者の出席を追跡することができます。 会議が完了すると、会議をクローズとしてマークしてロックできます。" text_meeting_open_dropdown_description: "既存の結果は残りますが、ユーザーは新しい結果を追加することはできません。" - text_meeting_in_progress_dropdown_description: "会議中に必要な情報や決定事項などの成果を文書化する。" + text_meeting_in_progress_dropdown_description: "会議中に取られた情報のニーズや意思決定などの成果を記録します。" text_meeting_closed_dropdown_description: "この会議は終了しました。これ以上、議題や結果を変更することはできません。" text_meeting_draft_banner: "現在下書きモードです。 会議の詳細を変更したり出席者を追加/削除したりしても,この会議はカレンダーの更新や招待状を送信しません。" text_onetime_meeting_template_banner: "You are currently editing a meeting template. You can use this template to create one-time meetings with a predefined agenda. Changes will not affect already-created meetings." @@ -615,6 +616,11 @@ ja: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "この会議はもう編集できません。" text_meeting_not_present_anymore: "この会議は削除されました。別の会議を選択してください。" label_add_work_package_to_meeting_dialog_title: "会議の選択" diff --git a/modules/meeting/config/locales/crowdin/ka.yml b/modules/meeting/config/locales/crowdin/ka.yml index 1fffeca46f5..bd2e6a5a181 100644 --- a/modules/meeting/config/locales/crowdin/ka.yml +++ b/modules/meeting/config/locales/crowdin/ka.yml @@ -42,6 +42,7 @@ ka: start_date: "თარიღი" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "სათაური" author: "ავტორი" @@ -622,6 +623,11 @@ ka: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/kk.yml b/modules/meeting/config/locales/crowdin/kk.yml index f30132835af..2a9a0513c55 100644 --- a/modules/meeting/config/locales/crowdin/kk.yml +++ b/modules/meeting/config/locales/crowdin/kk.yml @@ -42,6 +42,7 @@ kk: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -622,6 +623,11 @@ kk: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/ko.yml b/modules/meeting/config/locales/crowdin/ko.yml index 98cd86be342..f624df287a7 100644 --- a/modules/meeting/config/locales/crowdin/ko.yml +++ b/modules/meeting/config/locales/crowdin/ko.yml @@ -41,6 +41,7 @@ ko: start_date: "날짜" start_time: "시작 시간" start_time_hour: "시작 시간" + sharing: "공유" meeting_agenda_item: title: "제목" author: "작성자" @@ -615,6 +616,11 @@ ko: text_exit_draft_mode_dialog_subtitle: "미팅을 예약한 후에는 초안 모드로 돌아갈 수 없습니다." text_exit_draft_mode_dialog_template_title: "이 미팅 시리즈의 첫 번째 항목을 열어보시겠습니까?" text_exit_draft_mode_dialog_template_subtitle: "이후에는 초안 모드로 돌아갈 수 없습니다." + label_meeting_template_sharing: "공유" + label_meeting_template_sharing_none: "이 프로젝트만" + label_meeting_template_sharing_descendants: "하위 프로젝트" + label_meeting_template_sharing_system: "모든 프로젝트" + text_meeting_template_sharing_description: "이 템플릿은 해당 인스턴스의 하위 프로젝트 또는 다른 프로젝트와 공유할 수 있습니다. 의제 항목과 첨부 파일만 복사됩니다." text_meeting_not_editable_anymore: "이 미팅은 더 이상 편집할 수 없습니다." text_meeting_not_present_anymore: "이 미팅은 삭제되었습니다. 다른 미팅을 선택해 주세요." label_add_work_package_to_meeting_dialog_title: "미팅 선택" diff --git a/modules/meeting/config/locales/crowdin/lt.yml b/modules/meeting/config/locales/crowdin/lt.yml index 525672df94f..91b3f801a5f 100644 --- a/modules/meeting/config/locales/crowdin/lt.yml +++ b/modules/meeting/config/locales/crowdin/lt.yml @@ -44,6 +44,7 @@ lt: start_date: "Data" start_time: "Pradžios laikas" start_time_hour: "Pradžios laikas" + sharing: "Sharing" meeting_agenda_item: title: "Pavadinimas" author: "Autorius" @@ -636,6 +637,11 @@ lt: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Šis susitikimas daugiau neberedaguojamas" text_meeting_not_present_anymore: "Šis susitikimas buvo ištrintas. Prašome parinkti kitą susitikimą." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/lv.yml b/modules/meeting/config/locales/crowdin/lv.yml index 217d983e044..5998f60280c 100644 --- a/modules/meeting/config/locales/crowdin/lv.yml +++ b/modules/meeting/config/locales/crowdin/lv.yml @@ -43,6 +43,7 @@ lv: start_date: "Datums" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Virsraksts" author: "Autors" @@ -629,6 +630,11 @@ lv: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/mn.yml b/modules/meeting/config/locales/crowdin/mn.yml index 0cad0db0661..c644fac93f1 100644 --- a/modules/meeting/config/locales/crowdin/mn.yml +++ b/modules/meeting/config/locales/crowdin/mn.yml @@ -42,6 +42,7 @@ mn: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -622,6 +623,11 @@ mn: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/ms.yml b/modules/meeting/config/locales/crowdin/ms.yml index 0b532677465..66e80c49f9c 100644 --- a/modules/meeting/config/locales/crowdin/ms.yml +++ b/modules/meeting/config/locales/crowdin/ms.yml @@ -41,6 +41,7 @@ ms: start_date: "Tarikh" start_time: "Masa mula" start_time_hour: "Masa mula" + sharing: "Sharing" meeting_agenda_item: title: "Tajuk" author: "Pengarang" @@ -615,6 +616,11 @@ ms: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Mesyuarat ini tidak boleh diedit lagi." text_meeting_not_present_anymore: "Mesyuarat ini telah dipadamkan. Sila pilih mesyuarat lain." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/ne.yml b/modules/meeting/config/locales/crowdin/ne.yml index 1ac65742bd4..bcc2c2aa0bc 100644 --- a/modules/meeting/config/locales/crowdin/ne.yml +++ b/modules/meeting/config/locales/crowdin/ne.yml @@ -42,6 +42,7 @@ ne: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -622,6 +623,11 @@ ne: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/nl.yml b/modules/meeting/config/locales/crowdin/nl.yml index eedbdaadb4d..75ee48f568c 100644 --- a/modules/meeting/config/locales/crowdin/nl.yml +++ b/modules/meeting/config/locales/crowdin/nl.yml @@ -42,6 +42,7 @@ nl: start_date: "Datum" start_time: "Starttijd" start_time_hour: "Starttijd" + sharing: "Sharing" meeting_agenda_item: title: "Titel" author: "Auteur" @@ -622,6 +623,11 @@ nl: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Deze vergadering kan niet meer bewerkt worden." text_meeting_not_present_anymore: "Deze vergadering is verwijderd. Selecteer een andere vergadering." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/no.yml b/modules/meeting/config/locales/crowdin/no.yml index a01f411dd63..315f1ff0db7 100644 --- a/modules/meeting/config/locales/crowdin/no.yml +++ b/modules/meeting/config/locales/crowdin/no.yml @@ -42,6 +42,7 @@ start_date: "Dato" start_time: "Starttid" start_time_hour: "Starttid" + sharing: "Sharing" meeting_agenda_item: title: "Tittel" author: "Forfatter" @@ -622,6 +623,11 @@ text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Dette møtet er ikke lenger redigerbart." text_meeting_not_present_anymore: "Dette møtet ble slettet. Velg et annet møte." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/pl.yml b/modules/meeting/config/locales/crowdin/pl.yml index 63058498157..9b1ae56c1df 100644 --- a/modules/meeting/config/locales/crowdin/pl.yml +++ b/modules/meeting/config/locales/crowdin/pl.yml @@ -44,6 +44,7 @@ pl: start_date: "Data" start_time: "Data rozpoczęcia" start_time_hour: "Godzina rozpoczęcia" + sharing: "Udostępnianie" meeting_agenda_item: title: "Tytuł" author: "Autor" @@ -636,6 +637,11 @@ pl: text_exit_draft_mode_dialog_subtitle: "Po zaplanowaniu spotkania nie można powrócić do trybu wersji roboczej." text_exit_draft_mode_dialog_template_title: "Otworzyć pierwsze wystąpienie z tej serii spotkań?" text_exit_draft_mode_dialog_template_subtitle: "Po tym nie możesz powrócić do trybu wersji roboczej." + label_meeting_template_sharing: "Udostępnianie" + label_meeting_template_sharing_none: "Tylko ten projekt" + label_meeting_template_sharing_descendants: "Podprojekty" + label_meeting_template_sharing_system: "Wszystkie projekty" + text_meeting_template_sharing_description: "Ten szablon można udostępniać podprojektom lub innym projektom w tym wystąpieniu. Skopiowane zostaną tylko pozycje planu spotkania i załączniki." text_meeting_not_editable_anymore: "Tego spotkania nie można już edytować." text_meeting_not_present_anymore: "To spotkanie zostało usunięte. Wybierz inne spotkanie." label_add_work_package_to_meeting_dialog_title: "Wybierz spotkanie" diff --git a/modules/meeting/config/locales/crowdin/pt-BR.yml b/modules/meeting/config/locales/crowdin/pt-BR.yml index f738c3ad94f..88f509605ac 100644 --- a/modules/meeting/config/locales/crowdin/pt-BR.yml +++ b/modules/meeting/config/locales/crowdin/pt-BR.yml @@ -42,6 +42,7 @@ pt-BR: start_date: "Data" start_time: "Hora de início" start_time_hour: "Hora de início" + sharing: "Compartilhamento" meeting_agenda_item: title: "Título" author: "Autor" @@ -622,6 +623,11 @@ pt-BR: text_exit_draft_mode_dialog_subtitle: "Não é possível voltar ao modo rascunho depois de agendar uma reunião." text_exit_draft_mode_dialog_template_title: "Abrir a primeira ocorrência desta série de reuniões?" text_exit_draft_mode_dialog_template_subtitle: "Você não pode retornar ao modo rascunho após isto." + label_meeting_template_sharing: "Compartilhamento" + label_meeting_template_sharing_none: "Apenas neste projeto" + label_meeting_template_sharing_descendants: "Subprojetos" + label_meeting_template_sharing_system: "Todos os projetos" + text_meeting_template_sharing_description: "Este modelo pode ser compartilhado com subprojetos ou outros projetos nesta instância. Apenas os itens de pauta e os anexos serão copiados." text_meeting_not_editable_anymore: "A reunião não pode mais ser editada." text_meeting_not_present_anymore: "Esta reunião foi excluída. Selecione outra reunião." label_add_work_package_to_meeting_dialog_title: "Selecionar reunião" diff --git a/modules/meeting/config/locales/crowdin/pt-PT.yml b/modules/meeting/config/locales/crowdin/pt-PT.yml index 4949f4cd24e..3ff2ab89a9d 100644 --- a/modules/meeting/config/locales/crowdin/pt-PT.yml +++ b/modules/meeting/config/locales/crowdin/pt-PT.yml @@ -42,6 +42,7 @@ pt-PT: start_date: "Data" start_time: "Hora de início" start_time_hour: "Hora de início" + sharing: "Partilhar" meeting_agenda_item: title: "Título" author: "Autor" @@ -622,6 +623,11 @@ pt-PT: text_exit_draft_mode_dialog_subtitle: "Não pode regressar ao modo de rascunho após agendar uma reunião." text_exit_draft_mode_dialog_template_title: "Abrir a primeira ocorrência desta série de reuniões?" text_exit_draft_mode_dialog_template_subtitle: "Depois disso, não pode voltar ao modo de rascunho." + label_meeting_template_sharing: "Partilhar" + label_meeting_template_sharing_none: "Apenas este projeto" + label_meeting_template_sharing_descendants: "Sub-projetos" + label_meeting_template_sharing_system: "Todos os projetos" + text_meeting_template_sharing_description: "Este modelo pode ser partilhado com sub-projetos ou outros projetos nesta instância. Só os elementos da agenda e os anexos serão copiados." text_meeting_not_editable_anymore: "Esta reunião não é mais editável." text_meeting_not_present_anymore: "Esta reunião foi excluída. Por favor, selecione outra reunião." label_add_work_package_to_meeting_dialog_title: "Selecionar reunião" diff --git a/modules/meeting/config/locales/crowdin/ro.yml b/modules/meeting/config/locales/crowdin/ro.yml index 827903fd42b..919502a6d40 100644 --- a/modules/meeting/config/locales/crowdin/ro.yml +++ b/modules/meeting/config/locales/crowdin/ro.yml @@ -43,6 +43,7 @@ ro: start_date: "Dată" start_time: "Dată început" start_time_hour: "Dată început" + sharing: "Sharing" meeting_agenda_item: title: "Titlu" author: "Autor" @@ -629,6 +630,11 @@ ro: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/ru.yml b/modules/meeting/config/locales/crowdin/ru.yml index e3a11f1299b..ab2052d8fb7 100644 --- a/modules/meeting/config/locales/crowdin/ru.yml +++ b/modules/meeting/config/locales/crowdin/ru.yml @@ -44,6 +44,7 @@ ru: start_date: "Дата" start_time: "Время начала" start_time_hour: "Время начала" + sharing: "Совместное использование" meeting_agenda_item: title: "Заголовок" author: "Автор" @@ -636,6 +637,11 @@ ru: text_exit_draft_mode_dialog_subtitle: "Вы не можете вернуться в режим черновика после того, как запланировали совещание." text_exit_draft_mode_dialog_template_title: "Открыть первое совещание из этой серии?" text_exit_draft_mode_dialog_template_subtitle: "После этого Вы не сможете вернуться в режим черновика." + label_meeting_template_sharing: "Совместное использование" + label_meeting_template_sharing_none: "Только этот проект" + label_meeting_template_sharing_descendants: "Подпроекты" + label_meeting_template_sharing_system: "Все проекты" + text_meeting_template_sharing_description: "Этот шаблон можно использовать совместно с подпроектами или другими проектами. Будут скопированы только пункты повестки дня и вложения." text_meeting_not_editable_anymore: "Это совещание больше нельзя редактировать." text_meeting_not_present_anymore: "Это совещание было удалено. Пожалуйста, выберите другое." label_add_work_package_to_meeting_dialog_title: "Выберите совещание" diff --git a/modules/meeting/config/locales/crowdin/rw.yml b/modules/meeting/config/locales/crowdin/rw.yml index d571d5898da..257865c0a66 100644 --- a/modules/meeting/config/locales/crowdin/rw.yml +++ b/modules/meeting/config/locales/crowdin/rw.yml @@ -42,6 +42,7 @@ rw: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -622,6 +623,11 @@ rw: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/si.yml b/modules/meeting/config/locales/crowdin/si.yml index 0a559a1c509..36d11e30472 100644 --- a/modules/meeting/config/locales/crowdin/si.yml +++ b/modules/meeting/config/locales/crowdin/si.yml @@ -42,6 +42,7 @@ si: start_date: "දිනය" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "මාතෘකාව" author: "කර්තෘ" @@ -622,6 +623,11 @@ si: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/sk.yml b/modules/meeting/config/locales/crowdin/sk.yml index 8627fc7c99a..25e8114fbb9 100644 --- a/modules/meeting/config/locales/crowdin/sk.yml +++ b/modules/meeting/config/locales/crowdin/sk.yml @@ -44,6 +44,7 @@ sk: start_date: "Dátum" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Nadpis" author: "Autor" @@ -636,6 +637,11 @@ sk: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/sl.yml b/modules/meeting/config/locales/crowdin/sl.yml index f1c13874f33..88cb32dc8a5 100644 --- a/modules/meeting/config/locales/crowdin/sl.yml +++ b/modules/meeting/config/locales/crowdin/sl.yml @@ -44,6 +44,7 @@ sl: start_date: "Datum" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Naslov" author: "Avtor" @@ -636,6 +637,11 @@ sl: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/sr.yml b/modules/meeting/config/locales/crowdin/sr.yml index cc001255629..86c904021be 100644 --- a/modules/meeting/config/locales/crowdin/sr.yml +++ b/modules/meeting/config/locales/crowdin/sr.yml @@ -43,6 +43,7 @@ sr: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -629,6 +630,11 @@ sr: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/sv.yml b/modules/meeting/config/locales/crowdin/sv.yml index 4ffa38c02b7..bb4ab06ea8b 100644 --- a/modules/meeting/config/locales/crowdin/sv.yml +++ b/modules/meeting/config/locales/crowdin/sv.yml @@ -42,6 +42,7 @@ sv: start_date: "Datum" start_time: "Starttid" start_time_hour: "Starttid" + sharing: "Sharing" meeting_agenda_item: title: "Titel" author: "Upphovsman" @@ -622,6 +623,11 @@ sv: text_exit_draft_mode_dialog_subtitle: "Du kan inte gå tillbaka till utkastet när du schemalägger ett möte." text_exit_draft_mode_dialog_template_title: "Öppna den första förekomsten av denna mötesserie?" text_exit_draft_mode_dialog_template_subtitle: "Du kan inte återgå till utkastläget efter detta." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Detta möte är inte längre redigerbart." text_meeting_not_present_anymore: "Detta möte har tagits bort. Vänligen välj ett annat möte." label_add_work_package_to_meeting_dialog_title: "Välj möte" diff --git a/modules/meeting/config/locales/crowdin/th.yml b/modules/meeting/config/locales/crowdin/th.yml index e6748e372cc..7e481e714e7 100644 --- a/modules/meeting/config/locales/crowdin/th.yml +++ b/modules/meeting/config/locales/crowdin/th.yml @@ -41,6 +41,7 @@ th: start_date: "วันที่" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "ชื่อเรื่อง" author: "ผู้เขียน" @@ -615,6 +616,11 @@ th: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/tr.yml b/modules/meeting/config/locales/crowdin/tr.yml index bc3c97dbf78..71a1418677a 100644 --- a/modules/meeting/config/locales/crowdin/tr.yml +++ b/modules/meeting/config/locales/crowdin/tr.yml @@ -42,6 +42,7 @@ tr: start_date: "Tarih" start_time: "Başlama tarihi" start_time_hour: "Başlama saati" + sharing: "Sharing" meeting_agenda_item: title: "Başlık" author: "Yazar" @@ -622,6 +623,11 @@ tr: text_exit_draft_mode_dialog_subtitle: "Bir toplantı planladıktan sonra taslak moduna geri dönemezsiniz." text_exit_draft_mode_dialog_template_title: "Bu toplantı serisinin ilkini açar mısınız?" text_exit_draft_mode_dialog_template_subtitle: "Bundan sonra taslak moduna geri dönemezsiniz." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Bu toplantıyı artık düzenleyemezsiniz." text_meeting_not_present_anymore: "Toplantı silinmiş. Başka bir toplantı seçin lütfen." label_add_work_package_to_meeting_dialog_title: "Toplantı seç" diff --git a/modules/meeting/config/locales/crowdin/uk.yml b/modules/meeting/config/locales/crowdin/uk.yml index fa8017c3968..3ab697eafbb 100644 --- a/modules/meeting/config/locales/crowdin/uk.yml +++ b/modules/meeting/config/locales/crowdin/uk.yml @@ -44,6 +44,7 @@ uk: start_date: "Дата" start_time: "Час початку" start_time_hour: "Час початку" + sharing: "Надання доступу" meeting_agenda_item: title: "Назва " author: "Автор" @@ -636,6 +637,11 @@ uk: text_exit_draft_mode_dialog_subtitle: "Запланувавши нараду, не можна повернутися в режим чернетки." text_exit_draft_mode_dialog_template_title: "Відкрити першу нараду цієї серії?" text_exit_draft_mode_dialog_template_subtitle: "Після цього не можна буде повернутися в режим чернетки." + label_meeting_template_sharing: "Надання доступу" + label_meeting_template_sharing_none: "Лише цей проєкт" + label_meeting_template_sharing_descendants: "Підпроєкти" + label_meeting_template_sharing_system: "Усі проєкти" + text_meeting_template_sharing_description: "Цим шаблоном можна поділитися з підпроєктами або іншими проєктами цього екземпляра. Буде скопійовано лише пункти порядку денного й вкладення." text_meeting_not_editable_anymore: "Цю нараду більше не можна редагувати." text_meeting_not_present_anymore: "Цю нараду видалено. Виберіть іншу нараду." label_add_work_package_to_meeting_dialog_title: "Вибрати нараду" diff --git a/modules/meeting/config/locales/crowdin/uz.yml b/modules/meeting/config/locales/crowdin/uz.yml index 4e1e7562442..274fb9613e5 100644 --- a/modules/meeting/config/locales/crowdin/uz.yml +++ b/modules/meeting/config/locales/crowdin/uz.yml @@ -42,6 +42,7 @@ uz: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -622,6 +623,11 @@ uz: text_exit_draft_mode_dialog_subtitle: "You cannot return to draft mode once you schedule a meeting." text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." label_add_work_package_to_meeting_dialog_title: "Select meeting" diff --git a/modules/meeting/config/locales/crowdin/vi.yml b/modules/meeting/config/locales/crowdin/vi.yml index f632f5f3599..19540bead09 100644 --- a/modules/meeting/config/locales/crowdin/vi.yml +++ b/modules/meeting/config/locales/crowdin/vi.yml @@ -41,6 +41,7 @@ vi: start_date: "ngày" start_time: "Thời gian bắt đầu" start_time_hour: "Thời gian bắt đầu" + sharing: "Sharing" meeting_agenda_item: title: "tiêu đề" author: "tác giả" @@ -615,6 +616,11 @@ vi: text_exit_draft_mode_dialog_subtitle: "Bạn không thể quay lại chế độ nháp sau khi lên lịch cuộc họp." text_exit_draft_mode_dialog_template_title: "Mở lần xuất hiện đầu tiên của chuỗi cuộc họp này?" text_exit_draft_mode_dialog_template_subtitle: "Bạn không thể quay lại chế độ nháp sau này." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "Cuộc họp này không thể chỉnh sửa được nữa." text_meeting_not_present_anymore: "Cuộc họp này đã bị xóa. Vui lòng chọn một cuộc họp khác." label_add_work_package_to_meeting_dialog_title: "Chọn cuộc họp" diff --git a/modules/meeting/config/locales/crowdin/zh-CN.yml b/modules/meeting/config/locales/crowdin/zh-CN.yml index 639016e481a..6ad4209c0e4 100644 --- a/modules/meeting/config/locales/crowdin/zh-CN.yml +++ b/modules/meeting/config/locales/crowdin/zh-CN.yml @@ -41,6 +41,7 @@ zh-CN: start_date: "日期" start_time: "开始时间" start_time_hour: "开始时间" + sharing: "共享中" meeting_agenda_item: title: "标题" author: "作者" @@ -615,6 +616,11 @@ zh-CN: text_exit_draft_mode_dialog_subtitle: "会议安排完成后,您将无法返回到草稿模式。" text_exit_draft_mode_dialog_template_title: "是否打开此会议系列的第一个会议?" text_exit_draft_mode_dialog_template_subtitle: "在此之后,您将无法返回到草稿模式。" + label_meeting_template_sharing: "共享中" + label_meeting_template_sharing_none: "仅此项目" + label_meeting_template_sharing_descendants: "子项目" + label_meeting_template_sharing_system: "所有项目" + text_meeting_template_sharing_description: "此模板可以与此实例中的子项目或其他项目共享。仅复制议程条目和附件。" text_meeting_not_editable_anymore: "本次会议已不可编辑。" text_meeting_not_present_anymore: "此次会议已被删除。请选择另一次会议。" label_add_work_package_to_meeting_dialog_title: "选择会议" diff --git a/modules/meeting/config/locales/crowdin/zh-TW.yml b/modules/meeting/config/locales/crowdin/zh-TW.yml index b26d49712c9..d922d064442 100644 --- a/modules/meeting/config/locales/crowdin/zh-TW.yml +++ b/modules/meeting/config/locales/crowdin/zh-TW.yml @@ -41,6 +41,7 @@ zh-TW: start_date: "日期" start_time: "開始時間" start_time_hour: "開始時間" + sharing: "Sharing" meeting_agenda_item: title: "標題" author: "會議發起者" @@ -615,6 +616,11 @@ zh-TW: text_exit_draft_mode_dialog_subtitle: "一旦排定會議,就無法返回草稿模式。" text_exit_draft_mode_dialog_template_title: "要開啟此會議系列的第一場會議嗎?" text_exit_draft_mode_dialog_template_subtitle: "之後您就無法回到草稿模式。" + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." text_meeting_not_editable_anymore: "本次會議已不可編輯。" text_meeting_not_present_anymore: "此次會議已被刪除。請選擇另一場會議。" label_add_work_package_to_meeting_dialog_title: "選擇會議" diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index ead5e53ff23..cb21d7eae97 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -53,6 +53,7 @@ en: start_date: "Date" start_time: "Start time" start_time_hour: "Start time" + sharing: "Sharing" meeting_agenda_item: title: "Title" author: "Author" @@ -693,6 +694,12 @@ en: text_exit_draft_mode_dialog_template_title: "Open the first occurrence of this meeting series?" text_exit_draft_mode_dialog_template_subtitle: "You cannot return to draft mode after this." + label_meeting_template_sharing: "Sharing" + label_meeting_template_sharing_none: "Only this project" + label_meeting_template_sharing_descendants: "Subprojects" + label_meeting_template_sharing_system: "All projects" + text_meeting_template_sharing_description: "This template can be shared with subprojects or other projects in this instance. Only the agenda items and attachments will be copied." + text_meeting_not_editable_anymore: "This meeting is not editable anymore." text_meeting_not_present_anymore: "This meeting was deleted. Please select another meeting." diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index 6679305e5f2..fecf73d4ee0 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -73,6 +73,7 @@ Rails.application.routes.draw do get :details_dialog put :update_details put :change_state + put :change_sharing post :notify get :history get :delete_dialog diff --git a/modules/meeting/db/migrate/20260302152255_add_sharing_to_meetings.rb b/modules/meeting/db/migrate/20260302152255_add_sharing_to_meetings.rb new file mode 100644 index 00000000000..96725388a20 --- /dev/null +++ b/modules/meeting/db/migrate/20260302152255_add_sharing_to_meetings.rb @@ -0,0 +1,35 @@ +# 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 AddSharingToMeetings < ActiveRecord::Migration[8.1] + def change + add_column :meetings, :sharing, :string, null: true, default: nil + end +end diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index ea092bf9af0..da1df3a357e 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -65,7 +65,7 @@ module OpenProject::Meeting contract_actions: { meetings: %i[create] } permission :edit_meetings, { - meetings: %i[edit cancel_edit update update_title change_state toggle_notifications_dialog + meetings: %i[edit cancel_edit update update_title change_state change_sharing toggle_notifications_dialog details_dialog update_details toggle_notifications exit_draft_mode_dialog exit_draft_mode], recurring_meetings: %i[edit cancel_edit update update_title details_dialog update_details notify end_series end_series_dialog], diff --git a/modules/meeting/spec/contracts/meetings/create_contract_spec.rb b/modules/meeting/spec/contracts/meetings/create_contract_spec.rb index be7a26635ae..4858a65b5ae 100644 --- a/modules/meeting/spec/contracts/meetings/create_contract_spec.rb +++ b/modules/meeting/spec/contracts/meetings/create_contract_spec.rb @@ -44,6 +44,18 @@ RSpec.describe Meetings::CreateContract do end it_behaves_like "contract is valid" + + context "when setting sharing on a non-template" do + let(:meeting) { build(:meeting, project:, sharing: :none) } + + it_behaves_like "contract is invalid", sharing: %i[present not_allowed] + end + + context "when setting sharing on a onetime template" do + let(:meeting) { build(:onetime_template, project:, sharing: :none) } + + it_behaves_like "contract is valid" + end end context "without permission" do diff --git a/modules/meeting/spec/contracts/meetings/update_contract_spec.rb b/modules/meeting/spec/contracts/meetings/update_contract_spec.rb index 29d95655bfd..c8aef61331d 100644 --- a/modules/meeting/spec/contracts/meetings/update_contract_spec.rb +++ b/modules/meeting/spec/contracts/meetings/update_contract_spec.rb @@ -45,6 +45,20 @@ RSpec.describe Meetings::UpdateContract do it_behaves_like "contract is valid" + context "when setting sharing on a non-template" do + let(:meeting) { create(:meeting, project:, sharing: nil) } + + before { meeting.sharing = "none" } + + it_behaves_like "contract is invalid", sharing: %i[present not_allowed] + end + + context "when setting sharing on a onetime template" do + let(:meeting) { create(:onetime_template, project:, sharing: :none) } + + it_behaves_like "contract is valid" + end + context "when lock_version is changed" do before do allow(meeting).to receive(:lock_version_changed?).and_return(true) diff --git a/modules/meeting/spec/factories/meeting_factory.rb b/modules/meeting/spec/factories/meeting_factory.rb index 1711cc37caa..9aabde5eda9 100644 --- a/modules/meeting/spec/factories/meeting_factory.rb +++ b/modules/meeting/spec/factories/meeting_factory.rb @@ -67,6 +67,7 @@ FactoryBot.define do meeting.sequence(:title) { |n| "Onetime template #{n}" } template { true } recurring_meeting { nil } + sharing { :none } end end end diff --git a/modules/meeting/spec/features/meeting_backlogs_spec.rb b/modules/meeting/spec/features/meeting_backlogs_spec.rb index 88e22a76eea..be53398bb00 100644 --- a/modules/meeting/spec/features/meeting_backlogs_spec.rb +++ b/modules/meeting/spec/features/meeting_backlogs_spec.rb @@ -247,14 +247,6 @@ RSpec.describe "Meeting Backlogs", :js do end describe "for meeting series" do - before_all do - travel_to(Date.new(2024, 12, 1)) - end - - after(:all) do # rubocop:disable RSpec/BeforeAfterAll - travel_back - end - shared_let(:recurring_meeting) do create :recurring_meeting, project:, @@ -276,6 +268,10 @@ RSpec.describe "Meeting Backlogs", :js do RecurringMeetings::InitNextOccurrenceJob.perform_now(recurring_meeting, next_occurrence_time) end + after do + travel_back + end + describe "backlog visibility" do context "when the meeting is 'open'" do it "is expanded" do diff --git a/modules/meeting/spec/features/meeting_templates/template_sharing_spec.rb b/modules/meeting/spec/features/meeting_templates/template_sharing_spec.rb new file mode 100644 index 00000000000..8442ed84632 --- /dev/null +++ b/modules/meeting/spec/features/meeting_templates/template_sharing_spec.rb @@ -0,0 +1,159 @@ +# 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. +#++ + +require "spec_helper" + +require_relative "../../support/pages/meetings/show" +require_relative "../../support/pages/meetings/index" + +RSpec.describe "Template sharing", :js do + shared_let(:project) { create(:project, enabled_module_names: %i[meetings]) } + shared_let(:template) { create(:onetime_template, project:, title: "My template", sharing: :none) } + + let(:show_page) { Pages::Meetings::Show.new(template) } + + context "as user with edit_meetings permission" do + shared_let(:editor) do + create(:user, member_with_permissions: { project => %i[view_meetings edit_meetings] }) + end + + before do + login_as editor + show_page.visit! + end + + it "shows the sharing section in the sidebar" do + expect(page).to have_text(I18n.t(:label_meeting_template_sharing)) + expect(page).to have_text(I18n.t(:label_meeting_template_sharing_none)) + end + + it "can change sharing to subprojects" do + within("#meetings-side-panel-sharing-component") do + click_button I18n.t(:label_meeting_template_sharing_none) + click_link_or_button I18n.t(:label_meeting_template_sharing_descendants) + end + + wait_for_network_idle + + expect(page).to have_text(I18n.t(:label_meeting_template_sharing_descendants)) + expect(template.reload.sharing).to eq("descendants") + end + + it "can change sharing to all projects" do + within("#meetings-side-panel-sharing-component") do + click_button I18n.t(:label_meeting_template_sharing_none) + click_link_or_button I18n.t(:label_meeting_template_sharing_system) + end + + wait_for_network_idle + + expect(page).to have_text(I18n.t(:label_meeting_template_sharing_system)) + expect(template.reload.sharing).to eq("system") + end + + it "can change sharing back to only this project" do + template.update!(sharing: :descendants) + show_page.visit! + + within("#meetings-side-panel-sharing-component") do + click_button I18n.t(:label_meeting_template_sharing_descendants) + click_link_or_button I18n.t(:label_meeting_template_sharing_none) + end + + wait_for_network_idle + + expect(page).to have_text(I18n.t(:label_meeting_template_sharing_none)) + expect(template.reload.sharing).to eq("none") + end + end + + context "as user with view_meetings only" do + shared_let(:viewer) do + create(:user, member_with_permissions: { project => %i[view_meetings] }) + end + + before do + login_as viewer + show_page.visit! + end + + it "does not show the sharing section" do + expect(page).to have_no_css("#meetings-side-panel-sharing-component") + end + end + + # Happy path + context "when sharing level is changed", with_ee: [:meeting_templates] do + shared_let(:child_project) { create(:project, enabled_module_names: %i[meetings], parent: project) } + shared_let(:user) do + create(:user, member_with_permissions: { + project => %i[view_meetings edit_meetings], + child_project => %i[view_meetings create_meetings] + }) + end + + let(:child_meetings_page) { Pages::Meetings::Index.new(project: child_project) } + + before { login_as user } + + it "toggles visibility in child project's new meeting form" do + # Template not visible before sharing change + child_meetings_page.visit! + child_meetings_page.click_on "add-meeting-button" + child_meetings_page.click_on "One-time" + + within_dialog "New one-time meeting" do + expect(page).to have_no_css('[data-test-selector="template_id"]') + end + + # Change sharing to descendants + visit project_meeting_path(project, template) + + within("#meetings-side-panel-sharing-component") do + click_button I18n.t(:label_meeting_template_sharing_none) + click_link_or_button I18n.t(:label_meeting_template_sharing_descendants) + end + + wait_for_network_idle + + expect(template.reload.sharing).to eq("descendants") + + # Template is now visible in child project form + child_meetings_page.visit! + child_meetings_page.click_on "add-meeting-button" + child_meetings_page.click_on "One-time" + + within_dialog "New one-time meeting" do + find('[data-test-selector="template_id"]').click + expect(page).to have_text(template.title) + end + end + end +end diff --git a/modules/meeting/spec/features/meetings_index_spec.rb b/modules/meeting/spec/features/meetings_index_spec.rb index fae6aaaa574..88c1b3d81a6 100644 --- a/modules/meeting/spec/features/meetings_index_spec.rb +++ b/modules/meeting/spec/features/meetings_index_spec.rb @@ -32,6 +32,12 @@ require "spec_helper" require_relative "../support/pages/meetings/index" RSpec.describe "Meetings", "Index", :js do + shared_let(:business_day_at_noon) { Time.zone.local(2025, 1, 8, 12, 0, 0) } + + after do + travel_back + end + # The order the Projects are created in is important. By naming `project` alphanumerically # after `other_project`, we can ensure that subsequent specs that assert sorting is # correct for the right reasons (sorting by Project name and not id) @@ -55,14 +61,14 @@ RSpec.describe "Meetings", "Index", :js do :author_participates, project:, title: "Awesome meeting today!", - start_time: Time.current) + start_time: business_day_at_noon - 5.minutes) end shared_let(:tomorrows_meeting) do create(:meeting, :author_participates, project:, title: "Awesome meeting tomorrow!", - start_time: 1.day.from_now, + start_time: business_day_at_noon + 1.day, duration: 2.0, location: "no-protocol.com") end @@ -71,7 +77,7 @@ RSpec.describe "Meetings", "Index", :js do :author_participates, project:, title: "Boring meeting without a location!", - start_time: 1.day.from_now, + start_time: business_day_at_noon + 1.day + 5.minutes, location: "") end shared_let(:meeting_with_malicious_location) do @@ -79,7 +85,7 @@ RSpec.describe "Meetings", "Index", :js do :author_participates, project:, title: "Sneaky meeting!", - start_time: 1.day.from_now, + start_time: business_day_at_noon + 1.day + 10.minutes, location: "") end shared_let(:yesterdays_meeting) do @@ -87,7 +93,7 @@ RSpec.describe "Meetings", "Index", :js do :author_participates, project:, title: "Awesome meeting yesterday!", - start_time: 1.day.ago) + start_time: business_day_at_noon - 1.day) end shared_let(:other_project_meeting) do @@ -95,7 +101,7 @@ RSpec.describe "Meetings", "Index", :js do :author_participates, project: other_project, title: "Awesome other project meeting!", - start_time: 2.days.from_now, + start_time: business_day_at_noon + 2.days, duration: 2.0, location: "not-a-url") end @@ -104,7 +110,7 @@ RSpec.describe "Meetings", "Index", :js do :author_participates, project:, title: "Awesome ongoing meeting!", - start_time: 30.minutes.ago) + start_time: business_day_at_noon - 30.minutes) end def setup_meeting_involvement @@ -120,6 +126,7 @@ RSpec.describe "Meetings", "Index", :js do end before do + travel_to(business_day_at_noon) login_as user end diff --git a/modules/meeting/spec/features/move_to_section_spec.rb b/modules/meeting/spec/features/move_to_section_spec.rb index df2317e5133..64690b0f88a 100644 --- a/modules/meeting/spec/features/move_to_section_spec.rb +++ b/modules/meeting/spec/features/move_to_section_spec.rb @@ -52,6 +52,10 @@ RSpec.describe "Move agenda items to section", :js do login_as current_user end + after do + travel_back + end + describe "for one-time meetings" do shared_let(:meeting) do create :meeting, diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_create_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_create_spec.rb index 81287ee636c..e91d16f84e6 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_create_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_create_spec.rb @@ -74,6 +74,10 @@ RSpec.describe "Recurring meetings creation", travel_to(Date.new(2024, 12, 1)) end + after do + travel_back + end + context "with a user with permissions" do it "can create a recurring meeting" do login_as current_user diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb index a95a70bc171..b91732cfafb 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_crud_spec.rb @@ -38,14 +38,6 @@ RSpec.describe "Recurring meetings CRUD", :js do include Components::Autocompleter::NgSelectAutocompleteHelpers - before_all do - travel_to(Date.new(2024, 12, 1)) - end - - after(:all) do # rubocop:disable RSpec/BeforeAfterAll - travel_back - end - shared_let(:project) { create(:project, enabled_module_names: %w[meetings]) } shared_let(:user) do create :user, @@ -85,6 +77,10 @@ RSpec.describe "Recurring meetings CRUD", RecurringMeetings::InitNextOccurrenceJob.perform_now(meeting, meeting.first_occurrence.to_time) end + after do + travel_back + end + it "can delete a recurring meeting from the show page and return to the index page" do show_page.visit! diff --git a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb index 6f03deb0517..010ae29a69b 100644 --- a/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb +++ b/modules/meeting/spec/features/recurring_meetings/recurring_meeting_global_crud_spec.rb @@ -37,14 +37,6 @@ require_relative "../../support/pages/meetings/index" RSpec.describe "Recurring meetings global CRUD", :js do include Components::Autocompleter::NgSelectAutocompleteHelpers - before_all do - travel_to(Date.new(2024, 12, 1)) - end - - after(:all) do # rubocop:disable RSpec/BeforeAfterAll - travel_back - end - shared_let(:project) { create(:project, enabled_module_names: %w[meetings]) } shared_let(:user) do create :user, @@ -84,6 +76,10 @@ RSpec.describe "Recurring meetings global CRUD", :js do RecurringMeetings::InitNextOccurrenceJob.perform_now(meeting, meeting.first_occurrence.to_time) end + after do + travel_back + end + it "can delete a recurring meeting from the show page and return to the index page" do show_page.visit! diff --git a/modules/meeting/spec/features/structured_meetings/structured_meeting_update_flash_spec.rb b/modules/meeting/spec/features/structured_meetings/structured_meeting_update_flash_spec.rb index 5bb68fe2392..77eb0ccf0f8 100644 --- a/modules/meeting/spec/features/structured_meetings/structured_meeting_update_flash_spec.rb +++ b/modules/meeting/spec/features/structured_meetings/structured_meeting_update_flash_spec.rb @@ -193,14 +193,6 @@ RSpec.describe "Meetings CRUD", end context "for meeting series, across the same occurrence" do - before_all do - travel_to(Date.new(2024, 12, 1)) - end - - after(:all) do # rubocop:disable RSpec/BeforeAfterAll - travel_back - end - let(:recurring_meeting) do create :recurring_meeting, project:, @@ -222,6 +214,10 @@ RSpec.describe "Meetings CRUD", RecurringMeetings::InitNextOccurrenceJob.perform_now(recurring_meeting, first_occurrence_time) end + after do + travel_back + end + it_behaves_like "no flash appears when interacting with backlog in multiple windows" end diff --git a/modules/meeting/spec/models/meeting_spec.rb b/modules/meeting/spec/models/meeting_spec.rb index 50e3395aff5..2e3b34d9222 100644 --- a/modules/meeting/spec/models/meeting_spec.rb +++ b/modules/meeting/spec/models/meeting_spec.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -189,4 +190,84 @@ RSpec.describe Meeting do end end end + + describe ".templates_visible_in_project" do + shared_let(:ancestor_project) { create(:project) } + shared_let(:current_project) { create(:project, parent: ancestor_project) } + shared_let(:descendant_project) { create(:project, parent: current_project) } + shared_let(:unrelated_project) { create(:project) } + + shared_let(:user) { create(:user, member_with_permissions: { current_project => [:view_meetings] }) } + + subject { described_class.templates_visible_in_project(current_project, user) } + + context "with templates in the same project" do + shared_let(:template_none) { create(:onetime_template, project: current_project, sharing: :none) } + shared_let(:template_descendants) { create(:onetime_template, project: current_project, sharing: :descendants) } + shared_let(:template_system) { create(:onetime_template, project: current_project, sharing: :system) } + + it { expect(subject).to include(template_none) } + it { expect(subject).to include(template_descendants) } + it { expect(subject).to include(template_system) } + end + + context "with templates in an unrelated project" do + shared_let(:template_none) { create(:onetime_template, project: unrelated_project, sharing: :none) } + shared_let(:template_descendants) { create(:onetime_template, project: unrelated_project, sharing: :descendants) } + shared_let(:template_system) { create(:onetime_template, project: unrelated_project, sharing: :system) } + + it { expect(subject).not_to include(template_none) } + it { expect(subject).not_to include(template_descendants) } + it { expect(subject).to include(template_system) } + end + + context "with templates in a descendant project" do + shared_let(:template_none) { create(:onetime_template, project: descendant_project, sharing: :none) } + shared_let(:template_descendants) { create(:onetime_template, project: descendant_project, sharing: :descendants) } + shared_let(:template_system) { create(:onetime_template, project: descendant_project, sharing: :system) } + + it { expect(subject).not_to include(template_none) } + it { expect(subject).not_to include(template_descendants) } + it { expect(subject).to include(template_system) } + end + + context "with templates in an ancestor project" do + shared_let(:template_none) { create(:onetime_template, project: ancestor_project, sharing: :none) } + shared_let(:template_descendants) { create(:onetime_template, project: ancestor_project, sharing: :descendants) } + shared_let(:template_system) { create(:onetime_template, project: ancestor_project, sharing: :system) } + + it { expect(subject).not_to include(template_none) } + it { expect(subject).to include(template_descendants) } + it { expect(subject).to include(template_system) } + end + end + + describe "sharing" do + context "for a regular meeting" do + let(:meeting) { build(:meeting, project:, sharing: :none) } + + it "is invalid" do + expect(meeting).not_to be_valid + expect(meeting.errors[:sharing]).to be_present + end + end + + context "for a series template" do + let(:recurring) { create(:recurring_meeting, project:) } + let(:meeting) { build(:meeting_template, sharing: :none, recurring_meeting: recurring) } + + it "is invalid" do + expect(meeting).not_to be_valid + expect(meeting.errors[:sharing]).to be_present + end + end + + context "for an onetime template" do + let(:meeting) { build(:onetime_template, project:, sharing: :none) } + + it "is valid" do + expect(meeting).to be_valid + end + end + end end diff --git a/modules/meeting/spec/requests/meeting_templates_spec.rb b/modules/meeting/spec/requests/meeting_templates_spec.rb index 10bbb49baed..0c0c9b1cc75 100644 --- a/modules/meeting/spec/requests/meeting_templates_spec.rb +++ b/modules/meeting/spec/requests/meeting_templates_spec.rb @@ -204,4 +204,74 @@ RSpec.describe "Meeting templates requests", end end end + + describe "GET /projects/:id/meetings/templates (project templates index page)" do + shared_let(:ancestor_project) { create(:project, enabled_module_names: %i[meetings]) } + shared_let(:current_project) { create(:project, enabled_module_names: %i[meetings], parent: ancestor_project) } + shared_let(:descendant_project) { create(:project, enabled_module_names: %i[meetings], parent: current_project) } + shared_let(:unrelated_project) { create(:project, enabled_module_names: %i[meetings]) } + + shared_let(:user) do + create(:user, member_with_permissions: { current_project => %i[view_meetings] }) + end + + shared_let(:own_template) { create(:onetime_template, project: current_project, title: "Own template") } + shared_let(:ancestor_none_template) do + create(:onetime_template, project: ancestor_project, sharing: :none, title: "Ancestor none") + end + shared_let(:ancestor_descendants_template) do + create(:onetime_template, project: ancestor_project, sharing: :descendants, title: "Ancestor descendants") + end + shared_let(:descendant_template) do + create(:onetime_template, project: descendant_project, sharing: :descendants, title: "Descendant template") + end + shared_let(:system_template) do + create(:onetime_template, project: unrelated_project, sharing: :system, title: "System template") + end + + before { login_as user } + + it "shows only templates belonging to the project, regardless of sharing level" do + get templates_project_meetings_path(current_project) + + expect(response.body).to include("Own template") + + expect(response.body).not_to include("Ancestor none") + expect(response.body).not_to include("Ancestor descendants") + expect(response.body).not_to include("Descendant template") + expect(response.body).not_to include("System template") + end + end + + describe "GET /meetings/templates (global templates index page)" do + shared_let(:accessible_project) { create(:project, enabled_module_names: %i[meetings]) } + shared_let(:inaccessible_project) { create(:project, enabled_module_names: %i[meetings]) } + + shared_let(:user) do + create(:user, member_with_permissions: { accessible_project => %i[view_meetings] }) + end + + shared_let(:own_template) { create(:onetime_template, project: accessible_project, title: "Own template") } + shared_let(:inaccessible_none_template) do + create(:onetime_template, project: inaccessible_project, sharing: :none, title: "Inaccessible none") + end + shared_let(:inaccessible_descendants_template) do + create(:onetime_template, project: inaccessible_project, sharing: :descendants, title: "Inaccessible descendants") + end + shared_let(:system_template) do + create(:onetime_template, project: inaccessible_project, sharing: :system, title: "System template") + end + + before { login_as user } + + it "shows system templates and templates from accessible projects" do + get templates_meetings_path + + expect(response.body).to include("Own template") + expect(response.body).to include("System template") + + expect(response.body).not_to include("Inaccessible none") + expect(response.body).not_to include("Inaccessible descendants") + end + end end diff --git a/modules/meeting/spec/requests/meetings_spec.rb b/modules/meeting/spec/requests/meetings_spec.rb index 86c060c4946..38b95110020 100644 --- a/modules/meeting/spec/requests/meetings_spec.rb +++ b/modules/meeting/spec/requests/meetings_spec.rb @@ -201,4 +201,50 @@ RSpec.describe "Meeting requests", end end end + + describe "GET new_dialog - template selector visibility", with_ee: [:meeting_templates] do + shared_let(:ancestor_project) { create(:project, enabled_module_names: %i[meetings]) } + shared_let(:current_project) { create(:project, enabled_module_names: %i[meetings], parent: ancestor_project) } + shared_let(:descendant_project) { create(:project, enabled_module_names: %i[meetings], parent: current_project) } + shared_let(:unrelated_project) { create(:project, enabled_module_names: %i[meetings]) } + + shared_let(:user) do + create(:user, member_with_permissions: { current_project => %i[view_meetings create_meetings] }) + end + + shared_let(:own_template) { create(:onetime_template, project: current_project, title: "Own template") } + shared_let(:ancestor_none_template) do + create(:onetime_template, project: ancestor_project, sharing: :none, title: "Ancestor none") + end + shared_let(:ancestor_descendants_template) do + create(:onetime_template, project: ancestor_project, sharing: :descendants, title: "Ancestor descendants") + end + shared_let(:descendant_none_template) do + create(:onetime_template, project: descendant_project, sharing: :none, title: "Descendant none") + end + shared_let(:descendant_descendants_template) do + create(:onetime_template, project: descendant_project, sharing: :descendants, title: "Descendant descendants") + end + shared_let(:unrelated_none_template) do + create(:onetime_template, project: unrelated_project, sharing: :none, title: "Unrelated none") + end + shared_let(:system_template) do + create(:onetime_template, project: unrelated_project, sharing: :system, title: "System template") + end + + before { login_as user } + + it "shows own and those shared with descendants and all projects" do + get new_dialog_project_meetings_path(current_project), as: :turbo_stream + + expect(response.body).to include("Own template") + expect(response.body).to include("Ancestor descendants") + expect(response.body).to include("System template") + + expect(response.body).not_to include("Ancestor none") + expect(response.body).not_to include("Descendant none") + expect(response.body).not_to include("Descendant descendants") + expect(response.body).not_to include("Unrelated none") + end + end end diff --git a/modules/meeting/spec/services/meetings/create_service_integration_spec.rb b/modules/meeting/spec/services/meetings/create_service_integration_spec.rb index 26dc2e991c4..508e3b60954 100644 --- a/modules/meeting/spec/services/meetings/create_service_integration_spec.rb +++ b/modules/meeting/spec/services/meetings/create_service_integration_spec.rb @@ -109,4 +109,22 @@ RSpec.describe Meetings::CreateService, "integration", type: :model do end end end + + describe "sharing" do + context "when creating a regular meeting" do + it "does not set sharing" do + expect(subject).to be_success + expect(subject.result.sharing).to be_nil + end + end + + context "when creating an onetime template" do + let(:default_params) { { project:, title: "My template", template: true } } + + it "sets sharing to none by default" do + expect(subject).to be_success + expect(subject.result.sharing).to eq("none") + end + end + end end diff --git a/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb index 0246817d18b..c22de3556b2 100644 --- a/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb +++ b/modules/openid_connect/app/components/openid_connect/providers/side_panel/information_component.html.erb @@ -9,7 +9,7 @@ end collection.with_component( - OpPrimer::CopyToClipboardComponent.new(provider.slug, scheme: :input) + OpPrimer::CopyToClipboardComponent.new(provider.slug, scheme: :value) ) collection.with_component(Primer::Beta::Heading.new(tag: :h5, mt: 4, mb: 1)) do @@ -17,7 +17,7 @@ end collection.with_component( - OpPrimer::CopyToClipboardComponent.new(provider.callback_url, scheme: :input) + OpPrimer::CopyToClipboardComponent.new(provider.callback_url, scheme: :value) ) collection.with_component(Primer::Beta::Heading.new(tag: :h5, mt: 4, mb: 1)) do @@ -25,7 +25,7 @@ end collection.with_component( - OpPrimer::CopyToClipboardComponent.new(provider.backchannel_logout_url, scheme: :input) + OpPrimer::CopyToClipboardComponent.new(provider.backchannel_logout_url, scheme: :value) ) end end diff --git a/modules/openid_connect/app/services/openid_connect/user_tokens/token_request.rb b/modules/openid_connect/app/services/openid_connect/user_tokens/token_request.rb index f774185c546..3d43de64ec9 100644 --- a/modules/openid_connect/app/services/openid_connect/user_tokens/token_request.rb +++ b/modules/openid_connect/app/services/openid_connect/user_tokens/token_request.rb @@ -78,7 +78,7 @@ module OpenIDConnect # Client ID and Client Secret must be form-encoded. Otherwise characters such as colon (:) # would not be allowed in the Client ID, since HTTP Basic Auth does not support it # as per https://datatracker.ietf.org/doc/html/rfc7617#section-2 - OpenProject.httpx.basic_auth(CGI.escape(provider.client_id), CGI.escape(provider.client_secret)) + OpenProject.httpx.plugin(:basic_auth).basic_auth(CGI.escape(provider.client_id), CGI.escape(provider.client_secret)) end end end diff --git a/modules/overviews/spec/migrations/rename_manage_overview_to_manage_dashboards_spec.rb b/modules/overviews/spec/migrations/rename_manage_overview_to_manage_dashboards_spec.rb new file mode 100644 index 00000000000..2aff50081ef --- /dev/null +++ b/modules/overviews/spec/migrations/rename_manage_overview_to_manage_dashboards_spec.rb @@ -0,0 +1,67 @@ +# 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. +#++ + +require "spec_helper" +require Rails.root.join("modules/overviews/db/migrate/20250910085916_rename_manage_overview_to_manage_dashboards") + +RSpec.describe RenameManageOverviewToManageDashboards, type: :model do + subject(:migrate) { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } } + + let!(:empty_role) { create(:project_role) } + let!(:role_with_permission) do + create(:project_role, permissions: %i[manage_overview], add_public_permissions: false) + end + + describe "migrating up" do + it "does not add permissions to a role without manage_overview" do + expect { migrate }.not_to change { empty_role.reload.permissions } + end + + it "renames manage_overview to manage_dashboards" do + expect { migrate } + .to change { role_with_permission.reload.permissions } + .from(match_array(%i[manage_overview])) + .to(match_array(%i[manage_dashboards])) + end + end + + describe "migrating down" do + subject(:rollback) { ActiveRecord::Migration.suppress_messages { described_class.migrate(:down) } } + + before { migrate } + + it "reverts manage_dashboards to manage_overview" do + expect { rollback } + .to change { role_with_permission.reload.permissions } + .from(match_array(%i[manage_dashboards])) + .to(match_array(%i[manage_overview])) + end + end +end diff --git a/modules/reporting/config/locales/crowdin/ro.yml b/modules/reporting/config/locales/crowdin/ro.yml index c113cc4a5d4..ddd90658fff 100644 --- a/modules/reporting/config/locales/crowdin/ro.yml +++ b/modules/reporting/config/locales/crowdin/ro.yml @@ -70,7 +70,7 @@ ro: label_filter: "Filtrează" label_filter_add: "Adaugă filtru" label_filter_plural: "Filtre" - label_group_by: "Grupează după" + label_group_by: "Grupare după" label_group_by_add: "Adaugă atributul Grupează-după" label_inactive: "Inactiv" label_no: "Nu" diff --git a/modules/reporting/config/locales/crowdin/vi.yml b/modules/reporting/config/locales/crowdin/vi.yml index 0b864f3df4a..43bd0a017d5 100644 --- a/modules/reporting/config/locales/crowdin/vi.yml +++ b/modules/reporting/config/locales/crowdin/vi.yml @@ -73,7 +73,7 @@ vi: label_group_by: "Nhóm theo" label_group_by_add: "Thêm thuộc tính theo nhóm" label_inactive: "«không hoạt động»" - label_no: "không" + label_no: "Không" label_none: "(không có dữ liệu)" label_no_reports: "Chưa có báo cáo chi phí." label_report: "Báo cáo" diff --git a/modules/reporting/config/locales/crowdin/zh-TW.yml b/modules/reporting/config/locales/crowdin/zh-TW.yml index 6a0980310d3..014919fb7f9 100644 --- a/modules/reporting/config/locales/crowdin/zh-TW.yml +++ b/modules/reporting/config/locales/crowdin/zh-TW.yml @@ -53,7 +53,7 @@ zh-TW: label_money: "金額" label_month_reporting: "月" label_new_report: "新建成本報表" - label_open: "開啟" + label_open: "開啟中" label_operator: "操作員" label_private_report_plural: "私密成本報告" label_progress_bar_explanation: "產生報告中..." @@ -70,7 +70,7 @@ zh-TW: label_filter: "篩選條件" label_filter_add: "新增篩選條件" label_filter_plural: "篩選條件" - label_group_by: "分類" + label_group_by: "分組依據" label_group_by_add: "新增群組欄位" label_inactive: "«不活動»" label_no: "否" diff --git a/modules/storages/app/common/storages/adapters/authentication_strategies/basic_auth.rb b/modules/storages/app/common/storages/adapters/authentication_strategies/basic_auth.rb index cea3bb61c9a..7a47f8684bd 100644 --- a/modules/storages/app/common/storages/adapters/authentication_strategies/basic_auth.rb +++ b/modules/storages/app/common/storages/adapters/authentication_strategies/basic_auth.rb @@ -40,7 +40,7 @@ module Storages return build_failure(storage) if username.blank? || password.blank? - yield OpenProject.httpx.basic_auth(username, password).with(http_options) + yield OpenProject.httpx.plugin(:basic_auth).basic_auth(username, password).with(http_options) end private diff --git a/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_client_credentials.rb b/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_client_credentials.rb index ac771777fdd..083c36ee3e0 100644 --- a/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_client_credentials.rb +++ b/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_client_credentials.rb @@ -39,21 +39,21 @@ module Storages @use_cache = use_cache end - def call(storage:, http_options: {}) + def call(storage:, http_options: {}) # rubocop:disable Metrics/AbcSize config = validate_configuration(storage).value_or { return Failure(it) } token_cache_key = TOKEN_CACHE_KEY % storage.id access_token = @use_cache ? Rails.cache.read(token_cache_key) : nil - http = build_http_session(access_token, config, http_options).value_or { return Failure(it) } + session = build_http_session(access_token, config, http_options).value_or { Failure(it) } - operation_result = yield http + operation_result = yield session return operation_result unless @use_cache case operation_result in Success if @use_cache && access_token.blank? - write_cache(token_cache_key, http) + write_cache(token_cache_key, session) in Failure(code: :forbidden) clear_cache(token_cache_key) else @@ -61,6 +61,18 @@ module Storages end operation_result + # HTTPX default behaviour is to return error responses and not raise errors unless + # explicitly asked by using the `#raise_for_status`method. + # + # On Storages codebase we handle the error responses, but the OAuth + # plugin raises and exception when it fails to get a Token.. + # The handling below will only apply to authentication errors. + rescue HTTPX::HTTPError => e + error("Error while refreshing OAuth token - Payload: #{e.response}") + + Failure(Results::Error.new(code: :unauthorized, payload: e.response, source: self.class)) + rescue HTTPX::TimeoutError => e + Failure(Results::Error.new(code: :timeout, payload: e.to_s, source: self.class)) end private @@ -73,35 +85,15 @@ module Storages end def write_cache(key, httpx_session) - access_token = httpx_session.instance_variable_get(:@options).oauth_session.access_token + access_token = httpx_session.send(:oauth_session).access_token Rails.cache.write(key, access_token, expires_in: 50.minutes) end def clear_cache(key) = Rails.cache.delete(key) def build_http_session(access_token, config, http_options) - if access_token.present? - http_with_current_token(access_token:, http_options:) - else - http_with_new_token(config:, http_options:) - end - end - - def http_with_current_token(access_token:, http_options:) - opts = http_options.deep_merge({ headers: { "Authorization" => "Bearer #{access_token}" } }) - Success(OpenProject.httpx.with(opts)) - end - - def http_with_new_token(config:, http_options:) - http = OpenProject.httpx - .oauth_auth(**config.to_h, token_endpoint_auth_method: "client_secret_post") - .with_access_token - .with(http_options) - Success(http) - rescue HTTPX::HTTPError => e - Failure(Results::Error.new(code: :unauthorized, payload: e.response, source: self.class)) - rescue HTTPX::TimeoutError => e - Failure(Results::Error.new(code: :timeout, payload: e.to_s, source: self.class)) + Success(OpenProject.httpx.plugin(:oauth) + .with(**http_options, oauth_options: { **config, access_token: access_token })) end end end diff --git a/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_configuration.rb b/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_configuration.rb index 3eb74f53a28..cddf79f3077 100644 --- a/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_configuration.rb +++ b/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_configuration.rb @@ -49,6 +49,7 @@ module Storages def to_h { issuer:, client_id:, client_secret:, scope: } end + alias_method :to_hash, :to_h end end end diff --git a/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_user_token.rb b/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_user_token.rb index 650f4951fc6..036841b5fe7 100644 --- a/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_user_token.rb +++ b/modules/storages/app/common/storages/adapters/authentication_strategies/oauth_user_token.rb @@ -39,22 +39,15 @@ module Storages @error_data = Results::Error.new(source: self.class, code: :error) end - # rubocop:disable Metrics/AbcSize def call(storage:, http_options: {}, &) oauth_client = validate_oauth_client(storage).value_or { return Failure(it) } token = current_token(oauth_client).value_or { return Failure(it) } - original_response = yield(httpx_with_auth(token.access_token, http_options)) - - case original_response - in Failure(code: :unauthorized) - updated_token = refresh_token!(storage.oauth_configuration.to_httpx_oauth_config.to_h, - http_options, - token).value_or { return Failure(it) } - yield(httpx_with_auth(updated_token.access_token, http_options)) - else - original_response - end + perform_request( + httpx_oauth_session(storage.oauth_configuration.to_httpx_oauth_config, token, http_options), + token, + & + ) rescue ActiveRecord::StaleObjectError => e raise e if @retried @@ -62,14 +55,33 @@ module Storages @retried = true retry end - # rubocop:enable Metrics/AbcSize private - def httpx_with_auth(access_token, http_options) - OpenProject - .httpx - .with(http_options.deep_merge(headers: { "Authorization" => "Bearer #{access_token}" })) + def perform_request(session, token, &) + response = yield(session) + + response.bind { update_token(session, token) } + + response + rescue HTTPX::TimeoutError => e + handle_timeout(token, e) + rescue HTTPX::Error => e + handle_http_error(token, e) + end + + def update_token(session, token) + oauth_session = session.send(:oauth_session) + token.update!(access_token: oauth_session.access_token, refresh_token: oauth_session.refresh_token) + end + + def httpx_oauth_session(oauth_config, token, http_options) + OpenProject.httpx + .plugin(:retries) + .plugin(:oauth) + .with(**http_options, + oauth_options: { **oauth_config, + access_token: token.access_token, refresh_token: token.refresh_token }) end def validate_oauth_client(storage) @@ -78,34 +90,16 @@ module Storages Failure(@error_data.with(code: :missing_oauth_client, payload: storage)) end - def refresh_token!(oauth_config, http_options, token) - oauth_session = OpenProject - .httpx - .oauth_auth(**oauth_config, - refresh_token: token.refresh_token, - token_endpoint_auth_method: "client_secret_post") - .with(http_options) - .with_access_token - .instance_variable_get(:@options) - .oauth_session - token.update!(access_token: oauth_session.access_token, refresh_token: oauth_session.refresh_token) - Success(token) - rescue HTTPX::HTTPError => e - handle_http_error(token, e) - rescue HTTPX::TimeoutError => e - handle_timeout(token, e) - end - def handle_timeout(token, exception) - Rails.logger.error("Timeout while refreshing OAuth token. - Payload: #{exception.message}") - token.destroy + error("Timeout while refreshing OAuth token. - Payload: #{exception.message}") + token.destroy! Failure(@error_data.with(error: :timeout_on_refresh, payload: exception)) end - def handle_http_error(token, error) - Rails.logger.error("Error while refreshing OAuth token - Payload: #{error.response}") - token.destroy - Failure(@error_data.with(code: :unauthorized, payload: error.response)) + def handle_http_error(token, exception) + error("Error while refreshing OAuth token - Payload: #{exception.response}") + token.destroy! + Failure(@error_data.with(code: :unauthorized, payload: exception.response)) end def current_token(client) diff --git a/modules/storages/app/common/storages/adapters/providers/nextcloud/queries/download_link_query.rb b/modules/storages/app/common/storages/adapters/providers/nextcloud/queries/download_link_query.rb index 5c5d34ad56b..7ee86871c57 100644 --- a/modules/storages/app/common/storages/adapters/providers/nextcloud/queries/download_link_query.rb +++ b/modules/storages/app/common/storages/adapters/providers/nextcloud/queries/download_link_query.rb @@ -35,11 +35,9 @@ module Storages module Queries class DownloadLinkQuery < Base def call(auth_strategy:, input_data:) - Authentication[auth_strategy].call(storage: @storage, http_options:) do |http| - fetch_origin_name(input_data, auth_strategy).bind do |origin_name| - fetch_download_token(auth_strategy, input_data.file_id).fmap do |token| - URI(download_link(token, origin_name)) - end + fetch_origin_name(input_data, auth_strategy).bind do |origin_name| + fetch_download_token(auth_strategy, input_data.file_id).fmap do |token| + URI(download_link(token, origin_name)) end end end @@ -55,11 +53,7 @@ module Storages case response in { status: 200..299 } - if response.body.blank? - Failure(error.with(code: :unauthorized)) - else - build_download_link(response, error) - end + build_download_link(response, error) in { status: 404 } Failure(error.with(code: :not_found)) in { status: 401 } @@ -70,14 +64,12 @@ module Storages end def fetch_origin_name(input_data, auth_strategy) - FileInfoQuery.call(storage: @storage, auth_strategy:, input_data:) - .bind do |file_info| - file_name = file_info.name - return Success(file_name) if file_name.present? + FileInfoQuery.call(storage: @storage, auth_strategy:, input_data:).bind do |file_info| + file_name = file_info.name + return Success(file_name) if file_name.present? - error = Results::Error.new(source: self.class, payload: file_info) - Failure(error.with(code: :not_found)) - end + Failure(Results::Error.new(source: self.class, payload: file_info, code: :not_found)) + end end def fetch_download_token(auth_strategy, file_id) @@ -100,6 +92,8 @@ module Storages return parsing_error if token.blank? Success(token) + rescue HTTPX::Error + parsing_error end def download_link(token, origin_name) diff --git a/modules/storages/app/validator/nextcloud_application_credentials_validator.rb b/modules/storages/app/validator/nextcloud_application_credentials_validator.rb index 794a10937b6..8aa7e1e5ed9 100644 --- a/modules/storages/app/validator/nextcloud_application_credentials_validator.rb +++ b/modules/storages/app/validator/nextcloud_application_credentials_validator.rb @@ -40,6 +40,7 @@ class NextcloudApplicationCredentialsValidator response = OpenProject .httpx + .plugin(:basic_auth) .basic_auth(contract.username, contract.password) .head(Storages::UrlBuilder.url(contract.model.uri, "remote.php/dav")) case response diff --git a/modules/storages/config/locales/crowdin/ja.yml b/modules/storages/config/locales/crowdin/ja.yml index 86bc0b213bc..4708d197140 100644 --- a/modules/storages/config/locales/crowdin/ja.yml +++ b/modules/storages/config/locales/crowdin/ja.yml @@ -18,7 +18,7 @@ ja: token_exchange_scope: ストレージスコープ storages/project_storage: project_folder: プロジェクトフォルダ - project_folder_mode: プロジェクトフォルダーモード + project_folder_mode: プロジェクトフォルダモード storage: ストレージ storage_url: ストレージURL storages/sharepoint_storage: @@ -29,53 +29,53 @@ ja: storages/storage: authentication_method: 認証方法 creator: 作成者 - drive: ドライブID + drive: ドライブ ID host: ホスト name: 名称 password: アプリケーションのパスワード - provider_type: プロバイダー・タイプ - tenant: ディレクトリ(テナント)ID + provider_type: プロバイダーの種類 + tenant: ディレクトリ (テナント) ID errors: messages: invalid_host_url: は有効な URL ではありません。 - invalid_sharepoint_url: は有効なSharePointサイト、ライブラリ、ドキュメントのURLではありません。 - not_linked_to_project: はプロジェクトにリンクされていない。 + invalid_sharepoint_url: は有効なSharePointサイト、ライブラリ、またはドキュメントのURLではありません。 + not_linked_to_project: はプロジェクトにリンクされていません。 models: storages/file_link: attributes: origin_id: - only_numeric_or_uuid: には数値かuuidしか指定できない。 + only_numeric_or_uuid: は数値またはuuidのみとなります。 storages/project_storage: attributes: project_folder_id: blank: フォルダーを選択してください。 project_folder_mode: - mode_unavailable: はこのストレージでは使用できない。 + mode_unavailable: このストレージでは使用できません。 project_ids: blank: プロジェクトを選択してください。 storages/storage: attributes: host: - authorization_header_missing: が完全にセットアップされていません。APIリクエストのベアラートークンベースの認証に必要な "Authorization "ヘッダーをNextcloudインスタンスが受け取っていません。HTTPサーバーの設定を再度ご確認ください。 - cannot_be_connected_to: に到達できませんでした。ホストに到達可能で、OpenProject 統合アプリがインストールされていることを確認してください。 - minimal_nextcloud_version_unmet: 最小バージョン要件を満たしていない(Nextcloud 23以上である必要があります。) - not_nextcloud_server: はNextcloudサーバーではありません。 - op_application_not_installed: は、アプリ「OpenProject integration」がインストールされていないようです。インストールしてからもう一度お試しください。 + authorization_header_missing: 完全には設定されていません。 Nextcloudインスタンスは、APIリクエストのベアラートークンベースの認可に必要な「Authorization」ヘッダーを受け取りません。 HTTPサーバーの設定を再確認してください。 + cannot_be_connected_to: に到達できませんでした。ホストが到達可能で、OpenProject 統合アプリがインストールされていることを確認してください。 + minimal_nextcloud_version_unmet: 最小バージョン要件を満たしていません(Nextcloud23以上でなければなりません) + not_nextcloud_server: はNextcloudサーバーではありません + op_application_not_installed: アプリ「OpenProject統合」がインストールされていません。最初にインストールしてからもう一度お試しください。 password: - invalid_password: は無効である。 + invalid_password: は無効です。 unknown_error: could not be validated with the file storage provider. Please verify that the connection is functioning properly. models: file_link: ファイル storages/storage: ストレージ api_v3: errors: - too_many_elements_created_at_once: 一度に作成される要素が多すぎる。最大でも %{max} 、 %{actual}。 + too_many_elements_created_at_once: 一度に作成された要素が多すぎます。 %{max} の期待値は %{actual} です。 external_file_storages: 外部ファイルストレージ permission_create_files: '自動的に管理されたプロジェクトフォルダ: ファイルの作成' permission_create_files_explanation: この権限はNextcloudストレージでのみ利用できます permission_delete_files: '自動的に管理されたプロジェクトフォルダ: ファイルの削除' permission_delete_files_explanation: この権限はNextcloudストレージでのみ利用できます - permission_header_for_project_module_storages: 自動的に管理されるプロジェクトフォルダ + permission_header_for_project_module_storages: 自動的に管理されたプロジェクトフォルダ permission_manage_file_links: ファイルへのリンク管理 permission_manage_files_in_project: プロジェクト内のファイル管理 permission_read_files: '自動的に管理されたプロジェクトフォルダ: ファイルの読み込み' @@ -86,16 +86,16 @@ ja: project_module_storages: ファイルを添付する project_storages: edit_project_folder: - label: プロジェクトフォルダの編集 + label: プロジェクトフォルダを編集 open: - contact_admin: このエラーを解決するには、管理者に連絡してください。 - remote_identity_error: ストレージへの接続中に予期せぬエラーが発生しました。 + contact_admin: このエラーを解決するには管理者に問い合わせてください。 + remote_identity_error: ストレージへの接続中に予期しないエラーが発生しました。 project_folder_mode: - automatic: 自動的に管理される - inactive: 特定のフォルダなし + automatic: 自動的に管理 + inactive: 特定のフォルダがありません manual: 既存のフォルダを手動で管理 remove_project: - deletion_failure_flash: ストレージからのプロジェクトの削除に失敗しました。 %{error} + deletion_failure_flash: プロジェクトをストレージから削除できませんでした。 %{error} label: プロジェクトを削除 services: attributes: @@ -110,7 +110,7 @@ ja: one_drive_sync_service: create_folder: 'プロジェクトフォルダの作成を管理:' ensure_root_folder_permissions: 'ベースフォルダの権限を設定:' - hide_inactive_folders: '非アクティブフォルダを隠す ステップ:' + hide_inactive_folders: '非アクティブフォルダを隠す ステップ' remote_folders: 'Read contents of the drive root folder:' rename_project_folder: '管理プロジェクトフォルダの名前を変更します:' sharepoint_sync_service: @@ -121,16 +121,16 @@ ja: rename_project_folder: '管理プロジェクトフォルダの名前を変更します:' errors: messages: - error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください + error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください。 forbidden: OpenProject could not access the requested resource. Please check your permissions configuration on the Storage Provider. unauthorized: OpenProjectはストレージプロバイダと認証できませんでした。アクセスできることを確認してください。 models: copy_project_folders_service: conflict: フォルダ %{destination_path} は既に存在する。上書きを避けるために処理を中断しています。 - error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください - forbidden: OpenProject はソースフォルダにアクセスできませんでした。ストレージ・プロバイダの権限設定を確認してください + error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください。 + forbidden: OpenProject はソースフォルダにアクセスできませんでした。ストレージ・プロバイダの権限設定を確認してください。 not_found: ソース・テンプレートの場所 %{source_path} が見つかりませんでした。 - unauthorized: OpenProject はストレージプロバイダと認証できませんでした。ストレージの設定を確認してください + unauthorized: OpenProject はストレージプロバイダと認証できませんでした。ストレージの設定を確認してください。 nextcloud_sync_service: attributes: add_user_to_group: @@ -151,31 +151,31 @@ ja: conflict: '以下の理由により、 %{user} のユーザーを %{group} グループから削除できませんでした: %{reason}' failed_to_remove: '以下の理由により、 %{user} のユーザーを %{group} グループから削除できませんでした: %{reason}' rename_project_folder: - conflict: OpenProjectは、同じ名前のフォルダが既に存在するため、プロジェクトフォルダの名前を %{current_path} に変更できませんでした - forbidden: OpenProject ユーザーは %{current_path} フォルダにアクセスできません。 - not_found: "%{current_path} は見つからなかった。" + conflict: OpenProjectは、同じ名前のフォルダが既に存在するため、プロジェクトフォルダの名前を %{current_path} に変更できませんでした。 + forbidden: OpenProjectユーザーは %{current_path} フォルダにアクセスできません。 + not_found: "%{current_path} は見つかりませんでした。" set_folders_permissions: - permission_not_set: '%{path}にパーミッションを設定できなかった。' - error: 予期しないエラーが発生しました。Nextcloud インスタンスに到達可能であることを確認し、OpenProject ワーカーのログを確認してください + permission_not_set: '%{path} に権限を設定できませんでした。' + error: 予期しないエラーが発生しました。Nextcloudインスタンスがアクセス可能であることを確認し、詳細についてはOpenProjectワーカーログを確認してください。 group_does_not_exist: "%{group} は存在しません。Nextcloudインスタンスの設定を確認してください。" - insufficient_privileges: OpenProjectには、 %{group}に %{user} を追加するのに十分な権限がありません。Nextcloudのグループ設定を確認してください。 - not_allowed: ネクストクラウドはリクエストをブロックする。 + insufficient_privileges: OpenProjectには %{user} を %{group}に追加するための十分な権限がありません。Nextcloudでグループ設定を確認してください。 + not_allowed: Nextcloudはリクエストをブロックします。 not_found: OpenProject could not find the file on the Nextcloud Storage Provider. Please check if it wasn't deleted. unauthorized: OpenProjectがNextcloudと同期できませんでした。ストレージとNextcloudの設定を確認してください。 - user_does_not_exist: "%{user} はNextcloudには存在しません。" + user_does_not_exist: "Nextcloudには%{user} は存在しません。" one_drive_sync_service: attributes: create_folder: - conflict: '%{folder_name} はすでに %{parent_location}に存在している。' - not_found: "%{parent_location} は見つからなかった。" + conflict: '%{folder_name} は %{parent_location} に既に存在します。' + not_found: "%{parent_location} は見つかりませんでした。" hide_inactive_folders: - permission_not_set: '%{path}にパーミッションを設定できなかった。' + permission_not_set: '%{path} に権限を設定できませんでした。' remote_folders: - request_error: OpenProject は %{drive_id}ドライブにアクセスできませんでした。ストレージの設定が正しいかどうか確認してください。 + request_error: OpenProjectがドライブ %{drive_id}にアクセスできませんでした。ストレージの設定が正しいか確認してください。 rename_project_folder: conflict: OpenProject could not rename the folder %{current_path} to %{project_folder_name} as a folder with the same name already exists. - forbidden: OpenProject は、 %{current_path} にアクセスできず、名前を変更できません。 - not_found: "%{current_path} は見つからなかった。" + forbidden: OpenProject は名前を変更するために %{current_path} にアクセスできません。 + not_found: "%{current_path} は見つかりませんでした。" set_folders_permissions: permission_not_set: '%{path} に権限を設定できませんでした。' error: An unexpected error occurred. Please ensure that OneDrive is reachable and check OpenProject worker logs for more information. @@ -303,41 +303,41 @@ ja: drive_id_format: ドライブIDフォーマット header: 構成 host: ホスト URL - host_url_accessible: アクセス可能なホストURL + host_url_accessible: ホスト URL アクセス storage_configured: 設定完了 - tenant_id: テナントID + tenant_id: Tenant ID failures: - other: "%{count} チェック失敗" + other: "%{count} チェックに失敗しました" success: すべてのチェックに合格 warnings: other: "%{count} は警告を返しました" connection_validation: client_id_invalid: 設定されたOAuth 2クライアントIDが無効です。設定を確認してください。 client_secret_invalid: 設定されたOAuth 2クライアントシークレットが無効です。設定を確認してください。 - nc_dependency_missing: 'ファイルストレージに必要な依存関係がありません。次の依存関係を追加してください: %{dependency}。' + nc_dependency_missing: 'ファイルストレージに必要な依存関係がありません。次の依存関係を追加してください: %{dependency}。' nc_dependency_version_mismatch: '%{dependency} アプリのバージョンがサポートされていません。Nextcloudサーバーをアップデートしてください。' nc_host_not_found: 設定されたホストURLにNextcloudサーバーが見つかりません。設定を確認してください。 nc_oauth_request_not_found: 現在接続しているユーザーを取得するエンドポイントが見つかりませんでした。詳細については、サーバーのログを確認してください。 nc_oauth_request_unauthorized: 現在のユーザーにはリモートファイルストレージにアクセスする権限がありません。サーバーのログを確認してください。 - nc_oauth_token_missing: OpenProjectでは、ユーザーがNextcloudアカウントをリンクしていないため、ユーザーレベルのNextcloudとの通信をテストできません。 + nc_oauth_token_missing: OpenProject は、Nextcloudアカウントへのリンクがまだないため、Nextcloudとのユーザーレベルの通信をテストできません。 nc_team_folder_not_found: The team folder could not be found. nc_unexpected_content: Unexpected content found in the managed team folder. nc_userless_access_denied: 設定されているアプリのパスワードが無効です。 not_configured: 接続を検証できませんでした。先に設定を完了してください。 - od_client_cant_delete_folder: クライアントがフォルダの削除に失敗しています。お使いのストレージのセットアップドキュメントを確認してください。 - od_client_write_permission_missing: クライアントの書き込み権限が不足しているようです。お使いのストレージのセットアップドキュメントを確認してください。 - od_drive_id_invalid: 設定されたドライブIDが無効のようです。設定を確認してください。 - od_drive_id_not_found: 設定されたドライブIDが見つかりません。設定を確認してください。 - od_oauth_request_not_found: 現在接続しているユーザーを取得するエンドポイントが見つかりませんでした。詳細については、サーバーのログを確認してください。 - od_oauth_request_unauthorized: 現在のユーザーにはリモートファイルストレージにアクセスする権限がありません。サーバーのログを確認してください。 - od_oauth_token_missing: OpenProjectは、ユーザーがまだMicrosoftアカウントをリンクしていないため、OneDriveとのユーザーレベルの通信をテストできません。 - od_tenant_id_wrong: 設定されたディレクトリ(テナント)IDが無効です。設定を確認してください。 + od_client_cant_delete_folder: クライアントがフォルダを削除できません。ストレージのセットアップドキュメントを確認してください。 + od_client_write_permission_missing: クライアントは書き込み権限がありません。ストレージの設定ドキュメントを確認してください。 + od_drive_id_invalid: 設定されたドライブ ID が無効です。設定を確認してください。 + od_drive_id_not_found: 設定されたドライブ ID が見つかりません。設定を確認してください。 + od_oauth_request_not_found: 現在接続されているユーザーを取得するエンドポイントが見つかりませんでした。詳細についてはサーバーログを確認してください。 + od_oauth_request_unauthorized: 現在のユーザーはリモートファイルストレージにアクセスする権限がありません。詳細についてはサーバーログを確認してください。 + od_oauth_token_missing: OpenProject は、ユーザーが Microsoft アカウントをまだリンクしていないため、OneDrive とのユーザー レベルの通信をテストできません。 + od_tenant_id_wrong: 設定されたディレクトリ (テナント) IDは無効です。設定を確認してください。 od_test_folder_exists: テストに必要なフォルダ %{folder_name} はすでに存在します。削除して再度お試しください。 od_unexpected_content: ドライブに予期しないコンテンツが見つかりました。 - offline_access_scope_missing: OpenID Connectプロバイダがoffline_accessスコープを要求するように設定することをお勧めします。統合はまだ機能するかもしれませんが、リフレッシュトークンの有効期限が切れていないことを確認してください。 + offline_access_scope_missing: offline_access スコープを要求するために OpenID Connect プロバイダを設定することをお勧めします。 統合はまだ動作するかもしれませんが、更新トークンが期限切れでないことを確認してください。 oidc_cant_refresh_token: ストレージへのアクセスを確認中にエラーが発生しました。詳細についてはサーバーログを確認してください。 - oidc_non_oidc_user: 現在のユーザーはプロビジョニングされていますが、OpenID Connect (OIDC) Identity Providerによってプロビジョニングされていません。OIDCプロビジョニングされたユーザーでチェックを再実行してください。 - oidc_non_provisioned_user: 現在のユーザはOpenID Connect Identity Providerから提供されていません。提供されたユーザーでチェックを再実行してください。 + oidc_non_oidc_user: 現在のユーザは、プロビジョニング中にOpenID Connect(OIDC)アイデンティティプロバイダによってプロビジョニングされていませんでした。OIDCプロビジョニングされたユーザでチェックを再実行してください。 + oidc_non_provisioned_user: 現在のユーザーはOpenID Connectアイデンティティプロバイダーによって提供されていません。指定されたユーザーとチェックを再実行してください。 oidc_provider_cant_exchange: OpenID Connectプロバイダはトークン交換をサポートしていないようですが、トークン交換はストレージ用に設定されています。 oidc_token_acquisition_failed: OpenID Connectのセットアップでは、必要なオーディエンスが提供されておらず、トークン交換機能も提供されていません。詳しくはドキュメントをご覧ください。 oidc_token_exchange_failed: OpenID Connect ProviderのToken Exchange設定に問題があるようです。設定を確認し、再度お試しください。 @@ -352,7 +352,7 @@ ja: sp_oauth_token_missing: OpenProject は、ユーザーがまだ SharePoint アカウントをリンクしていないため、ユーザーレベルの SharePoint との通信をテストできません。 sp_tenant_id_missing: 構成されたディレクトリ(テナント)IDがSharePointにありません。設定を確認してください。 sp_unexpected_content: Unexpected content found in the SharePoint Document Library. - unknown_error: 接続を検証できませんでした。不明なエラーが発生しました。詳細については、サーバーのログを確認してください。 + unknown_error: 接続を検証できませんでした。不明なエラーが発生しました。詳細についてはサーバーログを確認してください。 label_error: エラー label_failed: 失敗しました label_healthy: 健康的 @@ -360,55 +360,55 @@ ja: label_pending: 保留中 label_skipped: スキップ label_warning: 注意 - no_report: 報告書なし - no_report_description: 今すぐチェックを実行し、このファイル・ストレージの完全な健全性ステータスをレポートする。 + no_report: 利用可能なレポートがありません + no_report_description: 今すぐこのファイルストレージの完全な健康状態レポートを確認します。 open_report: 完全な健康報告を開く project_folders: subtitle: 自動的に管理されるプロジェクトフォルダ - since: '%{datetime}より' + since: '%{datetime} 以降' summary: - failure: いくつかのチェックに失敗し、システムが期待通りに機能しない。 - success: すべての接続とシステムは期待通りに機能している。 - warning: いくつかのチェックは警告を返した。これは予期せぬ動作につながる可能性がある。 - title: 健康状態報告 + failure: いくつかのチェックに失敗し、システムが期待どおりに動作しません。 + success: すべての接続とシステムは期待どおりに動作しています。 + warning: いくつかのチェックが警告を返しました。これは予期しない動作につながる可能性があります。 + title: 健康状態レポート health_email_notifications: description_disabled: 管理者は、重要なアップデートがあった場合、メールでアップデートを受け取ることはできません。 description_enabled: 管理者は、重要なアップデートがあった場合、メールで最新情報を受け取ります。 - error_could_not_be_saved: 電子メール通知の設定を保存できませんでした。もう一度お試しください。 + error_could_not_be_saved: メール通知設定を保存できませんでした。もう一度やり直してください。 title: 管理者にメールで更新する help_texts: - project_folder: プロジェクトフォルダは、このプロジェクトのファイルアップロード用のデフォルトフォルダです。それでも、ユーザーは他の場所にファイルをアップロードすることができます。 - project_folder_bulk: プロジェクトフォルダは、選択したすべてのプロジェクトのファイルアップロード用のデフォルトフォルダです。これは、各プロジェクト設定で個別に変更できます。それでも、ユーザーは他の場所にファイルをアップロードすることができます。 + project_folder: プロジェクトフォルダは、このプロジェクトのファイルアップロードのデフォルトフォルダです。ただし、ユーザーは他の場所にファイルをアップロードすることができます。 + project_folder_bulk: プロジェクトフォルダは、選択したすべてのプロジェクトのファイルアップロードのデフォルトフォルダです。 プロジェクトごとの設定で個別に変更することができますが、ユーザーは別の場所にファイルをアップロードすることもできます。 instructions: - all_available_storages_already_added: 利用可能なすべてのストレージはすでにプロジェクトに追加されている。 - authentication_method: OpenProject とストレージ間のリクエストの認証方法。 - automatic_folder: これにより、このプロジェクトのルート・フォルダーが自動的に作成され、各プロジェクト・メンバーのアクセス権が管理されます。 - empty_project_folder_validation: 続行するには、フォルダの選択が必須です。 - existing_manual_folder: 既存のフォルダをこのプロジェクトのルートフォルダとして指定することができます。ただし、パーミッションは自動的に管理されないため、管理者は関連するユーザーがアクセスできることを手動で確認する必要があります。選択したフォルダは、複数のプロジェクトで使用できます。 - host: https:// を含むストレージのホスト・アドレスを追加してください。255文字以内にしてください。 - managed_project_folders_application_password_caption: '%{provider_type_link}からこの値をコピーして、自動管理フォルダを有効にする。' - name: ユーザーが複数のストレージを区別できるように、ストレージに名前を付ける。 + all_available_storages_already_added: 利用可能なすべてのストレージが既にプロジェクトに追加されています。 + authentication_method: OpenProjectとストレージ間のリクエストは認証されます。 + automatic_folder: これにより、このプロジェクトのルートフォルダが自動的に作成され、各プロジェクトメンバーのアクセス権限が管理されます。 + empty_project_folder_validation: フォルダの選択は必須です。 + existing_manual_folder: このプロジェクトのルートフォルダとして既存のフォルダを指定できます。 ただし、権限は自動的に管理されておらず、管理者は関連するユーザーに手動でアクセス権があることを確認する必要があります。 選択したフォルダは複数のプロジェクトで使用できます。 + host: https://を含むストレージのホストアドレスを追加してください。255文字以内にしてください。 + managed_project_folders_application_password_caption: '%{provider_type_link} からこの値をコピーすることで、自動管理フォルダを有効にします。' + name: ユーザーが複数のストレージを区別できるように、ストレージに名前を付けます。 new_storage: 詳しくは、 %{provider_name} ファイルストレージ統合の設定に関するドキュメントをお読みください。 nextcloud: application_link_text: アプリケーション "Integration OpenProject" - integration: ネクストクラウド管理 / OpenProject + integration: Nextcloudの管理 / OpenProject oauth_configuration: '%{application_link_text} からこれらの値をコピーします。' - provider_configuration: セットアップを行う前に、Nextcloudインスタンスの管理者権限があり、 %{application_link_text} がインストールされていることを確認してください。 - storage_audience: Nextcloud インスタンスが ID プロバイダとの通信に使用するクライアント ID。 - storage_audience_placeholder: 例:ネクストクラウド - token_exchange_scope: トークン交換時に要求するスコープを、それぞれスペースで区切って指定する。 - no_specific_folder: デフォルトでは、ファイルをアップロードすると、各ユーザーは自分のホームフォルダから開始します。 - no_storage_set_up: ファイルストレージはまだ設定されていない。 - not_logged_into_storage: プロジェクトフォルダを選択するには、まずログインしてください。 + provider_configuration: Nextcloudインスタンスに管理権限があり、設定を行う前に %{application_link_text} がインストールされていることを確認してください。 + storage_audience: NextcloudインスタンスがIDプロバイダーと通信するために使用するクライアントID。 + storage_audience_placeholder: 例:nextcloud + token_exchange_scope: トークン交換中に要求されるべきスコープは、それぞれスペースで区切られています。 + no_specific_folder: デフォルトでは、各ユーザーはファイルをアップロードしたときに自分のホームフォルダから開始します。 + no_storage_set_up: まだ設定されているファイルストレージがありません。 + not_logged_into_storage: プロジェクトフォルダを選択するには、最初にログインしてください oauth_application_details: クライアントシークレットの値は、このウィンドウを閉じた後は二度とアクセスできなくなります。これらの値を %{oauth_application_details_link}にコピーしてください。 - oauth_application_details_link_text: NextcloudのOpenProject統合設定 + oauth_application_details_link_text: Nextcloud OpenProjectインテグレーション設定 one_drive: application_link_text: Azure Portal copy_redirect_uri: リダイレクトURIをコピーする documentation_link_text: OneDriveファイルストレージのドキュメント drive_id: '%{drive_id_link_text} の手順に従って、目的のドライブからIDをコピーしてください。' - integration: ワンドライブ - missing_client_id_for_redirect_uri: OAuthの値を入力してURIを生成してください。 + integration: OneDrive + missing_client_id_for_redirect_uri: OAuthの値を入力してURIを生成してください oauth_client_redirect_uri: この値を「リダイレクト URIs」にある新しい Web リダイレクト URI にコピーしてください。 oauth_client_secret: Client 資格情報にアプリケーション クライアント シークレットがない場合は、新しいシークレットを作成してください。 oauth_configuration: '%{application_link_text}、目的のアプリケーションからこれらの値をコピーします。' @@ -480,13 +480,13 @@ ja: login_button_aria_label: '%{storage} にログイン' login_button_label: "%{provider_type} ログイン" project_settings: - description: プロジェクトフォルダにアクセスするには、 %{storage}にログインする必要があります。 + description: プロジェクトフォルダにアクセスするには、 %{storage} にログインする必要があります。 requesting_access_to: '%{storage} へのアクセスをリクエストしています' storage_admin: description: このストレージにプロジェクトを追加するには、 %{provider_type}にログインする必要があります。ログインしてもう一度やり直してください。 open_project_storage_modal: success: - subtitle: リダイレクトされます + subtitle: リダイレクトしています title: 連携のセットアップが完了しました timeout: link_text: ファイルストレージセットアップの状態の状態 @@ -505,8 +505,8 @@ ja: subtitle_short: OpenProjectにプロジェクトごとにフォルダを自動的に作成させます。 title: 自動的に管理されるプロジェクトフォルダ project_settings: - edit: このプロジェクトのファイル・ストレージを編集する - members_connection_status: メンバーの接続状況 + edit: このプロジェクトのファイルストレージを編集 + members_connection_status: 会員の接続状況 new: このプロジェクトにファイルストレージを追加する project_storage_members: subtitle: 全プロジェクトメンバーのストレージ %{storage_name_link} の接続状態を確認する。 @@ -517,14 +517,14 @@ ja: provider_types: label: プロバイダー・タイプ nextcloud: - label_oauth_client_id: NextcloudのOAuthクライアントID - label_oauth_client_secret: NextcloudOAuthクライアントシークレット + label_oauth_client_id: Nextcloud OAuthクライアントID + label_oauth_client_secret: Nextcloud OAuth クライアントシークレット name: ネクストクラウド name_placeholder: 例:ネクストクラウド one_drive: - label_oauth_client_id: Azure OAuthアプリケーション(クライアント)ID + label_oauth_client_id: Azure OAuth アプリケーション (クライアント) ID label_oauth_client_secret: Azure OAuth クライアントの秘密値 - name: ワンドライブ + name: OneDrive name_placeholder: '例: OneDrive' sharepoint: drive_description: OpenProject access-managed document library @@ -534,18 +534,18 @@ ja: name_placeholder: 例:シェアポイント show_attachments_toggle: description: このオプションを無効にすると、ワークパッケージのファイルタブの添付ファイルリストが非表示になります。ワークパッケージの説明に添付されたファイルは、内部添付ファイルストレージにアップロードされます。 - label: ワークパッケージのファイルタブに添付ファイルを表示 + label: ワークパッケージファイルタブに添付ファイルを表示 storage_audience: - documentation_intro: 以下のオプションと ID プロバイダの設定の詳細については、当社のドキュメントをお読みください。 + documentation_intro: アイデンティティプロバイダの以下のオプションと設定については、当社のドキュメントをお読みください。 idp: - helptext: OpenProjectは、ストレージへのリクエストを認証するために、ログイン時にIDプロバイダから受け取ったアクセストークンを使用します。別のトークンを取得しようとすることはありません。 - label: ユーザーログイン時に取得したアクセストークンを使用する + helptext: OpenProjectはログイン中にIDプロバイダーが受け取ったアクセストークンを使用して、ストレージへのリクエストを認証します。 別のトークンを取得しようとしません。 + label: ログイン中に取得したアクセストークンを使用する manual: - helptext: OpenProjectは、指定されたオーディエンスのIDプロバイダとトークンを交換します。 + helptext: OpenProject は、特定のオーディエンスの ID プロバイダーとトークンを交換します。 label: Manually specify audience for which to exchange access token (Recommended) storage_list_blank_slate: - description: ストレージを追加して、ここで見ることができる。 - heading: あなたはまだ倉庫を持っていない。 + description: ここにそれらを見るためにストレージを追加します。 + heading: まだストレージがありません。 successful_storage_connection: ストレージが正常に接続されました! 使用する各プロジェクトの「プロジェクト」タブでストレージをアクティブにすることを忘れないでください。 upsell: one_drive: diff --git a/modules/storages/config/locales/crowdin/js-ja.yml b/modules/storages/config/locales/crowdin/js-ja.yml index 04cb36084f5..35563523140 100644 --- a/modules/storages/config/locales/crowdin/js-ja.yml +++ b/modules/storages/config/locales/crowdin/js-ja.yml @@ -3,14 +3,14 @@ ja: js: storages: authentication_error: "%{storageType} での認証に失敗しました" - link_files_in_storage: "リンクファイル %{storageType}" - link_existing_files: "既存のファイルをリンク" - upload_files: "ファイルのアップロード" + link_files_in_storage: "%{storageType}のファイルをリンクする" + link_existing_files: "既存のファイルをリンクする" + upload_files: "ログファイル" drop_files: "ここにファイルをドロップして、 %{name} にアップロードします。" drop_or_click_files: "ここにファイルをドロップするか、クリックして %{name} にアップロードします。" login: "%{storageType} ログイン" login_to: "%{storageType}にログイン" - no_connection: "%{storageType} 接続がありません" + no_connection: "%{storageType} 接続なし" open_storage: "%{storageType} を開く" select_location: "場所を選択" choose_location: "場所を選ぶ" @@ -24,7 +24,7 @@ ja: authentication_error: "%{storageType} へのリクエストを認証できませんでした。これはエラーです。" connection_error: > %{storageType} の設定が一部機能していません。 %{storageType} 管理者にお問い合わせください。 - live_data_error: "ファイルの詳細の取得に失敗しました" + live_data_error: "ファイル詳細の取得エラー" live_data_error_description: > 一部の %{storageType} データを取得できませんでした。このページを再読み込みするか、 %{storageType} 管理者にお問い合わせください。 no_file_links: "このワークパッケージにファイルをリンクするには、 %{storageType}を使用してください。" @@ -33,7 +33,7 @@ ja: suggest_logout: ログアウトしてログインし直すと、この問題が解決するかどうか試してみてください。 suggest_relink: 以下のログインボタンからアカウントを再リンクすると、この問題が解決するかどうか試してみてください。 files: - already_existing_header: "このファイルはすでに存在する" + already_existing_header: "このファイルは既に存在します" already_existing_body: > このファイルをアップロードしようとしている場所に、"%{fileName}"という名前のファイルがすでに存在します。どうしますか? directory_not_writeable: "このフォルダにファイルを追加する権限がありません。" @@ -41,7 +41,7 @@ ja: dragging_folder: "%{storageType} へのアップロードはフォルダをサポートしていません。" empty_folder: "このフォルダは空です。" empty_folder_location_hint: "下のボタンをクリックして、この場所にファイルをアップロードしてください。" - file_not_selectable_location: "場所を選択する過程でファイルを選択することはできない。" + file_not_selectable_location: "ファイルを選択することは、場所を選択する過程ではできません。" project_folder_no_access: > プロジェクトフォルダにアクセスできません。管理者に連絡してアクセス権を取得するか、別の場所にファイルをアップロードしてください。 managed_project_folder_not_available: > @@ -79,9 +79,9 @@ ja: ファイル (%{fileName}) の容量がストレージ・クォータの許容量を超えています。管理者に連絡して、このクォータを変更してください。 detail: nextcloud: > - 最新版のNextcloudアプリ「OpenProject Integration」がインストールされていることを確認し、管理者にお問い合わせください。 + Nextcloudアプリ「OpenProject統合」の最新バージョンがインストールされていることを確認し、詳細については管理者にお問い合わせください。 link_uploaded_file_error: > - 最近アップロードされたファイル '%{fileName}' をワークパッケージ %{workPackageId}にリンクするエラーが発生しました。 + 最近アップロードされたファイル '%{fileName}' をワークパッケージ %{workPackageId} にリンクしてエラーが発生しました。 tooltip: not_logged_in: "このファイルにアクセスするには、ストレージにログインしてください。" view_not_allowed: "このファイルを閲覧する権限がありません。" diff --git a/modules/storages/spec/common/storages/adapters/authentication_strategies/bearer_token_spec.rb b/modules/storages/spec/common/storages/adapters/authentication_strategies/bearer_token_spec.rb index 5dbea598a83..b7abbebd15c 100644 --- a/modules/storages/spec/common/storages/adapters/authentication_strategies/bearer_token_spec.rb +++ b/modules/storages/spec/common/storages/adapters/authentication_strategies/bearer_token_spec.rb @@ -38,13 +38,14 @@ module Storages let(:storage) { create(:nextcloud_storage) } let(:access_token) { "my_access_token" } - it "must yield with passed access token without" do + it "must yield with the passed access token" do strategy = Input::Strategy.build(key: :bearer_token, token: access_token) was_yielded = false Authentication[strategy].call(storage:) do |http| was_yielded = true - expect(http.instance_variable_get(:@options).headers["authorization"]).to eq("Bearer #{access_token}") + expect(http.instance_variable_get(:@options).auth_header_type).to eq("Bearer") + expect(http.instance_variable_get(:@options).auth_header_value).to eq(access_token) end expect(was_yielded).to be_truthy diff --git a/modules/storages/spec/common/storages/adapters/authentication_strategies/oauth_client_credentials_spec.rb b/modules/storages/spec/common/storages/adapters/authentication_strategies/oauth_client_credentials_spec.rb index 0ac4624a754..42d78317082 100644 --- a/modules/storages/spec/common/storages/adapters/authentication_strategies/oauth_client_credentials_spec.rb +++ b/modules/storages/spec/common/storages/adapters/authentication_strategies/oauth_client_credentials_spec.rb @@ -43,7 +43,7 @@ module Storages context "with valid oauth credentials", vcr: "auth/one_drive/client_credentials" do it "return success" do - result = Authentication[strategy_data].call(storage:, http_options: {}) { |http| make_request(http) } + result = Authentication[strategy_data].call(storage:) { make_request(it) } expect(result).to be_success expect(result.value!).to eq("EXPECTED_RESULT") @@ -51,9 +51,7 @@ module Storages it "caches the token if use_cache is true" do strategy_data = Input::Strategy.build(key: :oauth_client_credentials, use_cache: true) - Authentication[strategy_data].call(storage:, http_options: {}) do |http| - make_request(http) - end + Authentication[strategy_data].call(storage:) { make_request(it) } cache_key = described_class::TOKEN_CACHE_KEY % storage.id expect(Rails.cache.read(cache_key)).not_to be_nil @@ -62,7 +60,7 @@ module Storages context "with invalid client secret", vcr: "auth/one_drive/client_credentials_invalid_client_secret" do it "must return unauthorized" do - result = Authentication[strategy_data].call(storage:) { |http| make_request(http) } + result = Authentication[strategy_data].call(storage:) { make_request(it) } expect(result).to be_failure error = result.failure @@ -73,7 +71,7 @@ module Storages context "with invalid client id", vcr: "auth/one_drive/client_credentials_invalid_client_id" do it "must return unauthorized" do - result = Authentication[strategy_data].call(storage:) { |http| make_request(http) } + result = Authentication[strategy_data].call(storage:) { make_request(it) } expect(result).to be_failure error = result.failure @@ -84,7 +82,9 @@ module Storages private - def make_request(http) = handle_response(http.get(request_url)) + def make_request(http) + handle_response(http.get(request_url)) + end def handle_response(response) case response diff --git a/modules/storages/spec/common/storages/adapters/authentication_strategies/oauth_user_token_spec.rb b/modules/storages/spec/common/storages/adapters/authentication_strategies/oauth_user_token_spec.rb index 0742fa87b32..68ac775cfaa 100644 --- a/modules/storages/spec/common/storages/adapters/authentication_strategies/oauth_user_token_spec.rb +++ b/modules/storages/spec/common/storages/adapters/authentication_strategies/oauth_user_token_spec.rb @@ -98,7 +98,7 @@ module Storages allow(Rails.logger).to receive(:error) allow(strategy).to receive(:current_token).and_return(Success(token)) - allow(token).to receive(:destroy).and_raise(ActiveRecord::StaleObjectError).twice + allow(token).to receive(:destroy!).and_raise(ActiveRecord::StaleObjectError).twice expect do strategy.call(storage:, http_options:) { |http| make_request(http) } @@ -109,68 +109,16 @@ module Storages end context "with invalid oauth access token" do - it "must refresh token and return success" do - storage - token = OAuthClientToken.last - user_request_stub_1 = stub_request(:get, "https://nextcloud.local/ocs/v1.php/cloud/user") - .with( - headers: { - "Accept" => "*/*", - "Accept-Encoding" => "gzip, deflate", - "Authorization" => "Bearer #{token.access_token}", - "User-Agent" => /OpenProject \d+\.\d+\.\d+ HTTPX Client/ - } - ) - .to_return(status: 401, body: <<~XML, headers: {}) - - - - failure - 997 - Current user is not logged in - - - - - - XML - user_request_stub_2 = stub_request(:get, "https://nextcloud.local/ocs/v1.php/cloud/user") - .with( - headers: { - "Accept" => "*/*", - "Accept-Encoding" => "gzip, deflate", - "Authorization" => "Bearer NEW_ACCESS_TOKEN", - "User-Agent" => /OpenProject \d+\.\d+\.\d+ HTTPX Client/ - } - ) - .to_return(status: 200, body: <<~JSON, headers: { "Content-Type" => "application/json; charset=utf-8" }) - {"ocs":{"meta":{"status":"ok","statuscode":100,"message":"OK","totalitems":"","itemsperpage":""},"data":{"enabled":true,"storageLocation":"/var/www/html/data/admin","id":"admin","lastLogin":1709888213000,"backend":"Database","subadmin":[],"quota":{"free":962269761536,"used":1137306515,"total":963407068051,"relative":0.12,"quota":-3},"manager":"","avatarScope":"v2-federated","email":null,"emailScope":"v2-federated","additional_mail":[],"additional_mailScope":[],"displayname":"admin","display-name":"admin","displaynameScope":"v2-federated","phone":"","phoneScope":"v2-local","address":"","addressScope":"v2-local","website":"","websiteScope":"v2-local","twitter":"","twitterScope":"v2-local","fediverse":"","fediverseScope":"v2-local","organisation":"","organisationScope":"v2-local","role":"","roleScope":"v2-local","headline":"","headlineScope":"v2-local","biography":"","biographyScope":"v2-local","profile_enabled":"1","profile_enabledScope":"v2-local","groups":["admin"],"language":"en","locale":"","notify_email":null,"backendCapabilities":{"setDisplayName":true,"setPassword":true}}}} - JSON - token_request_stub = stub_request(:post, "https://nextcloud.local/index.php/apps/oauth2/api/v1/token") - .with( - body: { "client_id" => token.oauth_client.client_id, - "client_secret" => token.oauth_client.client_secret, - "grant_type" => "refresh_token", - "refresh_token" => token.refresh_token, - "scope" => "" }, - headers: { - "Accept" => "*/*", - "Accept-Encoding" => "gzip, deflate", - "Content-Type" => "application/x-www-form-urlencoded", - "User-Agent" => /OpenProject \d+\.\d+\.\d+ HTTPX Client/ - } - ) - .to_return(status: 200, body: <<~JSON, headers: { "Content-Type" => "application/json; charset=utf-8" }) - {"access_token":"NEW_ACCESS_TOKEN","token_type":"Bearer","expires_in":3600,"refresh_token":"NEW_REFRESH_TOKEN","user_id":"admin"} - JSON - result = Authentication[strategy_data].call(storage:) { |http| make_request(http) } + it "must refresh token and return success", vcr: "auth/nextcloud/refresh_token" do + token = OAuthClientToken.where(oauth_client_id: storage.oauth_client.id).last + original_access_token = token&.access_token + token&.update!(access_token: "NOT_A_VALID_TOKEN") - expect(user_request_stub_1).to have_been_made.once - expect(user_request_stub_2).to have_been_made.once - expect(token_request_stub).to have_been_made.once + result = Authentication[strategy_data].call(storage:) { |http| make_request(http) } expect(result).to be_success expect(result.value!).to eq("EXPECTED_RESULT") + expect(original_access_token).not_to eq(token&.reload&.access_token) end end diff --git a/modules/storages/spec/common/storages/adapters/providers/nextcloud/contracts/storage_contract_spec.rb b/modules/storages/spec/common/storages/adapters/providers/nextcloud/contracts/storage_contract_spec.rb index ae89c97cdf5..6a9cb9398bd 100644 --- a/modules/storages/spec/common/storages/adapters/providers/nextcloud/contracts/storage_contract_spec.rb +++ b/modules/storages/spec/common/storages/adapters/providers/nextcloud/contracts/storage_contract_spec.rb @@ -95,8 +95,7 @@ module Storages "Please verify that the connection is functioning properly." expect(subject.errors.to_hash).to eq({ password: [message] }) - # twice due to HTTPX retry plugin being enabled. - expect(credentials_request).to have_been_made.twice + expect(credentials_request).to have_been_made end end diff --git a/modules/storages/spec/common/storages/adapters/providers/nextcloud/queries/download_link_query_spec.rb b/modules/storages/spec/common/storages/adapters/providers/nextcloud/queries/download_link_query_spec.rb index ed9da57e56e..b5ab915fed1 100644 --- a/modules/storages/spec/common/storages/adapters/providers/nextcloud/queries/download_link_query_spec.rb +++ b/modules/storages/spec/common/storages/adapters/providers/nextcloud/queries/download_link_query_spec.rb @@ -82,14 +82,12 @@ module Storages end context "with outbound request returning 200 and an empty body" do - it "refreshes the token and returns success", vcr: "nextcloud/download_link_query_unauthorized" do + it "fails with code invalid_response", vcr: "nextcloud/download_link_query_unauthorized" do download_link = subject.call(auth_strategy:, input_data:) - expect(download_link).to be_success + expect(download_link).to be_failure - uri = download_link.value! - expect(uri.host).to eq("nextcloud.local") - expect(uri.path) - .to match(/index.php\/apps\/integration_openproject\/direct\/[0-9a-zA-Z]+\/#{file_link.origin_name}/) + error = download_link.failure + expect(error.code).to eq(:invalid_response) end end end diff --git a/modules/storages/spec/contracts/storages/storages/shared_contract_examples.rb b/modules/storages/spec/contracts/storages/storages/shared_contract_examples.rb index b4479c92e5a..b6a08f63096 100644 --- a/modules/storages/spec/contracts/storages/storages/shared_contract_examples.rb +++ b/modules/storages/spec/contracts/storages/storages/shared_contract_examples.rb @@ -192,8 +192,7 @@ RSpec.shared_examples_for "nextcloud storage contract", :storage_server_helpers, it "retries failed request once" do contract.validate - # twice due to HTTPX retry plugin being enabled. - expect(stub_server_capabilities).to have_been_made.twice + expect(stub_server_capabilities).to have_been_made end end @@ -204,8 +203,7 @@ RSpec.shared_examples_for "nextcloud storage contract", :storage_server_helpers, it "retries failed request once" do contract.validate - # twice due to HTTPX retry plugin being enabled. - expect(stub_config_check).to have_been_made.twice + expect(stub_config_check).to have_been_made end end end diff --git a/modules/storages/spec/support/fixtures/vcr_cassettes/auth/nextcloud/refresh_token.yml b/modules/storages/spec/support/fixtures/vcr_cassettes/auth/nextcloud/refresh_token.yml new file mode 100644 index 00000000000..12f2c24ea33 --- /dev/null +++ b/modules/storages/spec/support/fixtures/vcr_cassettes/auth/nextcloud/refresh_token.yml @@ -0,0 +1,451 @@ +--- +http_interactions: +- request: + method: get + uri: https://nextcloud.local/ocs/v1.php/cloud/user + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - OpenProject 17.2.0 HTTPX Client + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer + response: + status: + code: 401 + message: Unauthorized + headers: + Cache-Control: + - no-cache, no-store, must-revalidate + Content-Security-Policy: + - default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' + Content-Type: + - application/xml; charset=utf-8 + Date: + - Mon, 02 Mar 2026 16:08:46 GMT + Feature-Policy: + - autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone + 'none';payment 'none' + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.66 (Debian) + Set-Cookie: + - ocijlj3fsmm9=5b65855c97c459db69c61861f926501f; path=/; secure; HttpOnly; SameSite=Lax, + oc_sessionPassphrase=eeaqxrN0jZy61LxpAwJ7W1RwdhNnTJ4eE53GnTsym79KnC%2Fd%2Bs7VxPDGJ8sdlqgXt84b5inOVqF2cOYkCPIXrzCHtSBkF852ekuoKW9e0oBoEgxmnsccJbD5uSRwv9h%2B; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=5b65855c97c459db69c61861f926501f; + path=/; secure; HttpOnly; SameSite=Lax, __Host-nc_sameSiteCookielax=true; + path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax, + __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, + 31-Dec-2100 23:59:59 GMT; SameSite=strict, ocijlj3fsmm9=5b65855c97c459db69c61861f926501f; + path=/; secure; HttpOnly; SameSite=Lax, nc_username=deleted; expires=Thu, + 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_token=deleted; expires=Thu, + 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_session_id=deleted; + expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_username=deleted; + expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly, + nc_token=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; + secure; HttpOnly, nc_session_id=deleted; expires=Thu, 01 Jan 1970 00:00:01 + GMT; Max-Age=0; path=/; secure; HttpOnly + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.4.18 + X-Request-Id: + - mpcjYVvLLOeYrNKfHGA4 + X-Robots-Tag: + - noindex, nofollow + Content-Length: + - '230' + body: + encoding: UTF-8 + string: | + + + + failure + 997 + Current user is not logged in + + + + + + recorded_at: Mon, 02 Mar 2026 16:08:46 GMT +- request: + method: post + uri: https://nextcloud.local/index.php/apps/oauth2/api/v1/token + body: + encoding: ASCII-8BIT + string: grant_type=refresh_token&scope=&refresh_token= + headers: + User-Agent: + - OpenProject 17.2.0 HTTPX Client + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Authorization: + - Basic + Content-Type: + - application/x-www-form-urlencoded + Content-Length: + - '174' + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - no-cache, no-store, must-revalidate + Content-Encoding: + - gzip + Content-Security-Policy: + - default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 02 Mar 2026 16:08:46 GMT + Feature-Policy: + - autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone + 'none';payment 'none' + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.66 (Debian) + Set-Cookie: + - ocijlj3fsmm9=b3b43751df79bb2be113552e0da3c8ef; path=/; secure; HttpOnly; SameSite=Lax, + oc_sessionPassphrase=jjRcONBYop3Z1ttvqVG8PoNd928I2MOEQ5wvxPYxmsvSM07Ow5FKvhg1JnCFLwnYSlfMCU5FaEQ35ZGSwULlYqJ8Fndsqn5nk48zjTTdXiedpPMlZDU%2F%2FkWaYG%2BChSAK; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=b3b43751df79bb2be113552e0da3c8ef; + path=/; secure; HttpOnly; SameSite=Lax, __Host-nc_sameSiteCookielax=true; + path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax, + __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, + 31-Dec-2100 23:59:59 GMT; SameSite=strict, ocijlj3fsmm9=b3b43751df79bb2be113552e0da3c8ef; + path=/; secure; HttpOnly; SameSite=Lax + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.4.18 + X-Request-Id: + - JewgU7FS0QDRXDmGLnWf + X-Robots-Tag: + - noindex, nofollow + Content-Length: + - '270' + body: + encoding: UTF-8 + string: '{"access_token":"","token_type":"Bearer","expires_in":3600,"refresh_token":"","user_id":"admin"}' + recorded_at: Mon, 02 Mar 2026 16:08:46 GMT +- request: + method: get + uri: https://nextcloud.local/ocs/v1.php/cloud/user + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - OpenProject 17.2.0 HTTPX Client + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - no-cache, no-store, must-revalidate + Content-Encoding: + - gzip + Content-Security-Policy: + - default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' + Content-Type: + - application/xml; charset=utf-8 + Date: + - Mon, 02 Mar 2026 16:08:46 GMT + Feature-Policy: + - autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone + 'none';payment 'none' + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.66 (Debian) + Set-Cookie: + - ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; path=/; secure; HttpOnly; SameSite=Lax, + oc_sessionPassphrase=BzSroJ7oX15ip9xi5Y8Na48QPwG3mu7fpYl%2B2wlSxOFqh%2BUEIwflqvkgtdvr7LpA5zGPXrat0VvAckP95BkTm7ZcK8JJZEwKYK7shleHPn1ZaIzThTiJWuLuuRjnmxp%2F; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, __Host-nc_sameSiteCookielax=true; + path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax, + __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, + 31-Dec-2100 23:59:59 GMT; SameSite=strict, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, nc_username=deleted; expires=Thu, + 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_token=deleted; expires=Thu, + 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_session_id=deleted; + expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_username=deleted; + expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly, + nc_token=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; + secure; HttpOnly, nc_session_id=deleted; expires=Thu, 01 Jan 1970 00:00:01 + GMT; Max-Age=0; path=/; secure; HttpOnly, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.4.18 + X-Request-Id: + - 9KY3Gces4KNzsrkeOsWU + X-Robots-Tag: + - noindex, nofollow + X-User-Id: + - admin + Content-Length: + - '691' + body: + encoding: UTF-8 + string: | + + + + ok + 100 + OK + + + + + 1 + /var/www/html/data/admin + admin + 1772444624 + 1772467726 + 1772467726000 + Database + + + -3 + 60872728 + -3 + 0 + -3 + + + v2-federated + + v2-federated + + + admin + admin + v2-federated + + v2-local +
+ v2-local + + v2-local + + v2-local + + v2-local + + v2-local + + v2-local + + v2-local + + v2-local + + v2-local + 1 + v2-local + + v2-federated + + admin + + en + + Europe/Busingen + + + 1 + 1 + +
+
+ recorded_at: Mon, 02 Mar 2026 16:08:46 GMT +- request: + method: get + uri: https://nextcloud.local/ocs/v1.php/cloud/user + body: + encoding: US-ASCII + string: '' + headers: + User-Agent: + - OpenProject 17.2.0 HTTPX Client + Accept: + - "*/*" + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - no-cache, no-store, must-revalidate + Content-Encoding: + - gzip + Content-Security-Policy: + - default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none' + Content-Type: + - application/xml; charset=utf-8 + Date: + - Mon, 02 Mar 2026 16:08:46 GMT + Feature-Policy: + - autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone + 'none';payment 'none' + Referrer-Policy: + - no-referrer + Server: + - Apache/2.4.66 (Debian) + Set-Cookie: + - ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; path=/; secure; HttpOnly; SameSite=Lax, + oc_sessionPassphrase=BzSroJ7oX15ip9xi5Y8Na48QPwG3mu7fpYl%2B2wlSxOFqh%2BUEIwflqvkgtdvr7LpA5zGPXrat0VvAckP95BkTm7ZcK8JJZEwKYK7shleHPn1ZaIzThTiJWuLuuRjnmxp%2F; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, __Host-nc_sameSiteCookielax=true; + path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax, + __Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, + 31-Dec-2100 23:59:59 GMT; SameSite=strict, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, nc_username=deleted; expires=Thu, + 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_token=deleted; expires=Thu, + 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_session_id=deleted; + expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; secure; HttpOnly, nc_username=deleted; + expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly, + nc_token=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0; path=/; + secure; HttpOnly, nc_session_id=deleted; expires=Thu, 01 Jan 1970 00:00:01 + GMT; Max-Age=0; path=/; secure; HttpOnly, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax, ocijlj3fsmm9=8c15d45120403fd0c95c1c8d23c250b1; + path=/; secure; HttpOnly; SameSite=Lax + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Powered-By: + - PHP/8.4.18 + X-Request-Id: + - 9KY3Gces4KNzsrkeOsWU + X-Robots-Tag: + - noindex, nofollow + X-User-Id: + - admin + Content-Length: + - '691' + body: + encoding: UTF-8 + string: | + + + + ok + 100 + OK + + + + + 1 + /var/www/html/data/admin + admin + 1772444624 + 1772467726 + 1772467726000 + Database + + + -3 + 60872728 + -3 + 0 + -3 + + + v2-federated + + v2-federated + + + admin + admin + v2-federated + + v2-local +
+ v2-local + + v2-local + + v2-local + + v2-local + + v2-local + + v2-local + + v2-local + + v2-local + + v2-local + 1 + v2-local + + v2-federated + + admin + + en + + Europe/Busingen + + + 1 + 1 + +
+
+ recorded_at: Mon, 02 Mar 2026 16:08:46 GMT +recorded_with: VCR 6.4.0 diff --git a/modules/team_planner/config/locales/crowdin/js-fr.yml b/modules/team_planner/config/locales/crowdin/js-fr.yml index 4d8a0c78286..c96442a127a 100644 --- a/modules/team_planner/config/locales/crowdin/js-fr.yml +++ b/modules/team_planner/config/locales/crowdin/js-fr.yml @@ -19,7 +19,7 @@ fr: today: 'Aujourd''hui' drag_here_to_remove: 'Faites glisser ici pour supprimer le responsable et les dates de début et de fin.' cannot_drag_here: 'Impossible de déplacer le lot de travail en raison de restrictions d''autorisation ou d''édition.' - cannot_drag_to_non_working_day: 'Ce lot de travaux ne peut pas démarrer/terminer sur un jour non ouvré.' + cannot_drag_to_non_working_day: 'Ce lot de travail ne peut pas démarrer/terminer sur un jour non ouvré.' quick_add: empty_state: 'Utilisez le champ de recherche pour trouver des lots de travaux et faites-les glisser vers le planificateur pour l''assigner à quelqu''un et définir des dates de début et de fin.' search_placeholder: 'Rechercher...' diff --git a/modules/two_factor_authentication/config/locales/crowdin/ro.yml b/modules/two_factor_authentication/config/locales/crowdin/ro.yml index 6c8ef8748d3..7feb54c4620 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/ro.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/ro.yml @@ -178,7 +178,7 @@ ro: label_expiration_hint: "%{date} sau la deconectare" label_actions: "Acțiuni" label_confirmed: "Confirmat" - button_continue: "Continuă" + button_continue: "Continuaţi" button_make_default: "Marcați ca implicit" label_unverified_phone: "Telefonul mobil nu a fost încă verificat" notice_phone_number_format: "Te rog să introduci numărul în următorul format: +XX XXXXXXXX." diff --git a/modules/two_factor_authentication/config/locales/crowdin/ru.yml b/modules/two_factor_authentication/config/locales/crowdin/ru.yml index 80fa7dc0e25..bdba7c151d8 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/ru.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/ru.yml @@ -178,7 +178,7 @@ ru: label_expiration_hint: "%{date} или при выходе из системы" label_actions: "Действия" label_confirmed: "Подтвержден" - button_continue: "Продолжить" + button_continue: "Далее" button_make_default: "Задать по умолчанию" label_unverified_phone: "Сотовый телефон еще не подтвержден" notice_phone_number_format: "Введите номер в следующем формате: +XX XXXXXXXX." diff --git a/modules/two_factor_authentication/config/locales/crowdin/uk.yml b/modules/two_factor_authentication/config/locales/crowdin/uk.yml index 944c3bc181d..8649db79efe 100644 --- a/modules/two_factor_authentication/config/locales/crowdin/uk.yml +++ b/modules/two_factor_authentication/config/locales/crowdin/uk.yml @@ -119,7 +119,7 @@ uk: failed_to_delete: "Не вдалося видалити пристрій 2FA." is_default_cannot_delete: "Пристрій позначено як типовий і його не можна видалити через активну політику безпеки. Перед видаленням позначте інший пристрій як стандартний." not_existing: "Для вашого облікового запису не зареєстровано жодного пристрою 2FA." - 2fa_from_input: Введіть код, отриманий на пристрій %{device_name}, щоб підтвердити свою особу. + 2fa_from_input: Введіть код, що надійшов на пристрій %{device_name}, щоб підтвердити свою особу. 2fa_from_webauthn: Укажіть пристрій WebAuthn %{device_name}. Якщо це USB-пристрій, переконайтеся, що його підключено, і торкніться його. Потім натисніть кнопку входу. webauthn: title: "WebAuthn" diff --git a/modules/webhooks/spec/services/webhooks/outgoing/request_webhook_service_spec.rb b/modules/webhooks/spec/services/webhooks/outgoing/request_webhook_service_spec.rb index 3ad13e89f60..1d826303b63 100644 --- a/modules/webhooks/spec/services/webhooks/outgoing/request_webhook_service_spec.rb +++ b/modules/webhooks/spec/services/webhooks/outgoing/request_webhook_service_spec.rb @@ -43,7 +43,7 @@ RSpec.describe Webhooks::Outgoing::RequestWebhookService, :webmock, type: :model describe "#call!" do context "when the request is successful" do before do - stub_request(:post, ssrf_resolved_url(webhook.url)) + stub_request(:post, webhook.url) .with(body: "body", headers: { "X-Custom" => "header" }) .to_return(status: 200, body: "OK", headers: { "Content-Type" => "application/json" }) end @@ -52,7 +52,7 @@ RSpec.describe Webhooks::Outgoing::RequestWebhookService, :webmock, type: :model it "makes a POST request to the webhook URL with the given body and headers" do subject - expect(WebMock).to have_requested(:post, ssrf_resolved_url(webhook.url)) + expect(WebMock).to have_requested(:post, webhook.url) .with(body: "body", headers: { "X-Custom" => "header", "Host" => "example.net" }).once end @@ -67,11 +67,24 @@ RSpec.describe Webhooks::Outgoing::RequestWebhookService, :webmock, type: :model expect(log.response_body).to eq("OK") expect(log.url).to eq(webhook.url) end + + it "connects to the original hostname while routing through the resolved safe IP address" do + http_start_args = nil + allow(Net::HTTP).to receive(:start).and_wrap_original do |original, *args, **kwargs, &block| + http_start_args = { host: args[0], options: kwargs } + original.call(*args, **kwargs, &block) + end + + subject + + expect(http_start_args[:host]).to eq("example.net") + expect(http_start_args[:options]).to include(ipaddr: WithSsrfWebhookStubsMixin::SSRF_TEST_IP) + end end context "when the request times out" do before do - stub_request(:post, ssrf_resolved_url(webhook.url)).to_timeout + stub_request(:post, webhook.url).to_timeout end it "re-raises the timeout error while still creating a log entry" do @@ -83,7 +96,7 @@ RSpec.describe Webhooks::Outgoing::RequestWebhookService, :webmock, type: :model context "when request_url fails with SSL errors" do before do - stub_request(:post, ssrf_resolved_url(webhook.url)).to_raise(OpenSSL::SSL::SSLError) + stub_request(:post, webhook.url).to_raise(OpenSSL::SSL::SSLError) end it "still logs the exception" do @@ -135,7 +148,7 @@ RSpec.describe Webhooks::Outgoing::RequestWebhookService, :webmock, type: :model context "when an unexpected error occurs" do before do - stub_request(:post, ssrf_resolved_url(webhook.url)).to_raise(StandardError.new("something went wrong")) + stub_request(:post, webhook.url).to_raise(StandardError.new("something went wrong")) end it "creates a log entry" do diff --git a/modules/webhooks/spec/workers/attachment_webhook_job_spec.rb b/modules/webhooks/spec/workers/attachment_webhook_job_spec.rb index 63f9885e99f..04b0d8366b6 100644 --- a/modules/webhooks/spec/workers/attachment_webhook_job_spec.rb +++ b/modules/webhooks/spec/workers/attachment_webhook_job_spec.rb @@ -52,7 +52,7 @@ RSpec.describe AttachmentWebhookJob, :webmock, type: :job do end let(:stub) do - stub_request(:post, ssrf_resolved_url(stubbed_url)) + stub_request(:post, stubbed_url) .with( body: hash_including( "action" => event, diff --git a/modules/webhooks/spec/workers/project_webhook_job_spec.rb b/modules/webhooks/spec/workers/project_webhook_job_spec.rb index 63813ee0136..5bfc2864c65 100644 --- a/modules/webhooks/spec/workers/project_webhook_job_spec.rb +++ b/modules/webhooks/spec/workers/project_webhook_job_spec.rb @@ -56,7 +56,7 @@ RSpec.describe ProjectWebhookJob, :webmock, type: :job do end let(:stub) do - stub_request(:post, ssrf_resolved_url(stubbed_url)) + stub_request(:post, stubbed_url) .with( body: hash_including( "action" => event, diff --git a/modules/webhooks/spec/workers/time_entry_webhook_job_spec.rb b/modules/webhooks/spec/workers/time_entry_webhook_job_spec.rb index a99a56b546e..affb68d46a8 100644 --- a/modules/webhooks/spec/workers/time_entry_webhook_job_spec.rb +++ b/modules/webhooks/spec/workers/time_entry_webhook_job_spec.rb @@ -53,7 +53,7 @@ RSpec.describe TimeEntryWebhookJob, :webmock, type: :job do end let(:stub) do - stub_request(:post, ssrf_resolved_url(stubbed_url)) + stub_request(:post, stubbed_url) .with( body: hash_including( "action" => event, diff --git a/modules/webhooks/spec/workers/work_package_comment_webhook_job_spec.rb b/modules/webhooks/spec/workers/work_package_comment_webhook_job_spec.rb index 43908098b1b..843c9cd4bcd 100644 --- a/modules/webhooks/spec/workers/work_package_comment_webhook_job_spec.rb +++ b/modules/webhooks/spec/workers/work_package_comment_webhook_job_spec.rb @@ -53,7 +53,7 @@ RSpec.describe WorkPackageCommentWebhookJob, :webmock, type: :model do end let(:stub) do - stub_request(:post, ssrf_resolved_url(stubbed_url)).with( + stub_request(:post, stubbed_url).with( body: hash_including( "action" => event_name, "activity" => hash_including( diff --git a/modules/webhooks/spec/workers/work_package_webhook_job_spec.rb b/modules/webhooks/spec/workers/work_package_webhook_job_spec.rb index d38f97b707b..40c6eff15af 100644 --- a/modules/webhooks/spec/workers/work_package_webhook_job_spec.rb +++ b/modules/webhooks/spec/workers/work_package_webhook_job_spec.rb @@ -56,7 +56,7 @@ RSpec.describe WorkPackageWebhookJob, :webmock, type: :model do end let(:stub) do - stub_request(:post, ssrf_resolved_url(stubbed_url)) + stub_request(:post, stubbed_url) .with( body: hash_including( "action" => event, diff --git a/modules/xls_export/config/locales/crowdin/zh-CN.yml b/modules/xls_export/config/locales/crowdin/zh-CN.yml index 59230e603ec..31c8d3cdd71 100644 --- a/modules/xls_export/config/locales/crowdin/zh-CN.yml +++ b/modules/xls_export/config/locales/crowdin/zh-CN.yml @@ -13,4 +13,4 @@ zh-CN: xls_with_relations: "带关系的 XLS" xls_export: child_of: 此项的子项 - parent_of: 此项的父级 + parent_of: 此项的父项 diff --git a/nix/gemset.nix b/nix/gemset.nix index db77431ee4e..9b80c3d84d8 100644 --- a/nix/gemset.nix +++ b/nix/gemset.nix @@ -527,15 +527,15 @@ version = "1.0.25"; }; carrierwave = { - dependencies = ["activemodel" "activesupport" "mime-types" "ssrf_filter"]; + dependencies = ["activemodel" "activesupport" "addressable" "image_processing" "marcel" "mini_mime" "ssrf_filter"]; groups = ["default"]; platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "055i3ybjv9n9hqaazxn3d9ibqhlwh93d4hdlwbpjjfy8qbrz6hiw"; + sha256 = "dy6364hzm3ijfzxgwdi3ny66sc24gyqj1wvsx60zj7xzmy3qsyh1"; type = "gem"; }; - version = "1.3.2"; + version = "2.2.6"; }; carrierwave_direct = { dependencies = ["carrierwave" "fog-aws"]; @@ -1695,10 +1695,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "1vhp6lifwvqs2b0a276lj61n86c1l7d1xiswjj2w23f54gl51mpk"; + sha256 = "dhmj4zvnbs3kirgs6s2d933hsds9pim6hf88rk807l1m8km2n091"; type = "gem"; }; - version = "1.0.0"; + version = "1.0.4"; }; messagebird-rest = { groups = ["default" "opf_plugins"]; @@ -3314,10 +3314,10 @@ platforms = []; source = { remotes = ["https://rubygems.org"]; - sha256 = "03f49f54837e407d43ee93ec733a8a94dc1bcf8185647ac61606e63aaedaa0db"; + sha256 = "632vjyxwh673082knymg3j82ml22f2fxw5z2m31h2816q3cm6mw1"; type = "gem"; }; - version = "1.0.8"; + version = "1.3.0"; }; stackprof = { groups = ["development" "test"]; diff --git a/package-lock.json b/package-lock.json index 5c9d870c25d..cd5b9d00cbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1040,11 +1040,13 @@ "dev": true }, "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "dev": true, - "license": "(MPL-2.0 OR Apache-2.0)", + "engines": { + "node": ">=20" + }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } diff --git a/script/github_pr_errors b/script/github_pr_errors index 82f9abc693d..fcfb79f81bc 100755 --- a/script/github_pr_errors +++ b/script/github_pr_errors @@ -17,14 +17,14 @@ require "yaml" require "httpx" require "cgi" +require_relative "support/github_actions_failures" + GITHUB_API_OPENPROJECT_PREFIX = "https://api.github.com/repos/opf/openproject" GITHUB_HTML_OPENPROJECT_PREFIX = "https://github.com/opf/openproject" RAILS_ROOT = Pathname.new(__dir__).dirname EXCLUDED_JOB_NAMES = %w[eslint rubocop].freeze -if !ENV["GITHUB_USERNAME"] - raise "Missing GITHUB_USERNAME env" -elsif !ENV["GITHUB_TOKEN"] +if !ENV["GITHUB_TOKEN"] raise "Missing GITHUB_TOKEN env, go to https://github.com/settings/tokens and create one with 'repo' access" end @@ -179,8 +179,9 @@ end def http HTTPX .plugin(:follow_redirects) - .plugin(:basic_auth) - .basic_auth(ENV.fetch("GITHUB_USERNAME"), ENV.fetch("GITHUB_TOKEN")) + .with_headers( + "Authorization" => "Bearer #{ENV.fetch('GITHUB_TOKEN')}" + ) end def get_http(path) @@ -337,170 +338,6 @@ Report = Data.define( end end -class Error - attr_accessor :location, :page_html, :page_screenshot, :tests_group, :loading_error -end - -# rubocop:disable Layout/LineLength -# Looks like this in the job log: -# Process 28: TEST_ENV_NUMBER=28 RUBYOPT=-I/usr/local/bundle/bundler/gems/turbo_tests-3148ae6c3482/lib -r/usr/local/bundle/gems/bundler-2.5.23/lib/bundler/setup -W0 RSPEC_SILENCE_FILTER_ANNOUNCEMENTS=1 /usr/local/bundle/gems/bundler-2.5.23/exe/bundle exec rspec --seed 52674 --format TurboTests::JsonRowsFormatter --out tmp/test-pipes/subprocess-28 --format ParallelTests::RSpec::RuntimeLogger --out spec/support/turbo_runtime_features.log spec/features/api_docs/index_spec.rb spec/features/custom_fields/reorder_options_spec.rb spec/features/projects/projects_portfolio_spec.rb spec/features/projects/template_spec.rb spec/features/versions/edit_spec.rb spec/features/work_packages/details/markdown/description_editor_spec.rb spec/features/work_packages/table/hierarchy/hierarchy_parent_below_spec.rb spec/features/work_packages/table/inline_create/inline_create_refresh_spec.rb spec/features/work_packages/table/invalid_query_spec.rb spec/features/work_packages/tabs/activity_revisions_spec.rb -# rubocop:enable Layout/LineLength -class TestsGroup - attr_accessor :test_env_number, :seed, :files - - def initialize - @files = [] - end - - def include_error?(error) - return false if error.location.nil? - - files.any? { |file| error.location.include?(file) } - end - - def inspect - "#<#{self.class} @test_env_number=#{test_env_number} @seed=#{seed} (#{files.count} files)>" - end -end - -class JobErrorsFinder - SPEC_FAILURES_PATTERN = %r{^\S+ rspec (\S+) #.+$} - SPEC_LOADING_ERRORS_PATTERN = %r{^\S+ An error occurred while loading (\S+)\.\r?$} - SCREENSHOT_PATTERN = /\{"message":"Screenshot captured for failed feature test"[^\n]+$/ - TESTS_GROUP_PATTERN = /Process \d+: TEST_ENV_NUMBER=\d+ [^\n]+$/ - BRANCH_MERGE_PATTERN = /Merge \w{40} into (\w{40})$/ - - attr_reader :failures_explanation, :merge_branch_sha - - def self.scan_logs(report, logs) - finder = new - logs.each do |log| - finder.scan_log(log) - end - report.with( - errors: finder.errors, - failures_explanation: finder.failures_explanation, - merge_branch_sha: finder.merge_branch_sha - ) - end - - def scan_log(log) - find_failures(log) - find_failures_explanation(log) - find_loading_errors(log) - find_screenshots(log) - find_tests_groups(log) - find_merge_branch_info(log) - end - - def errors - @errors.values - end - - protected - - def initialize - @errors = {} - end - - def create_error(location) - return if location.nil? - - error = Error.new - error.location = location - @errors[location] ||= error - end - - def with_matching_error(location: nil, id: nil) - error = @errors[id] || @errors[location] - yield error if error && block_given? - error - end - - def find_failures(log) - log.scan(SPEC_FAILURES_PATTERN) - .flatten - .uniq - .sort - .each do |rerun_location| - create_error(rerun_location) - end - end - - def find_failures_explanation(log) - explanations = [] - log.split("\n").each do |line| - if line.end_with?("Failures:") .. line.end_with?("Failed examples:") - explanations << line - end - end - explanations.map! { it[29..] } # Remove leading timestamp (like "2024-02-05T08:37:54.5175930Z") - explanations.reject! do |line| - line == "Failures:" || - line == "Failed examples:" || - line.include?("gems/rspec-retry-") || - line.include?("gems/webmock-") - end - @failures_explanation = explanations.join("\n") - end - - def find_loading_errors(log) - log.scan(SPEC_LOADING_ERRORS_PATTERN) - .flatten - .uniq - .sort - .each do |location| - error = create_error(location) - error.loading_error = true - end - end - - def find_screenshots(log) - log.scan(SCREENSHOT_PATTERN) - .map { JSON.parse it } - .each do |screenshot_info| - id = screenshot_info["test_id"] - location = screenshot_info["test_location"] - with_matching_error(location:, id:) do |error| - error.page_html = screenshot_info["html"] - error.page_screenshot = screenshot_info["image"] - end - end - end - - def find_tests_groups(log) - tests_groups = log - .scan(TESTS_GROUP_PATTERN) - .flatten - .map { build_tests_group_from_command(it) } - - errors.each do |error| - error.tests_group = tests_groups.find { it.include_error?(error) } - end - end - - def find_merge_branch_info(log) - merge_branch_sha = log.scan(BRANCH_MERGE_PATTERN).flatten.first - @merge_branch_sha = merge_branch_sha if merge_branch_sha - end - - def build_tests_group_from_command(line) - tests_group = TestsGroup.new - parts = line.split - while parts.any? - case part = parts.shift - when /^TEST_ENV_NUMBER=/ - tests_group.test_env_number = part.delete_prefix("TEST_ENV_NUMBER=") - when "--seed" - tests_group.seed = parts.shift - when /_spec.rb$/ - tests_group.files << part - end - end - tests_group - end -end - class Formatter def initialize(compact: false) @compact = compact @@ -798,7 +635,12 @@ formatter = Formatter.new(compact: Options.compact) report = get_failed_jobs_logs(report, formatter) -report = JobErrorsFinder.scan_logs(report, report.failed_job_logs) +scan_result = GithubActionsFailures::JobErrorsFinder.scan_logs(report.failed_job_logs) +report = report.with( + errors: scan_result.errors, + failures_explanation: scan_result.failures_explanation, + merge_branch_sha: scan_result.merge_branch_sha +) case report.run_status when "completed" diff --git a/script/report_out_of_hours_ci_failures b/script/report_out_of_hours_ci_failures new file mode 100755 index 00000000000..afd2ff9bea2 --- /dev/null +++ b/script/report_out_of_hours_ci_failures @@ -0,0 +1,371 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "rubygems" +require "bundler" +Bundler.setup(:default, :development) + +require "json" +require "optparse" +require "pathname" +require "time" +require "yaml" +require "httpx" +require "active_support/all" + +require_relative "support/github_actions_failures" + +GITHUB_API_OPENPROJECT_PREFIX = "https://api.github.com/repos/opf/openproject" +RAILS_ROOT = Pathname.new(__dir__).dirname +TEST_WORKFLOW_FILE = "test-core.yml" +PRIMARY_BRANCH_PATTERN = /\A(?:dev|release\/.+)\z/ +TEST_STEP_NAMES = ["Unit tests", "Feature tests"].freeze +DEFAULT_TIMEZONE = "Europe/Berlin" +DEFAULT_LOOKBACK_DAYS = 30 + +module OutOfHoursCiFailures + RunSummary = Data.define(:run_id, :run_number, :html_url, :head_branch, :event, :created_at, :failed_steps, :errors) + SpecSummary = Data.define( + :location, + :test_types, + :out_of_hours_count, + :in_hours_count, + :branches, + :first_seen_at, + :last_seen_at, + :out_of_hours_runs, + :in_hours_runs, + :classification + ) + + class Options + DEFAULTS = { + days: DEFAULT_LOOKBACK_DAYS, + include_pr_runs: false, + json: false, + timezone: DEFAULT_TIMEZONE, + workflow: TEST_WORKFLOW_FILE + }.freeze + + class << self + # rubocop:disable Metrics/AbcSize + def parse!(argv) + options = DEFAULTS.dup + + OptionParser.new do |parser| + parser.banner = "Usage: script/report_out_of_hours_ci_failures [options]" + + parser.on("--days DAYS", Integer, "Look back this many days (default: #{DEFAULT_LOOKBACK_DAYS})") do |value| + options[:days] = value + end + + parser.on("--include-pr-runs", "Include pull_request runs in the report") do + options[:include_pr_runs] = true + end + + parser.on("--json", "Output JSON instead of a table") do + options[:json] = true + end + + parser.on( + "--timezone NAME", + "Timezone for the in-hours/out-of-hours classification (default: #{DEFAULT_TIMEZONE})" + ) do |value| + options[:timezone] = value + end + + parser.on("--workflow FILE", "Workflow file name to inspect (default: #{TEST_WORKFLOW_FILE})") do |value| + options[:workflow] = value + end + + parser.on("-h", "--help", "Print this help") do + puts parser + exit + end + end.parse!(argv) + + options + end + # rubocop:enable Metrics/AbcSize + end + end + + class GithubClient + def initialize(cache_dir:) + @cache_dir = cache_dir + end + + def workflow_runs(workflow_file:, page:) + get_json("actions/workflows/#{workflow_file}/runs?status=completed&per_page=100&page=#{page}") + end + + def jobs(run_id) + get_json("actions/runs/#{run_id}/jobs") + end + + def log(job_id) + cached("job_#{job_id}.log") do + get_http("actions/jobs/#{job_id}/logs") + end + end + + private + + def http + @http ||= HTTPX + .plugin(:follow_redirects) + .with_headers( + "Authorization" => "Bearer #{ENV.fetch('GITHUB_TOKEN')}" + ) + end + + def github_url(path) + path.start_with?("http") ? path : "#{GITHUB_API_OPENPROJECT_PREFIX}/#{path}" + end + + def get_http(path) + response = http.get(github_url(path)) + response.raise_for_status + response.to_s + end + + def get_json(path) + JSON.parse(get_http(path)) + rescue HTTPX::HTTPError => e + body = e.response.json + raise "#{body['message']} (see #{body['documentation_url']})" + end + + def cached(name) + path = @cache_dir.join(name) + return path.read if path.file? + + content = yield + path.dirname.mkpath + path.write(content) + content + end + end + + class ReportBuilder + def initialize(options:, client:) + @options = options + @client = client + @timezone = ActiveSupport::TimeZone[options[:timezone]] || raise("Unknown timezone #{options[:timezone]}") + @since = Time.current - options[:days].days + end + + # rubocop:disable Metrics/AbcSize + def build + spec_runs = Hash.new { |hash, key| hash[key] = [] } + + each_relevant_run do |workflow_run, failed_jobs| + logs = failed_jobs.map { |job| @client.log(job.fetch("id")) } + scan_result = GithubActionsFailures::JobErrorsFinder.scan_logs(logs) + next if scan_result.errors.empty? + + failed_steps = failed_jobs.flat_map { failing_test_steps(it) }.uniq.sort + summary = RunSummary.new( + run_id: workflow_run.fetch("id"), + run_number: workflow_run.fetch("run_number"), + html_url: workflow_run.fetch("html_url"), + head_branch: workflow_run.fetch("head_branch"), + event: workflow_run.fetch("event"), + created_at: parse_time(workflow_run.fetch("created_at")), + failed_steps:, + errors: scan_result.errors.map(&:location).sort + ) + + summary.errors.each do |location| + spec_runs[location] << summary + end + end + + spec_runs + .sort_by { |location, _| location } + .map { |location, runs| summarize(location, runs) } + .sort_by { |summary| [-summary.out_of_hours_count, -summary.in_hours_count, summary.location] } + end + # rubocop:enable Metrics/AbcSize + + private + + # rubocop:disable Metrics/AbcSize + def each_relevant_run + page = 1 + done = false + + loop do + response = @client.workflow_runs(workflow_file: @options[:workflow], page:) + runs = response.fetch("workflow_runs") + break if runs.empty? + + runs.each do |workflow_run| + created_at = parse_time(workflow_run.fetch("created_at")) + if created_at < @since + done = true + break + end + + next unless include_run?(workflow_run) + + jobs = @client.jobs(workflow_run.fetch("id")).fetch("jobs") + failed_jobs = jobs.select { test_job_failure?(it) } + next if failed_jobs.empty? + + yield workflow_run, failed_jobs + end + + page += 1 + break if done + end + end + # rubocop:enable Metrics/AbcSize + + def include_run?(workflow_run) + branch = workflow_run.fetch("head_branch") + return false unless branch.match?(PRIMARY_BRANCH_PATTERN) || @options[:include_pr_runs] + return false if workflow_run.fetch("event") == "pull_request" && !@options[:include_pr_runs] + + true + end + + def test_job_failure?(job) + job.fetch("conclusion") == "failure" && failing_test_steps(job).any? + end + + def failing_test_steps(job) + job + .fetch("steps") + .filter_map { |step| step["name"] if step["conclusion"] == "failure" && TEST_STEP_NAMES.include?(step["name"]) } + end + + # rubocop:disable Metrics/AbcSize + def summarize(location, runs) + out_of_hours_runs, in_hours_runs = runs.partition { out_of_hours?(it.created_at) } + branches = runs.map(&:head_branch).uniq.sort + test_types = runs.flat_map(&:failed_steps).uniq.sort + + SpecSummary.new( + location:, + test_types:, + out_of_hours_count: out_of_hours_runs.count, + in_hours_count: in_hours_runs.count, + branches:, + first_seen_at: runs.min_by(&:created_at).created_at, + last_seen_at: runs.max_by(&:created_at).created_at, + out_of_hours_runs: out_of_hours_runs.map(&:html_url), + in_hours_runs: in_hours_runs.map(&:html_url), + classification: classify(out_of_hours_runs:, in_hours_runs:, branches:) + ) + end + # rubocop:enable Metrics/AbcSize + + def classify(out_of_hours_runs:, in_hours_runs:, branches:) + total = out_of_hours_runs.count + in_hours_runs.count + return "needs manual review" if total < 2 + return "likely regression" if branches.one? + return "likely datetime-sensitive" if in_hours_runs.empty? + + ratio = out_of_hours_runs.count.to_f / total + return "likely datetime-sensitive" if ratio >= 0.75 && branches.many? + + "likely generic flaky" + end + + def out_of_hours?(time) + local = time.in_time_zone(@timezone) + local.saturday? || local.sunday? || local.hour < 9 || local.hour >= 18 + end + + def parse_time(value) + Time.iso8601(value) + end + end + + class Formatter + def initialize(timezone:) + @timezone = timezone + end + + def print(spec_summaries, json: false) + json ? print_json(spec_summaries) : print_table(spec_summaries) + end + + private + + # rubocop:disable Metrics/AbcSize + def print_json(spec_summaries) + puts JSON.pretty_generate( + spec_summaries.map do |summary| + { + location: summary.location, + test_types: summary.test_types, + out_of_hours_count: summary.out_of_hours_count, + in_hours_count: summary.in_hours_count, + branches: summary.branches, + first_seen_at: summary.first_seen_at.in_time_zone(@timezone).iso8601, + last_seen_at: summary.last_seen_at.in_time_zone(@timezone).iso8601, + classification: summary.classification, + out_of_hours_runs: summary.out_of_hours_runs, + in_hours_runs: summary.in_hours_runs + } + end + ) + end + # rubocop:enable Metrics/AbcSize + + # rubocop:disable Metrics/AbcSize + def print_table(spec_summaries) + puts [ + "Classification".ljust(26), + "OOH".rjust(3), + "IN".rjust(3), + "Type".ljust(16), + "Branches".ljust(18), + "First seen".ljust(17), + "Last seen".ljust(17), + "Spec" + ].join(" ") + + spec_summaries.each do |summary| + puts [ + summary.classification.ljust(26), + summary.out_of_hours_count.to_s.rjust(3), + summary.in_hours_count.to_s.rjust(3), + summary.test_types.join(",").ljust(16), + truncate(summary.branches.join(","), 18).ljust(18), + summary.first_seen_at.in_time_zone(@timezone).strftime("%F %H:%M"), + summary.last_seen_at.in_time_zone(@timezone).strftime("%F %H:%M"), + summary.location + ].join(" ") + end + end + # rubocop:enable Metrics/AbcSize + + def truncate(value, length) + return value if value.length <= length + + "#{value[0, length - 3]}..." + end + end +end + +if $PROGRAM_NAME == __FILE__ + if !ENV["GITHUB_TOKEN"] + raise "Missing GITHUB_TOKEN env, go to https://github.com/settings/tokens and create one with 'repo' access" + end + + # workaround an openssl 3.6.0 issue + # https://github.com/ruby/openssl/issues/949#issuecomment-3367944960 + s = OpenSSL::X509::Store.new.tap(&:set_default_paths) + OpenSSL::SSL::SSLContext.send(:remove_const, :DEFAULT_CERT_STORE) rescue nil # rubocop:disable Style/RescueModifier + OpenSSL::SSL::SSLContext.const_set(:DEFAULT_CERT_STORE, s.freeze) + + options = OutOfHoursCiFailures::Options.parse!(ARGV) + client = OutOfHoursCiFailures::GithubClient.new(cache_dir: RAILS_ROOT.join("tmp/report_out_of_hours_ci_failures")) + builder = OutOfHoursCiFailures::ReportBuilder.new(options:, client:) + formatter = OutOfHoursCiFailures::Formatter.new(timezone: options[:timezone]) + + formatter.print(builder.build, json: options[:json]) +end diff --git a/script/support/github_actions_failures.rb b/script/support/github_actions_failures.rb new file mode 100644 index 00000000000..f6407a3be6f --- /dev/null +++ b/script/support/github_actions_failures.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +require "json" + +module GithubActionsFailures + Result = Data.define(:errors, :failures_explanation, :merge_branch_sha) + + class Error + attr_accessor :location, :page_html, :page_screenshot, :tests_group, :loading_error + end + + class TestsGroup + attr_accessor :test_env_number, :seed, :files + + def initialize + @files = [] + end + + def include_error?(error) + return false if error.location.nil? + + files.any? { |file| error.location.include?(file) } + end + + def inspect + "#<#{self.class} @test_env_number=#{test_env_number} @seed=#{seed} (#{files.count} files)>" + end + end + + class JobErrorsFinder + SPEC_FAILURES_PATTERN = %r{^\S+ rspec (\S+) #.+$} + SPEC_LOADING_ERRORS_PATTERN = %r{^\S+ An error occurred while loading (\S+)\.\r?$} + SCREENSHOT_PATTERN = /\{"message":"Screenshot captured for failed feature test"[^\n]+$/ + # Looks like this in the job log: + # rubocop:disable Layout/LineLength + # Process 28: TEST_ENV_NUMBER=28 RUBYOPT=-I/usr/local/bundle/bundler/gems/turbo_tests-3148ae6c3482/lib -r/usr/local/bundle/gems/bundler-2.5.23/lib/bundler/setup -W0 RSPEC_SILENCE_FILTER_ANNOUNCEMENTS=1 /usr/local/bundle/gems/bundler-2.5.23/exe/bundle exec rspec --seed 52674 --format TurboTests::JsonRowsFormatter --out tmp/test-pipes/subprocess-28 --format ParallelTests::RSpec::RuntimeLogger --out spec/support/turbo_runtime_features.log spec/features/api_docs/index_spec.rb spec/features/custom_fields/reorder_options_spec.rb spec/features/projects/projects_portfolio_spec.rb spec/features/projects/template_spec.rb spec/features/versions/edit_spec.rb spec/features/work_packages/details/markdown/description_editor_spec.rb spec/features/work_packages/table/hierarchy/hierarchy_parent_below_spec.rb spec/features/work_packages/table/inline_create/inline_create_refresh_spec.rb spec/features/work_packages/table/invalid_query_spec.rb spec/features/work_packages/tabs/activity_revisions_spec.rb + # rubocop:enable Layout/LineLength + TESTS_GROUP_PATTERN = /Process \d+: TEST_ENV_NUMBER=\d+ [^\n]+$/ + BRANCH_MERGE_PATTERN = /Merge \w{40} into (\w{40})$/ + + def self.scan_logs(logs) + finder = new + logs.each do |log| + finder.scan_log(log) + end + + Result.new( + errors: finder.errors, + failures_explanation: finder.failures_explanation, + merge_branch_sha: finder.merge_branch_sha + ) + end + + attr_reader :failures_explanation, :merge_branch_sha + + def scan_log(log) + find_failures(log) + find_failures_explanation(log) + find_loading_errors(log) + find_screenshots(log) + find_tests_groups(log) + find_merge_branch_info(log) + end + + def errors + @errors.values + end + + private + + def initialize + @errors = {} + end + + def create_error(location) + return if location.nil? + + error = Error.new + error.location = location + @errors[location] ||= error + end + + def with_matching_error(location: nil, id: nil) + error = @errors[id] || @errors[location] + yield error if error && block_given? + error + end + + def find_failures(log) + log.scan(SPEC_FAILURES_PATTERN) + .flatten + .uniq + .sort + .each do |rerun_location| + create_error(rerun_location) + end + end + + def find_failures_explanation(log) + explanations = [] + log.split("\n").each do |line| + if line.end_with?("Failures:") .. line.end_with?("Failed examples:") + explanations << line + end + end + explanations.map! { it[29..] } # Remove leading GitHub Actions log timestamp (e.g. "2024-02-05T08:37:54.5175930Z ") + explanations.reject! do |line| + line == "Failures:" || + line == "Failed examples:" || + line.include?("gems/rspec-retry-") || + line.include?("gems/webmock-") + end + @failures_explanation = explanations.join("\n") + end + + def find_loading_errors(log) + log.scan(SPEC_LOADING_ERRORS_PATTERN) + .flatten + .uniq + .sort + .each do |location| + error = create_error(location) + error.loading_error = true + end + end + + def find_screenshots(log) + log.scan(SCREENSHOT_PATTERN) + .map { JSON.parse(it) } + .each do |screenshot_info| + id = screenshot_info["test_id"] + location = screenshot_info["test_location"] + with_matching_error(location:, id:) do |error| + error.page_html = screenshot_info["html"] + error.page_screenshot = screenshot_info["image"] + end + end + end + + def find_tests_groups(log) + tests_groups = log + .scan(TESTS_GROUP_PATTERN) + .flatten + .map { build_tests_group_from_command(it) } + + errors.each do |error| + error.tests_group = tests_groups.find { it.include_error?(error) } + end + end + + def find_merge_branch_info(log) + merge_branch_sha = log.scan(BRANCH_MERGE_PATTERN).flatten.first + @merge_branch_sha = merge_branch_sha if merge_branch_sha + end + + def build_tests_group_from_command(line) + tests_group = TestsGroup.new + parts = line.split + while parts.any? + case part = parts.shift + when /^TEST_ENV_NUMBER=/ + tests_group.test_env_number = part.delete_prefix("TEST_ENV_NUMBER=") + when "--seed" + tests_group.seed = parts.shift + when /_spec.rb$/ + tests_group.files << part + end + end + tests_group + end + end +end diff --git a/spec/contracts/work_packages/update_contract_spec.rb b/spec/contracts/work_packages/update_contract_spec.rb index bab6d8a4784..8d888aca538 100644 --- a/spec/contracts/work_packages/update_contract_spec.rb +++ b/spec/contracts/work_packages/update_contract_spec.rb @@ -384,9 +384,21 @@ RSpec.describe WorkPackages::UpdateContract do context "for a user having only the assign_versions permission" do let(:permissions) { %i[assign_versions] } - it "includes all attributes except version_id" do + it "includes version_id only" do expect(subject) - .to include("version_id", "version") + .to include("version_id", "version", "lock_version_id", "lock_version") + + expect(subject) + .not_to include("subject", "start_date", "description") + end + end + + context "for a user having only the manage_sprint_items permission" do + let(:permissions) { %i[manage_sprint_items] } + + it "includes version_id only" do + expect(subject) + .to include("version_id", "version", "lock_version_id", "lock_version") expect(subject) .not_to include("subject", "start_date", "description") diff --git a/spec/features/custom_styles/tabs_navigation_spec.rb b/spec/features/custom_styles/tabs_navigation_spec.rb index 2ab45898a2e..d2304dd356c 100644 --- a/spec/features/custom_styles/tabs_navigation_spec.rb +++ b/spec/features/custom_styles/tabs_navigation_spec.rb @@ -84,7 +84,7 @@ RSpec.describe "Tabs navigation and content switching on the admin/design page" expect(page).to have_current_path custom_style_path(tab: "branding") # remove the logo and redirect to the branding tab - custom_style.send :remove_logo + custom_style.send :remove_logo! expect(File.exist?(file_path)).to be false expect(page).to have_current_path custom_style_path(tab: "branding") end diff --git a/spec/features/notifications/notification_center/notification_center_date_alert_mention_spec.rb b/spec/features/notifications/notification_center/notification_center_date_alert_mention_spec.rb index 496eebf61b7..94dac8faead 100644 --- a/spec/features/notifications/notification_center/notification_center_date_alert_mention_spec.rb +++ b/spec/features/notifications/notification_center/notification_center_date_alert_mention_spec.rb @@ -12,9 +12,9 @@ RSpec.describe "Notification center date alert and mention", create(:user, member_with_permissions: { project => %w[view_work_packages] }) end - shared_let(:work_package) { create(:work_package, project:, due_date: 1.day.ago) } - - shared_let(:notification_mention) do + let(:reference_time) { Time.zone.local(2025, 1, 8, 12, 0, 0) } + let(:work_package) { create(:work_package, project:, due_date: 1.day.ago.to_date) } + let!(:notification_mention) do create(:notification, reason: :mentioned, recipient: user, @@ -22,7 +22,7 @@ RSpec.describe "Notification center date alert and mention", actor:) end - shared_let(:notification_date_alert) do + let!(:notification_date_alert) do create(:notification, reason: :date_alert_due_date, recipient: user, @@ -32,11 +32,16 @@ RSpec.describe "Notification center date alert and mention", let(:center) { Pages::Notifications::Center.new } before do + travel_to(reference_time) login_as user visit notifications_center_path wait_for_reload end + after do + travel_back + end + context "with date alerts ee", with_ee: %i[date_alerts] do it "shows only the date alert time, not the mentioned author" do center.within_item(notification_date_alert) do diff --git a/spec/features/notifications/notification_center/notification_center_reminder_spec.rb b/spec/features/notifications/notification_center/notification_center_reminder_spec.rb index f2d9ddb7abd..f7b0d877061 100644 --- a/spec/features/notifications/notification_center/notification_center_reminder_spec.rb +++ b/spec/features/notifications/notification_center/notification_center_reminder_spec.rb @@ -13,10 +13,11 @@ RSpec.describe "Notification center reminder, mention and date alert", create(:user, member_with_permissions: { project => %w[view_work_packages] }) end - shared_let(:work_package) { create(:work_package, project:, due_date: 1.day.ago) } - shared_let(:work_package2) { create(:work_package, project:) } + let(:reference_time) { Time.zone.local(2025, 1, 8, 12, 0, 0) } + let(:work_package) { create(:work_package, project:, due_date: 1.day.ago.to_date) } + let(:work_package2) { create(:work_package, project:) } - shared_let(:notification_mentions) do + let!(:notification_mentions) do [create(:notification, reason: :mentioned, recipient: user, @@ -29,29 +30,34 @@ RSpec.describe "Notification center reminder, mention and date alert", actor: actor)] end - shared_let(:notification_date_alert) do + let!(:notification_date_alert) do create(:notification, reason: :date_alert_due_date, recipient: user, resource: work_package) end - shared_let(:notification_reminder) do + let!(:notification_reminder) do create_reminder_notification_for(work_package: work_package, user: user) end - shared_let(:notification_reminder2) do + let!(:notification_reminder2) do create_reminder_notification_for(work_package: work_package2, user: user) end let(:center) { Pages::Notifications::Center.new } before do + travel_to(reference_time) login_as user visit notifications_center_path wait_for_reload end + after do + travel_back + end + context "with a reminder, mention and date alert" do it "shows the reminder alert within aggregation with date alert + reminder note" do center.within_item(notification_reminder) do diff --git a/spec/features/work_packages/edit_on_assign_version_permission_spec.rb b/spec/features/work_packages/edit_on_assign_version_permission_spec.rb index bf1f68515ce..ab0d2e05907 100644 --- a/spec/features/work_packages/edit_on_assign_version_permission_spec.rb +++ b/spec/features/work_packages/edit_on_assign_version_permission_spec.rb @@ -10,7 +10,7 @@ RSpec.describe "edit work package", :js do lastname: "Guy", member_with_permissions: { project => permissions }) end - let(:permissions) { %i[view_work_packages assign_versions] } + let(:permissions) { %i[view_work_packages manage_sprint_items assign_versions] } let(:cf_all) do create(:work_package_custom_field, is_for_all: true, field_format: "text") @@ -43,7 +43,21 @@ RSpec.describe "edit work package", :js do context "as a user having only the assign_versions permission" do it "can only change the version" do - wp_page.update_attributes version: version.name + wp_page.fill_in_attributes version: version.name + + wp_page.expect_toast(message: "Successful update") + wp_page.expect_attributes version: version.name + + subject_field = wp_page.work_package_field("subject") + subject_field.expect_read_only + end + end + + context "as a user having only the manage_sprint_items permission" do + let(:permissions) { %i[view_work_packages manage_sprint_items] } + + it "can only change the version" do + wp_page.fill_in_attributes version: version.name wp_page.expect_toast(message: "Successful update") wp_page.expect_attributes version: version.name diff --git a/spec/features/work_packages/reminders_spec.rb b/spec/features/work_packages/reminders_spec.rb index 9fd51cab04c..ad0b68b9eb1 100644 --- a/spec/features/work_packages/reminders_spec.rb +++ b/spec/features/work_packages/reminders_spec.rb @@ -32,6 +32,7 @@ require "spec_helper" RSpec.describe "Work package reminder modal", :js do + let(:reference_time) { Time.find_zone!("Europe/Berlin").local(2025, 1, 8, 12, 0, 0) } let!(:project) { create(:project) } let!(:work_package) { create(:work_package, project:) } let!(:role_that_allows_managing_own_reminders) do @@ -54,6 +55,14 @@ RSpec.describe "Work package reminder modal", let(:work_package_page) { Pages::FullWorkPackage.new(work_package) } let(:center) { Pages::Notifications::Center.new } + before do + travel_to(reference_time) + end + + after do + travel_back + end + context "with permissions to manage own reminders" do current_user { user_with_permissions } diff --git a/spec/features/work_packages/table/queries/filter_spec.rb b/spec/features/work_packages/table/queries/filter_spec.rb index 5e70bdda107..0e4ab78b8fb 100644 --- a/spec/features/work_packages/table/queries/filter_spec.rb +++ b/spec/features/work_packages/table/queries/filter_spec.rb @@ -556,26 +556,36 @@ RSpec.describe "filter work packages", :js do end describe "datetime filters" do + shared_let(:business_day_at_noon) { Time.find_zone!("Europe/Kyiv").local(2025, 1, 8, 12, 0, 0) } + + before do + travel_to(business_day_at_noon) + end + + after do + travel_back + end + shared_let(:wp_updated_today) do create(:work_package, subject: "Created today", project:, - created_at: Time.current.change(hour: 12), - updated_at: Time.current.change(hour: 12)) + created_at: business_day_at_noon, + updated_at: business_day_at_noon) end shared_let(:wp_updated_3d_ago) do create(:work_package, subject: "Created 3d ago", project:, - created_at: 3.days.ago, - updated_at: 3.days.ago) + created_at: business_day_at_noon - 3.days, + updated_at: business_day_at_noon - 3.days) end shared_let(:wp_updated_5d_ago) do create(:work_package, subject: "Created 5d ago", project:, - created_at: 5.days.ago, - updated_at: 5.days.ago) + created_at: business_day_at_noon - 5.days, + updated_at: business_day_at_noon - 5.days) end it "filters on date by created_at (Regression #28459)" do diff --git a/spec/features/work_packages/table/queries/mobile_date_filter_spec.rb b/spec/features/work_packages/table/queries/mobile_date_filter_spec.rb index 9f09f6e18c2..7cf0cdabc96 100644 --- a/spec/features/work_packages/table/queries/mobile_date_filter_spec.rb +++ b/spec/features/work_packages/table/queries/mobile_date_filter_spec.rb @@ -36,17 +36,23 @@ RSpec.describe "mobile date filter work packages", :js do shared_let(:wp_table) { Pages::WorkPackagesTable.new(project) } shared_let(:wp_cards) { Pages::WorkPackageCards.new(project) } shared_let(:filters) { Components::WorkPackages::Filters.new } - shared_let(:work_package_with_due_date) { create(:work_package, project:, due_date: Date.current) } - shared_let(:work_package_without_due_date) { create(:work_package, project:, due_date: 5.days.from_now) } + shared_let(:business_day_at_noon) { Time.zone.local(2025, 1, 8, 12, 0, 0) } + shared_let(:work_package_with_due_date) { create(:work_package, project:, due_date: business_day_at_noon.to_date) } + shared_let(:work_package_without_due_date) { create(:work_package, project:, due_date: business_day_at_noon.to_date + 5.days) } current_user { user } include_context "with mobile screen size" before do + travel_to(business_day_at_noon) wp_table.visit! end + after do + travel_back + end + context "when filtering between finish date" do it "allows filtering, saving and retrieving and altering the saved filter" do filters.open @@ -59,9 +65,9 @@ RSpec.describe "mobile date filter work packages", :js do clear_input_field_contents(start_field) clear_input_field_contents(end_field) - start_field.set 1.day.ago.to_date + start_field.set business_day_at_noon.to_date - 1.day start_field.send_keys :tab - end_field.set Date.current + end_field.set business_day_at_noon.to_date end_field.send_keys :tab wait_for_reload @@ -76,7 +82,7 @@ RSpec.describe "mobile date filter work packages", :js do last_query = Query.last date_filter = last_query.filters.last - expect(date_filter.values).to eq [1.day.ago.to_date.iso8601, Date.current.iso8601] + expect(date_filter.values).to eq [(business_day_at_noon.to_date - 1.day).iso8601, business_day_at_noon.to_date.iso8601] end end @@ -90,7 +96,7 @@ RSpec.describe "mobile date filter work packages", :js do expect(date_field["type"]).to eq "date" clear_input_field_contents(date_field) - date_field.set Date.current + date_field.set business_day_at_noon.to_date wait_for_reload loading_indicator_saveguard @@ -104,7 +110,7 @@ RSpec.describe "mobile date filter work packages", :js do last_query = Query.last date_filter = last_query.filters.last - expect(date_filter.values).to eq [Date.current.iso8601] + expect(date_filter.values).to eq [business_day_at_noon.to_date.iso8601] end end end diff --git a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb index 5de3c09d359..e8abbfe723d 100644 --- a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb @@ -490,6 +490,20 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do end end + context "when user lacks edit permission but has manage_sprint_items" do + let(:permissions) { all_permissions - [:edit_work_packages] + [:manage_sprint_items] } + + it_behaves_like "has an untitled link" do + let(:link) { "update" } + let(:href) { api_v3_paths.work_package_form(work_package.id) } + end + + it_behaves_like "has an untitled link" do + let(:link) { "updateImmediately" } + let(:href) { api_v3_paths.work_package(work_package.id) } + end + end + context "when user lacks edit permission but has change_work_package_status" do let(:permissions) { all_permissions - [:edit_work_packages] + [:change_work_package_status] } diff --git a/spec/migrations/fix_sprint_role_dependencies_spec.rb b/spec/migrations/fix_sprint_role_dependencies_spec.rb new file mode 100644 index 00000000000..68211e2ee51 --- /dev/null +++ b/spec/migrations/fix_sprint_role_dependencies_spec.rb @@ -0,0 +1,143 @@ +# 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. +#++ + +require "spec_helper" + +require Rails.root.join("db/migrate/20260304160505_fix_sprint_role_dependencies") + +RSpec.describe FixSprintRoleDependencies, type: :model do + let!(:role) { create(:project_role, permissions:, add_public_permissions: false) } + + subject(:migrate) { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } } + + describe "completing depencencies for view_sprints permission" do + context "when view_work_packages is missing" do + let(:permissions) { [:view_sprints] } + + it "adds view_work_packages" do + expect { migrate } + .to change { role.reload.permissions } + .from(match_array(%i[view_sprints])) + .to(match_array(%i[view_sprints view_work_packages])) + end + end + + context "when view_work_packages is present" do + let(:permissions) { %i[view_sprints view_work_packages] } + + it "does not duplicate view_work_packages" do + expect { migrate }.not_to change { role.reload.permissions } + end + end + end + + describe "completing dependencies for create_sprints permission" do + context "when view_sprints and view_work_packages are missing" do + let(:permissions) { [:create_sprints] } + + it "adds view_sprints and view_work_packages" do + expect { migrate } + .to change { role.reload.permissions } + .from(match_array(%i[create_sprints])) + .to(match_array(%i[create_sprints view_sprints view_work_packages])) + end + end + + context "when view_work_packages is present" do + let(:permissions) { %i[create_sprints view_work_packages] } + + it "adds view_sprints" do + expect { migrate } + .to change { role.reload.permissions } + .from(match_array(%i[create_sprints view_work_packages])) + .to(match_array(%i[create_sprints view_sprints view_work_packages])) + end + end + + context "when view_sprints and view_work_packages are present" do + let(:permissions) { %i[create_sprints view_sprints view_work_packages] } + + it "does not change the permissions" do + expect { migrate }.not_to change { role.reload.permissions } + end + end + end + + describe "completing dependencies for manage_sprint_items permission" do + context "when view_sprints and view_work_packages are missing" do + let(:permissions) { [:manage_sprint_items] } + + it "adds view_sprints and view_work_packages" do + expect { migrate } + .to change { role.reload.permissions } + .from(match_array(%i[manage_sprint_items])) + .to(match_array(%i[manage_sprint_items view_sprints view_work_packages])) + end + end + + context "when view_work_packages is present" do + let(:permissions) { %i[manage_sprint_items view_work_packages] } + + it "adds view_sprints" do + expect { migrate } + .to change { role.reload.permissions } + .from(match_array(%i[manage_sprint_items view_work_packages])) + .to(match_array(%i[manage_sprint_items view_sprints view_work_packages])) + end + end + + context "when view_sprints and view_work_packages are present" do + let(:permissions) { %i[manage_sprint_items view_sprints view_work_packages] } + + it "does not change the permissions" do + expect { migrate }.not_to change { role.reload.permissions } + end + end + end + + describe "completing combined dependencies with view_work_packages missing" do + let(:permissions) { %i[view_sprints create_sprints manage_sprint_items] } + + it "adds view_work_packages without duplicating view_sprints" do + expect { migrate } + .to change { role.reload.permissions } + .from(match_array(%i[view_sprints create_sprints manage_sprint_items])) + .to(match_array(%i[view_sprints create_sprints manage_sprint_items view_work_packages])) + end + end + + describe "not copmpleting dependencies for other roles" do + let(:permissions) { [:edit_work_packages] } + + it "does not change the permissions" do + expect { migrate }.not_to change { role.reload.permissions } + end + end +end diff --git a/spec/migrations/migrate_backlogs_permissions_spec.rb b/spec/migrations/migrate_backlogs_permissions_spec.rb new file mode 100644 index 00000000000..67703ea716a --- /dev/null +++ b/spec/migrations/migrate_backlogs_permissions_spec.rb @@ -0,0 +1,127 @@ +# 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. +#++ + +require "spec_helper" +require Rails.root.join("db/migrate/20260212145213_migrate_backlogs_permissions") + +RSpec.describe MigrateBacklogsPermissions, type: :model do + subject(:migrate) { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } } + + # source permission => expected permissions after up migration. + up_mapping = { + view_master_backlog: %i[view_sprints], + view_taskboards: %i[view_sprints], + update_sprints: %i[create_sprints], + manage_versions: %i[manage_versions create_sprints], + assign_versions: %i[assign_versions manage_sprint_items] + } + + # source permission => expected permissions after rollback. + # Backlogs-specific permissions (view_master_backlog, view_taskboards, update_sprints) + # are lost because the down migration cannot determine the original source. + down_mapping = { + view_master_backlog: [], + view_taskboards: [], + update_sprints: [], + manage_versions: %i[manage_versions], + assign_versions: %i[assign_versions] + } + + let(:all_source_permissions) do + %i(view_master_backlog + view_taskboards + update_sprints + manage_versions + assign_versions) + end + + let!(:role) { create(:project_role, permissions:, add_public_permissions: false) } + + describe "migrating up" do + context "with a role having no backlogs permissions" do + let(:permissions) { [] } + + it "does not change permissions" do + expect { migrate }.not_to change { role.reload.permissions } + end + end + + up_mapping.each do |source_permission, expected_permissions| + context "with a role having only :#{source_permission} permission" do + let(:permissions) { [source_permission] } + + it "results in #{expected_permissions}" do + expect { migrate } + .to change { role.reload.permissions } + .from(contain_exactly(source_permission)) + .to(match_array(expected_permissions)) + end + end + end + + context "with a role combining all source permissions" do + let(:permissions) { all_source_permissions } + + it "migrates to all new permissions plus retained core permissions" do + expect { migrate } + .to change { role.reload.permissions } + .to(match_array(%i[view_sprints create_sprints manage_sprint_items manage_versions assign_versions])) + end + end + end + + describe "migrating down" do + subject(:rollback) { ActiveRecord::Migration.suppress_messages { described_class.migrate(:down) } } + + before { migrate } + + down_mapping.each do |source_permission, expected_permissions| + context "with a role originally having only :#{source_permission} permission" do + let(:permissions) { [source_permission] } + + it "retains #{expected_permissions.empty? ? 'no' : expected_permissions} permissions" do + expect { rollback } + .to change { role.reload.permissions } + .to(match_array(expected_permissions)) + end + end + end + + context "with a role combining all source permissions" do + let(:permissions) { all_source_permissions } + + it "retains only core permissions that were not deleted during up" do + expect { rollback } + .to change { role.reload.permissions } + .to(match_array(%i[manage_versions assign_versions])) + end + end + end +end diff --git a/spec/migrations/rename_comment_permissions_spec.rb b/spec/migrations/rename_comment_permissions_spec.rb new file mode 100644 index 00000000000..e64fcb36094 --- /dev/null +++ b/spec/migrations/rename_comment_permissions_spec.rb @@ -0,0 +1,95 @@ +# 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. +#++ + +require "spec_helper" +require Rails.root.join("db/migrate/20250422072119_rename_comment_permissions") + +RSpec.describe RenameCommentPermissions, type: :model do + subject(:migrate) { ActiveRecord::Migration.suppress_messages { described_class.migrate(:up) } } + + let(:old_permissions) do + %i[add_work_package_notes edit_own_work_package_notes edit_work_package_notes + view_comments_with_restricted_visibility add_comments_with_restricted_visibility + edit_own_comments_with_restricted_visibility edit_others_comments_with_restricted_visibility] + end + let(:new_permissions) do + %i[add_work_package_comments edit_own_work_package_comments edit_work_package_comments + view_internal_comments add_internal_comments + edit_own_internal_comments edit_others_internal_comments] + end + + let!(:empty_role) { create(:project_role) } + let!(:role_with_all) do + create(:project_role, permissions: old_permissions, add_public_permissions: false) + end + let!(:role_with_notes_only) do + create(:project_role, permissions: %i[add_work_package_notes edit_own_work_package_notes], add_public_permissions: false) + end + + describe "migrating up" do + it "does not add permissions to a role without comment permissions" do + expect { migrate }.not_to change { empty_role.reload.permissions } + end + + it "renames all comment permissions" do + expect { migrate } + .to change { role_with_all.reload.permissions } + .from(match_array(old_permissions)) + .to(match_array(new_permissions)) + end + + it "renames only the matching permissions" do + expect { migrate } + .to change { role_with_notes_only.reload.permissions } + .from(match_array(%i[add_work_package_notes edit_own_work_package_notes])) + .to(match_array(%i[add_work_package_comments edit_own_work_package_comments])) + end + end + + describe "migrating down" do + subject(:rollback) { ActiveRecord::Migration.suppress_messages { described_class.migrate(:down) } } + + before { migrate } + + it "reverts all comment permissions" do + expect { rollback } + .to change { role_with_all.reload.permissions } + .from(match_array(new_permissions)) + .to(match_array(old_permissions)) + end + + it "reverts only the matching permissions" do + expect { rollback } + .to change { role_with_notes_only.reload.permissions } + .from(match_array(%i[add_work_package_comments edit_own_work_package_comments])) + .to(match_array(%i[add_work_package_notes edit_own_work_package_notes])) + end + end +end diff --git a/spec/models/custom_style_spec.rb b/spec/models/custom_style_spec.rb index 2101b05d698..b484d0be4f6 100644 --- a/spec/models/custom_style_spec.rb +++ b/spec/models/custom_style_spec.rb @@ -35,17 +35,24 @@ RSpec.describe CustomStyle do let!(:file_path) { custom_style.send(image).file.path } - before do - custom_style.send :"remove_#{image}" - end + subject { custom_style.send :"remove_#{image}!" } it "deletes the file" do + subject + expect(File.exist?(file_path)).to be false end it "clears the file mount column" do + subject + expect(custom_style.reload.send(image).file).to be_nil end + + it "updates the model" do + expect { subject } + .to change(custom_style, :updated_at) + end end describe "#remove_favicon" do diff --git a/spec/models/permitted_params_spec.rb b/spec/models/permitted_params_spec.rb index afdd270a5d5..a7fa32414c1 100644 --- a/spec/models/permitted_params_spec.rb +++ b/spec/models/permitted_params_spec.rb @@ -549,6 +549,12 @@ RSpec.describe PermittedParams do it_behaves_like "allows params" end + describe "sprint_id" do + let(:hash) { { "sprint_id" => "1" } } + + it_behaves_like "allows params" + end + describe "notes" do let(:hash) { { "journal_notes" => "blubs" } } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9370a5db8d9..322c4d7cc1d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -540,6 +540,23 @@ RSpec.describe Project do end end + describe "#custom_values_for_custom_field" do + let(:custom_field) { create(:list_project_custom_field, multi_value: true) } + # intentionally out of order + let!(:cv2) { create(:custom_value, id: 1002, customized: project, custom_field:) } + let!(:cv1) { create(:custom_value, id: 1001, customized: project, custom_field:) } + let!(:cv3) { create(:custom_value, id: 1003, customized: project, custom_field:) } + + before do + allow(project).to receive(:available_custom_fields) { ProjectCustomField.all } + end + + it "returns values ordered by id" do + values = project.custom_values_for_custom_field(custom_field) + expect(values).to eq([cv1, cv2, cv3]) + end + end + describe "url identifier" do let(:reserved) do Rails.application.routes.routes diff --git a/spec/scripts/out_of_hours_ci_failures/report_builder_spec.rb b/spec/scripts/out_of_hours_ci_failures/report_builder_spec.rb new file mode 100644 index 00000000000..6c08091558c --- /dev/null +++ b/spec/scripts/out_of_hours_ci_failures/report_builder_spec.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require "spec_helper" + +module OutOfHoursCiFailures +end + +load Rails.root.join("script/report_out_of_hours_ci_failures") + +RSpec.describe OutOfHoursCiFailures::ReportBuilder do + let(:options) do + { + days: 30, + include_pr_runs: false, + json: false, + timezone: "Europe/Berlin", + workflow: "test-core.yml" + } + end + + let(:client) { instance_spy(OutOfHoursCiFailures::GithubClient) } + let(:builder) { described_class.new(options:, client:) } + let(:run_id) { 1001 } + + before do + allow(Time).to receive(:current).and_return(Time.zone.parse("2026-03-08 12:00:00 UTC")) + end + + it "classifies boundary times using the configured timezone" do + stub_runs( + workflow(run_id:, created_at: "2026-03-03T07:59:00Z") + ) + stub_jobs(run_id => failed_jobs("Feature tests")) + stub_logs("spec/features/example_spec.rb:10") + + summary = builder.build.first + + expect(summary.out_of_hours_count).to eq(1) + expect(summary.in_hours_count).to eq(0) + end + + it "ignores build failures and keeps only unit and feature test failures" do + stub_runs( + workflow(run_id:, created_at: "2026-03-03T08:00:00Z"), + workflow(run_id: 1002, created_at: "2026-03-03T09:00:00Z") + ) + stub_jobs( + run_id => build_failure_jobs, + 1002 => failed_jobs("Unit tests") + ) + stub_logs("spec/models/example_spec.rb:12") + + summaries = builder.build + + expect(summaries.map(&:location)).to eq(["spec/models/example_spec.rb:12"]) + end + + it "classifies repeated out-of-hours failures on multiple branches as likely datetime-sensitive" do + stub_runs( + workflow(run_id:, branch: "dev", created_at: "2026-03-03T07:59:00Z"), + workflow( + run_id: 1002, + branch: "release/17.2", + created_at: "2026-03-04T18:00:00Z" + ) + ) + stub_jobs( + run_id => failed_jobs("Feature tests"), + 1002 => failed_jobs("Feature tests") + ) + stub_logs("spec/features/example_spec.rb:10") + + summary = builder.build.first + + expect(summary.classification).to eq("likely datetime-sensitive") + end + + it "classifies failures seen in and out of hours as likely generic flaky" do + stub_runs( + workflow(run_id:, branch: "dev", created_at: "2026-03-03T07:59:00Z"), + workflow( + run_id: 1002, + branch: "release/17.2", + created_at: "2026-03-04T10:00:00Z" + ) + ) + stub_jobs( + run_id => failed_jobs("Feature tests"), + 1002 => failed_jobs("Feature tests") + ) + stub_logs("spec/features/example_spec.rb:10") + + summary = builder.build.first + + expect(summary.classification).to eq("likely generic flaky") + end + + it "classifies repeated failures on a single branch as likely regression" do + stub_runs( + workflow( + run_id:, + created_at: "2026-03-03T07:59:00Z", + branch: "feature/foo", + event: "push" + ), + workflow( + run_id: 1002, + created_at: "2026-03-04T08:30:00Z", + branch: "feature/foo", + event: "push" + ) + ) + stub_jobs( + run_id => failed_jobs("Unit tests"), + 1002 => failed_jobs("Unit tests") + ) + stub_logs("spec/models/example_spec.rb:12") + + summaries = described_class.new(options: options.merge(include_pr_runs: true), client:).build + + expect(summaries.first.classification).to eq("likely regression") + end + + def stub_runs(*runs) + allow(client).to receive(:workflow_runs).and_return( + { "workflow_runs" => runs }, + { "workflow_runs" => [] } + ) + end + + def stub_jobs(jobs_by_run_id) + allow(client).to receive(:jobs) do |id| + jobs_by_run_id.fetch(id) + end + end + + def stub_logs(location) + allow(client).to receive(:log).and_return(job_log(location)) + end + + def workflow(run_id:, created_at:, branch: "dev", event: "push") + { + "id" => run_id, + "run_number" => run_id, + "html_url" => "https://github.com/opf/openproject/actions/runs/#{run_id}", + "head_branch" => branch, + "event" => event, + "created_at" => created_at + } + end + + def failed_jobs(step_name) + { + "jobs" => [ + { + "id" => 9001, + "conclusion" => "failure", + "steps" => [ + { "name" => step_name, "conclusion" => "failure" } + ] + } + ] + } + end + + def build_failure_jobs + { + "jobs" => [ + { + "id" => 9002, + "conclusion" => "failure", + "steps" => [ + { "name" => "Build", "conclusion" => "failure" } + ] + } + ] + } + end + + def job_log(location) + "2026-03-03T08:00:00.0000000Z rspec #{location} # example failure\n" + end +end diff --git a/spec/seeders/root_seeder_standard_edition_spec.rb b/spec/seeders/root_seeder_standard_edition_spec.rb index 326491fc278..c0ab9890c87 100644 --- a/spec/seeders/root_seeder_standard_edition_spec.rb +++ b/spec/seeders/root_seeder_standard_edition_spec.rb @@ -131,7 +131,7 @@ RSpec.describe RootSeeder, member_role = root_seeder.seed_data.find_reference(:default_role_member) expect(member_role.permissions).to include( :view_work_packages, # from common basic data - :view_taskboards, # from backlogs module + :view_sprints, # from backlogs module :show_board_views, # from board module :view_documents, # from documents module :view_budgets, # from costs module diff --git a/spec/services/project_custom_fields/load_service_spec.rb b/spec/services/project_custom_fields/load_service_spec.rb new file mode 100644 index 00000000000..8a5d2516d0b --- /dev/null +++ b/spec/services/project_custom_fields/load_service_spec.rb @@ -0,0 +1,87 @@ +# 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. +#++ + +require "spec_helper" + +RSpec.describe ProjectCustomFields::LoadService do + let(:project) { create(:project) } + let(:custom_field) { create(:list_project_custom_field, multi_value: true) } + let(:project_custom_fields) { ProjectCustomField.where(id: project_custom_field_ids) } + let(:project_custom_field_ids) { custom_field.id } + + subject(:service) { described_class.new(project:, project_custom_fields:) } + + describe "#get_eager_loaded_project_custom_field_values_for" do + context "when the custom field has no values" do + it "returns an empty array" do + expect(service.get_eager_loaded_project_custom_field_values_for(custom_field.id)).to eq([]) + end + end + + context "when the custom field has values" do + # intentionally out of order + let!(:cv2) { create(:custom_value, id: 1002, customized: project, custom_field:) } + let!(:cv1) { create(:custom_value, id: 1001, customized: project, custom_field:) } + let!(:cv3) { create(:custom_value, id: 1003, customized: project, custom_field:) } + + it "returns the values ordered by id" do + expect(service.get_eager_loaded_project_custom_field_values_for(custom_field.id)) + .to eq([cv1, cv2, cv3]) + end + + it "returns empty array for other custom field" do + expect(service.get_eager_loaded_project_custom_field_values_for(0)) + .to eq([]) + end + + context "when another custom field has values" do + let(:other_custom_field) { create(:list_project_custom_field, multi_value: true) } + let!(:other_cv) { create(:custom_value, customized: project, custom_field: other_custom_field) } + let(:project_custom_field_ids) { [custom_field.id, other_custom_field.id] } + + it "returns values only for the requested custom field" do + expect(service.get_eager_loaded_project_custom_field_values_for(custom_field.id)) + .to eq([cv1, cv2, cv3]) + expect(service.get_eager_loaded_project_custom_field_values_for(other_custom_field.id)) + .to eq([other_cv]) + end + end + + context "when another project has values for the same custom field" do + let!(:other_project_cv) { create(:custom_value, customized: create(:project), custom_field:) } + + it "returns only values for the given project" do + expect(service.get_eager_loaded_project_custom_field_values_for(custom_field.id)) + .to eq([cv1, cv2, cv3]) + end + end + end + end +end diff --git a/spec/support/components/common/filters.rb b/spec/support/components/common/filters.rb index 805ed1081f5..93a946a0cf9 100644 --- a/spec/support/components/common/filters.rb +++ b/spec/support/components/common/filters.rb @@ -198,6 +198,8 @@ module Components end def open_filters + return if filters_expanded? + retry_block do toggle_filters_section expect(page).to have_css(".op-filters-form.-expanded") @@ -213,6 +215,13 @@ module Components filters_toggle.click end + def filters_expanded? + # wait for widgets to be loaded (filters button should be visible) + filters_toggle + + page.has_css?(".op-filters-form.-expanded", wait: 0) + end + def autocomplete_filter?(filter) filter.has_css?('[data-filter-autocomplete="true"]', wait: 0) end diff --git a/spec/support/shared/with_direct_uploads.rb b/spec/support/shared/with_direct_uploads.rb index 302dd0b5677..144da94d473 100644 --- a/spec/support/shared/with_direct_uploads.rb +++ b/spec/support/shared/with_direct_uploads.rb @@ -172,14 +172,19 @@ RSpec.configure do |config| WithDirectUploads.new(self).before example - class FogAttachment < Attachment - # Remounting the uploader overrides the original file setter taking care of setting, - # among other things, the content type. So we have to restore that original - # method this way. - # We do this in a new, separate class, as to not interfere with any other specs. - alias_method :set_file, :file= - mount_uploader :file, FogFileUploader - alias_method :file=, :set_file + # Only define FogAttachment once. In CW 2.x, mount_uploader uses prepend, + # so re-opening the class and re-mounting would stack prepend modules and + # cause infinite recursion with the alias_method trick below. + unless defined?(FogAttachment) + class FogAttachment < Attachment + # Remounting the uploader overrides the original file setter taking care of setting, + # among other things, the content type. So we have to restore that original + # method this way. + # We do this in a new, separate class, as to not interfere with any other specs. + alias_method :set_file, :file= + mount_uploader :file, FogFileUploader + alias_method :file=, :set_file + end end end diff --git a/spec/support/shared/with_ssrf_webhook_stubs.rb b/spec/support/shared/with_ssrf_webhook_stubs.rb index 2114f66bddf..23bb7590060 100644 --- a/spec/support/shared/with_ssrf_webhook_stubs.rb +++ b/spec/support/shared/with_ssrf_webhook_stubs.rb @@ -29,23 +29,11 @@ module WithSsrfWebhookStubsMixin ## # A safe public IP returned by the stubbed resolver for any hostname. - # It is not in SsrfFilter's private-address blocklist, so SSRF validation passes, - # and WebMock stubs using this IP will match the actual Net::HTTP request. + # It is not in SsrfFilter's private-address blocklist, so SSRF validation passes. + # ssrf_filter 1.3+ makes requests to the original hostname URL (not the resolved IP), + # passing the resolved IP via the `ipaddr:` option to Net::HTTP.start instead. SSRF_TEST_IP = "93.184.216.34" - ## - # Translates a webhook URL containing a hostname to the IP-based URL that - # SsrfFilter will use when making the actual HTTP request. Use this when - # setting up WebMock stubs so that they match the resolved request. - # - # URLs that already contain an IP address are returned unchanged. - def ssrf_resolved_url(url) - uri = URI.parse(url) - return url if ip_address?(uri.host) - - url.sub(uri.host, SSRF_TEST_IP) - end - def ip_address?(host) [Resolv::IPv4::Regex, Resolv::IPv6::Regex].any? { host.match? it } end diff --git a/spec/support_spec/table_helpers/column_type/schedule_spec.rb b/spec/support_spec/table_helpers/column_type/schedule_spec.rb index f377b7c6947..0d849151808 100644 --- a/spec/support_spec/table_helpers/column_type/schedule_spec.rb +++ b/spec/support_spec/table_helpers/column_type/schedule_spec.rb @@ -50,6 +50,10 @@ module TableHelpers::ColumnType travel_to(fake_today) end + after do + travel_back + end + describe "origin day" do it "is identified by the 'M' in 'MTWTFSS' in the header and corresponds to the next monday" do expect(parsed_attributes(<<~TABLE)) diff --git a/spec/support_spec/table_helpers/example_methods_spec.rb b/spec/support_spec/table_helpers/example_methods_spec.rb index 18f44454e40..b212d8aca97 100644 --- a/spec/support_spec/table_helpers/example_methods_spec.rb +++ b/spec/support_spec/table_helpers/example_methods_spec.rb @@ -45,6 +45,10 @@ module TableHelpers travel_to(fake_today) end + after do + travel_back + end + it "applies attribute changes to a group of work packages from a visual table representation" do main = build_stubbed(:work_package, subject: "main") second = build_stubbed(:work_package, subject: "second")