mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge branch 'dev' into merge-release/17.0-20251204132109
This commit is contained in:
+140
-135
@@ -59,6 +59,9 @@ Layout/MultilineMethodCallIndentation:
|
||||
Layout/MultilineOperationIndentation:
|
||||
Enabled: false
|
||||
|
||||
Lint/AmbiguousBlockAssociation:
|
||||
AllowedMethods: [change]
|
||||
|
||||
Lint/AmbiguousOperator:
|
||||
Enabled: false
|
||||
|
||||
@@ -98,14 +101,11 @@ Lint/UnderscorePrefixedVariableName:
|
||||
Lint/Void:
|
||||
Enabled: false
|
||||
|
||||
Lint/AmbiguousBlockAssociation:
|
||||
AllowedMethods: [change]
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Enabled: false
|
||||
Metrics/AbcSize:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/**/*.rb"
|
||||
- "modules/*/spec/**/*.rb"
|
||||
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
||||
@@ -113,6 +113,12 @@ Metrics/BlockLength:
|
||||
Metrics/BlockNesting:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Enabled: false
|
||||
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
@@ -122,12 +128,6 @@ Metrics/ModuleLength:
|
||||
Metrics/ParameterLists:
|
||||
Enabled: false
|
||||
|
||||
Metrics/AbcSize:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/**/*.rb"
|
||||
- "modules/*/spec/**/*.rb"
|
||||
|
||||
Naming/AccessorMethodName:
|
||||
Enabled: false
|
||||
|
||||
@@ -150,10 +150,15 @@ Naming/VariableNumber:
|
||||
- '\w_20\d\d' # allow dates like christmas_2022 or date_2034_04_12
|
||||
- '\w\d++(_\d++)+' # allow hierarchical data like child1_2_5 (second + in regex is possessive qualifier)
|
||||
- 'custom_field_\d+' # allow custom field method names to be called with send :custom_field_1001
|
||||
# There are valid cases in which to use methods like:
|
||||
# * update_all
|
||||
# * touch_all
|
||||
Rails/SkipsModelValidations:
|
||||
|
||||
OpenProject/AddPreviewForViewComponent:
|
||||
Include:
|
||||
- app/components/op_turbo/**.rb
|
||||
- app/components/op_primer/**.rb
|
||||
- app/components/open_project/**.rb
|
||||
- app/components/concerns/**.rb
|
||||
|
||||
Performance/Casecmp:
|
||||
Enabled: false
|
||||
|
||||
# Don't force us to use tag instead of content_tag
|
||||
@@ -161,55 +166,6 @@ Rails/SkipsModelValidations:
|
||||
Rails/ContentTag:
|
||||
Enabled: false
|
||||
|
||||
# Disable I18n.locale = in specs, where it is reset
|
||||
# by us explicitly
|
||||
Rails/I18nLocaleAssignment:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/**/*.rb"
|
||||
|
||||
# Do not bother if `let` statements use an index in their name
|
||||
RSpec/IndexedLet:
|
||||
Enabled: false
|
||||
|
||||
# The http verbs in Rack::Test do not accept named parameters (params: params)
|
||||
Rails/HttpPositionalArguments:
|
||||
Enabled: false
|
||||
|
||||
# require_dependency is an obsolete method for Rails applications running in Zeitwerk mode.
|
||||
Rails/RequireDependency:
|
||||
Enabled: true
|
||||
|
||||
# For feature specs, we tend to have longer specs that cover a larger part of the functionality.
|
||||
# This is done for multiple reasons:
|
||||
# * performance, as setting up integration tests is costly
|
||||
# * following a scenario that is closer to how a user interacts
|
||||
RSpec/ExampleLength:
|
||||
Max: 25
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/features/**/*.rb"
|
||||
- "modules/*/spec/features/**/*.rb"
|
||||
|
||||
# We have specs that have no expect(..) syntax,
|
||||
# but only helper classes that expect themselves
|
||||
RSpec/NoExpectationExample:
|
||||
Enabled: false
|
||||
|
||||
RSpec/DescribeClass:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/features/**/*.rb"
|
||||
- "modules/*/spec/features/**/*.rb"
|
||||
|
||||
# Nothing wrong with `include_examples` when used properly.
|
||||
RSpec/IncludeExamples:
|
||||
Enabled: false
|
||||
|
||||
# Allow number HTTP status codes in specs
|
||||
RSpecRails/HttpStatus:
|
||||
Enabled: false
|
||||
|
||||
# dynamic finders cop clashes with capybara ID cop
|
||||
Rails/DynamicFindBy:
|
||||
Enabled: true
|
||||
@@ -230,53 +186,38 @@ Rails/FindEach:
|
||||
- select
|
||||
- lock
|
||||
|
||||
# The http verbs in Rack::Test do not accept named parameters (params: params)
|
||||
Rails/HttpPositionalArguments:
|
||||
Enabled: false
|
||||
|
||||
# Disable I18n.locale = in specs, where it is reset
|
||||
# by us explicitly
|
||||
Rails/I18nLocaleAssignment:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/**/*.rb"
|
||||
|
||||
# We have config.active_record.belongs_to_required_by_default = false ,
|
||||
# which means, we do have to declare presence validators on belongs_to relations.
|
||||
Rails/RedundantPresenceValidationOnBelongsTo:
|
||||
Enabled: false
|
||||
|
||||
# See RSpec/ExampleLength for why feature specs are excluded
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 15
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/features/**/*.rb"
|
||||
- "modules/*/spec/features/**/*.rb"
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Enabled: false
|
||||
|
||||
RSpec/NestedGroups:
|
||||
Enabled: false
|
||||
|
||||
# Don't force the second argument of describe
|
||||
# to be .class_method or #instance_method
|
||||
RSpec/DescribeMethod:
|
||||
Enabled: false
|
||||
|
||||
# Don't force the second argument of describe
|
||||
# to match the exact file name
|
||||
RSpec/SpecFilePathFormat:
|
||||
CustomTransform:
|
||||
OpenIDConnect: openid_connect
|
||||
OAuthClients: oauth_clients
|
||||
OAuth: oauth
|
||||
ICal: ical
|
||||
IgnoreMethods: true
|
||||
|
||||
# Prevent "fit" or similar to be committed
|
||||
RSpec/Focus:
|
||||
# require_dependency is an obsolete method for Rails applications running in Zeitwerk mode.
|
||||
Rails/RequireDependency:
|
||||
Enabled: true
|
||||
|
||||
# We use let!() to ensure dependencies are created
|
||||
# instead of let() and referencing them explicitly
|
||||
RSpec/LetSetup:
|
||||
# Require save! to prevent saving without validation when saving outside of a condition.
|
||||
Rails/SaveBang:
|
||||
Enabled: true
|
||||
|
||||
# There are valid cases in which to use methods like:
|
||||
# * update_all
|
||||
# * touch_all
|
||||
Rails/SkipsModelValidations:
|
||||
Enabled: false
|
||||
|
||||
RSpec/LeadingSubject:
|
||||
Enabled: false
|
||||
|
||||
RSpec/NamedSubject:
|
||||
# Allow number HTTP status codes in specs
|
||||
RSpecRails/HttpStatus:
|
||||
Enabled: false
|
||||
|
||||
# expect not_to change is not working as expected
|
||||
@@ -303,6 +244,80 @@ RSpec/ContextWording:
|
||||
- within
|
||||
- without
|
||||
|
||||
RSpec/DescribeClass:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/features/**/*.rb"
|
||||
- "modules/*/spec/features/**/*.rb"
|
||||
|
||||
# Don't force the second argument of describe
|
||||
# to be .class_method or #instance_method
|
||||
RSpec/DescribeMethod:
|
||||
Enabled: false
|
||||
|
||||
# For feature specs, we tend to have longer specs that cover a larger part of the functionality.
|
||||
# This is done for multiple reasons:
|
||||
# * performance, as setting up integration tests is costly
|
||||
# * following a scenario that is closer to how a user interacts
|
||||
RSpec/ExampleLength:
|
||||
Max: 25
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/features/**/*.rb"
|
||||
- "modules/*/spec/features/**/*.rb"
|
||||
|
||||
# Prevent "fit" or similar to be committed
|
||||
RSpec/Focus:
|
||||
Enabled: true
|
||||
|
||||
# Nothing wrong with `include_examples` when used properly.
|
||||
RSpec/IncludeExamples:
|
||||
Enabled: false
|
||||
|
||||
# Do not bother if `let` statements use an index in their name
|
||||
RSpec/IndexedLet:
|
||||
Enabled: false
|
||||
|
||||
RSpec/LeadingSubject:
|
||||
Enabled: false
|
||||
|
||||
# We use let!() to ensure dependencies are created
|
||||
# instead of let() and referencing them explicitly
|
||||
RSpec/LetSetup:
|
||||
Enabled: false
|
||||
|
||||
# We have specs that have no expect(..) syntax,
|
||||
# but only helper classes that expect themselves
|
||||
RSpec/NoExpectationExample:
|
||||
Enabled: false
|
||||
|
||||
# See RSpec/ExampleLength for why feature specs are excluded
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 15
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- "spec/features/**/*.rb"
|
||||
- "modules/*/spec/features/**/*.rb"
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Enabled: false
|
||||
|
||||
RSpec/NestedGroups:
|
||||
Enabled: false
|
||||
|
||||
# Don't force the second argument of describe
|
||||
# to match the exact file name
|
||||
RSpec/SpecFilePathFormat:
|
||||
CustomTransform:
|
||||
OpenIDConnect: openid_connect
|
||||
OAuthClients: oauth_clients
|
||||
OAuth: oauth
|
||||
ICal: ical
|
||||
IgnoreMethods: true
|
||||
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
|
||||
Style/Alias:
|
||||
Enabled: false
|
||||
|
||||
@@ -367,6 +382,19 @@ Style/FormatString:
|
||||
Style/FormatStringToken:
|
||||
AllowedMethods: [redirect]
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: true
|
||||
EnforcedStyle: always_true
|
||||
|
||||
Style/HashEachMethods:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformKeys:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformValues:
|
||||
Enabled: true
|
||||
|
||||
Style/GlobalVars:
|
||||
Enabled: false
|
||||
|
||||
@@ -409,6 +437,13 @@ Style/NilComparison:
|
||||
Style/Not:
|
||||
Enabled: false
|
||||
|
||||
Style/NumericLiterals:
|
||||
Enabled: false
|
||||
|
||||
# Avoid enforcing "positive?"
|
||||
Style/NumericPredicate:
|
||||
Enabled: false
|
||||
|
||||
Style/OneLineConditional:
|
||||
Enabled: false
|
||||
|
||||
@@ -471,33 +506,3 @@ Style/WhileUntilModifier:
|
||||
|
||||
Style/WordArray:
|
||||
Enabled: false
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: true
|
||||
EnforcedStyle: always_true
|
||||
|
||||
Style/NumericLiterals:
|
||||
Enabled: false
|
||||
|
||||
# Avoid enforcing "positive?"
|
||||
Style/NumericPredicate:
|
||||
Enabled: false
|
||||
|
||||
Style/HashEachMethods:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformKeys:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformValues:
|
||||
Enabled: true
|
||||
|
||||
Performance/Casecmp:
|
||||
Enabled: false
|
||||
|
||||
OpenProject/AddPreviewForViewComponent:
|
||||
Include:
|
||||
- app/components/op_turbo/**.rb
|
||||
- app/components/op_primer/**.rb
|
||||
- app/components/open_project/**.rb
|
||||
- app/components/concerns/**.rb
|
||||
|
||||
@@ -149,7 +149,7 @@ gem "structured_warnings", "~> 0.5.0"
|
||||
# don't require by default, instead load on-demand when actually configured
|
||||
gem "airbrake", "~> 13.0.0", require: false
|
||||
|
||||
gem "markly", "~> 0.14" # another markdown parser like commonmarker, but with AST support used in PDF export
|
||||
gem "markly", "~> 0.15" # another markdown parser like commonmarker, but with AST support used in PDF export
|
||||
gem "md_to_pdf", git: "https://github.com/opf/md-to-pdf", ref: "6c565541bfa390c58d90d49aa9b487777704fc66"
|
||||
gem "prawn", "~> 2.4"
|
||||
gem "ttfunk", "~> 1.7.0" # remove after https://github.com/prawnpdf/prawn/issues/1346 resolved.
|
||||
@@ -198,7 +198,7 @@ gem "aws-sdk-s3", "~> 1.205"
|
||||
|
||||
gem "openproject-token", "~> 8.2.0"
|
||||
|
||||
gem "plaintext", "~> 0.3.2"
|
||||
gem "plaintext", "~> 0.3.7"
|
||||
|
||||
gem "ruby-progressbar", "~> 1.13.0", require: false
|
||||
|
||||
@@ -356,7 +356,7 @@ group :development, :test do
|
||||
gem "rubocop-factory_bot", require: false
|
||||
gem "rubocop-openproject", require: false
|
||||
gem "rubocop-performance", require: false
|
||||
gem "rubocop-rails", "= 2.33.3", require: false # 2.33.4 has issues with Rails/ActionControllerFlashBeforeRender
|
||||
gem "rubocop-rails", "2.34.2", require: false # 2.33.4 has issues with Rails/ActionControllerFlashBeforeRender
|
||||
gem "rubocop-rspec", require: false
|
||||
gem "rubocop-rspec_rails", require: false
|
||||
|
||||
|
||||
+19
-19
@@ -36,10 +36,10 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/opf/omniauth-openid-connect.git
|
||||
revision: f0c1ecdb26e39017a9e929af75a166c772d960bb
|
||||
ref: f0c1ecdb26e39017a9e929af75a166c772d960bb
|
||||
revision: 825d06235b64f6bc872bba709f1c2d48fd5cede4
|
||||
ref: 825d06235b64f6bc872bba709f1c2d48fd5cede4
|
||||
specs:
|
||||
omniauth-openid-connect (0.4.2)
|
||||
omniauth-openid-connect (0.5.0)
|
||||
addressable (~> 2.5)
|
||||
omniauth (~> 1.6)
|
||||
openid_connect (~> 2.2.0)
|
||||
@@ -435,7 +435,7 @@ GEM
|
||||
compare-xml (0.66)
|
||||
nokogiri (~> 1.8)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.4)
|
||||
connection_pool (2.5.5)
|
||||
cookiejar (0.3.4)
|
||||
cose (1.3.1)
|
||||
cbor (~> 0.5.9)
|
||||
@@ -661,7 +661,7 @@ GEM
|
||||
rake (>= 13)
|
||||
googleapis-common-protos-types (1.22.0)
|
||||
google-protobuf (~> 4.26)
|
||||
googleauth (1.15.1)
|
||||
googleauth (1.16.0)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-cloud-env (~> 2.2)
|
||||
google-logging-utils (~> 0.1)
|
||||
@@ -801,7 +801,7 @@ GEM
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.1.0)
|
||||
markly (0.14.1)
|
||||
markly (0.15.0)
|
||||
matrix (0.4.3)
|
||||
messagebird-rest (5.0.0)
|
||||
jwt (< 4)
|
||||
@@ -1070,7 +1070,7 @@ GEM
|
||||
ostruct (0.6.3)
|
||||
ox (2.14.23)
|
||||
bigdecimal (>= 3.0)
|
||||
pagy (43.1.5)
|
||||
pagy (43.1.8)
|
||||
json
|
||||
yaml
|
||||
paper_trail (17.0.0)
|
||||
@@ -1098,7 +1098,7 @@ GEM
|
||||
pg (1.6.2-x86_64-darwin)
|
||||
pg (1.6.2-x86_64-linux)
|
||||
pg (1.6.2-x86_64-linux-musl)
|
||||
plaintext (0.3.6)
|
||||
plaintext (0.3.7)
|
||||
activesupport (> 2.2.1)
|
||||
nokogiri (~> 1.10, >= 1.10.4)
|
||||
rubyzip (>= 1.2.0)
|
||||
@@ -1333,7 +1333,7 @@ GEM
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.47.1, < 2.0)
|
||||
rubocop-rails (2.33.3)
|
||||
rubocop-rails (2.34.2)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
@@ -1625,7 +1625,7 @@ DEPENDENCIES
|
||||
lograge (~> 0.14.0)
|
||||
lookbook (= 2.3.13)
|
||||
mail (= 2.9.0)
|
||||
markly (~> 0.14)
|
||||
markly (~> 0.15)
|
||||
matrix (~> 0.4.3)
|
||||
md_to_pdf!
|
||||
meta-tags (~> 2.22.2)
|
||||
@@ -1676,7 +1676,7 @@ DEPENDENCIES
|
||||
parallel_tests (~> 4.0)
|
||||
pdf-inspector (~> 1.2)
|
||||
pg (~> 1.6.2)
|
||||
plaintext (~> 0.3.2)
|
||||
plaintext (~> 0.3.7)
|
||||
prawn (~> 2.4)
|
||||
pry-byebug (~> 3.11.0)
|
||||
pry-rails (~> 0.3.6)
|
||||
@@ -1712,7 +1712,7 @@ DEPENDENCIES
|
||||
rubocop-factory_bot
|
||||
rubocop-openproject
|
||||
rubocop-performance
|
||||
rubocop-rails (= 2.33.3)
|
||||
rubocop-rails (= 2.34.2)
|
||||
rubocop-rspec
|
||||
rubocop-rspec_rails
|
||||
ruby-duration (~> 3.2.0)
|
||||
@@ -1835,7 +1835,7 @@ CHECKSUMS
|
||||
commonmarker (2.5.0-x86_64-linux-musl) sha256=3ac20d8de38260d02306e3a539b90d136818998bcfb203114f7054ad7fdb7b37
|
||||
compare-xml (0.66) sha256=e21aa5c0f69ef1177eced997c688fd4df989084e74a1b612257af32e1dd05319
|
||||
concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6
|
||||
connection_pool (2.5.4) sha256=e9e1922327416091f3f6542f5f4446c2a20745276b9aa796dd0bb2fd0ea1e70a
|
||||
connection_pool (2.5.5) sha256=e54ff92855753df1fd7c59fa04a398833355f27dd14c074f8c83a05f72a716ad
|
||||
cookiejar (0.3.4) sha256=11b16acfc4baf7a0f463c21a6212005e04e25f5554d4d9f24d97f3492dfda0df
|
||||
cose (1.3.1) sha256=d5d4dbcd6b035d513edc4e1ab9bc10e9ce13b4011c96e3d1b8fe5e6413fd6de5
|
||||
costs (1.0.0)
|
||||
@@ -1928,7 +1928,7 @@ CHECKSUMS
|
||||
google-protobuf (4.32.1-x86_64-linux-gnu) sha256=2d209c1980dbdeb4114c7d839a3305fb78dc90bde42fb9a22974c8a4841e0263
|
||||
google-protobuf (4.32.1-x86_64-linux-musl) sha256=d5314aea8817bd372177205667b4f4442a5884212bcaccb1ba569ec8a1a06ec4
|
||||
googleapis-common-protos-types (1.22.0) sha256=f97492b77bd6da0018c860d5004f512fe7cd165554d7019a8f4df6a56fbfc4c7
|
||||
googleauth (1.15.1) sha256=d61960893d0d573601d94a38889613b991f40a59ab755b5beed87bf2c3d3cb24
|
||||
googleauth (1.16.0) sha256=1e7b5c2ee7edc6a0f5a4a4312c579b3822dc0be2679d6d09ca19d8c7ca5bd5f1
|
||||
grape (2.4.0) sha256=3d59673e80f11d49ba86270b78344e5348dc057b318c2bbc1c01f3532f9b6aec
|
||||
grape_logging (3.0.0) sha256=7b62d984ce96df15d120508668debe307e6a59ac1c511f1d9b5f3b4bea793e13
|
||||
gravatar_image_tag (1.2.0) sha256=eb5630fea846b711e713b934a0178fb9785f02f4eb9ced8d6faa4d537c40fdcf
|
||||
@@ -1978,7 +1978,7 @@ CHECKSUMS
|
||||
lookbook (2.3.13) sha256=acfa04a1ba7a87b057c222d78a2d72763546f52549e97590993344c8373f6d21
|
||||
mail (2.9.0) sha256=6fa6673ecd71c60c2d996260f9ee3dd387d4673b8169b502134659ece6d34941
|
||||
marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee
|
||||
markly (0.14.1) sha256=911970dcf1d077461ab39d46a6e7b68f3e8acbb3a7a9fec41393bad46dceab4f
|
||||
markly (0.15.0) sha256=63328dac2f7c14d8fd4b9f9ed4c50f02f63a2b3e43ddeb8516aae528086f47ec
|
||||
matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b
|
||||
md_to_pdf (0.2.5)
|
||||
messagebird-rest (5.0.0) sha256=da4cc1efba3d5e4aa021fad07426c2cb6b326ce5670da5104bb8f6056a39d59c
|
||||
@@ -2014,7 +2014,7 @@ CHECKSUMS
|
||||
oj (3.16.12) sha256=ad9fad6a06dabcf4cfe6a420690a4375377685c16eee0ae88e8d38a43ed7b556
|
||||
okcomputer (1.19.0) sha256=8548935a82f725bdd8f2c329925a9f1a1bb2ce19ce26b47d7515665ee363b458
|
||||
omniauth (1.9.2)
|
||||
omniauth-openid-connect (0.4.2)
|
||||
omniauth-openid-connect (0.5.0)
|
||||
omniauth-openid_connect-providers (0.2.0)
|
||||
omniauth-saml (1.10.6) sha256=13dde22f4fd1beff0ef2d6dae576f7b68594f159990e8e886d8a02b32397afbd
|
||||
op-clamav-client (3.4.2) sha256=f28d697d11758a2ba3dc530cfdf4871a00ecd517631e8bac30dee30cd6012964
|
||||
@@ -2108,7 +2108,7 @@ CHECKSUMS
|
||||
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
|
||||
overviews (1.0.0)
|
||||
ox (2.14.23) sha256=4a9aedb4d6c78c5ebac1d7287dc7cc6808e14a8831d7adb727438f6a1b461b66
|
||||
pagy (43.1.5) sha256=b4b8a12a6edd6d66fa8501989903238a5f767605ca306a90745c82b00f8cb543
|
||||
pagy (43.1.8) sha256=06d89d0924ef74d239e2410f5e7baedcd73ebcd0a020bbe582af71c79dd9e38a
|
||||
paper_trail (17.0.0) sha256=1c2842061d3874ca7015908e821e2aa14f9b982af2acb2a7974713bf79021c85
|
||||
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
||||
parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70
|
||||
@@ -2123,7 +2123,7 @@ CHECKSUMS
|
||||
pg (1.6.2-x86_64-darwin) sha256=c441a55723584e2ae41749bf26024d7ffdfe1841b442308ed50cd6b7fda04115
|
||||
pg (1.6.2-x86_64-linux) sha256=525f438137f2d1411a1ebcc4208ec35cb526b5a3b285a629355c73208506a8ea
|
||||
pg (1.6.2-x86_64-linux-musl) sha256=e5c8668ffeaf7a9c3458a3dcb002dffa6d8ee1fca9ae534ffef861d2b15644ca
|
||||
plaintext (0.3.6) sha256=6ae13dce9f94e26bde4069109455a1d4948ae161e89350db1a9b37667b61f385
|
||||
plaintext (0.3.7) sha256=3b923152ee84d98898d459e9442ec9cca84d2acdbb8d232e2bcf41c5723e70d5
|
||||
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
|
||||
prawn (2.4.0) sha256=82062744f7126c2d77501da253a154271790254dfa8c309b8e52e79bc5de2abd
|
||||
prawn-table (0.2.2) sha256=336d46e39e003f77bf973337a958af6a68300b941c85cb22288872dc2b36addb
|
||||
@@ -2202,7 +2202,7 @@ CHECKSUMS
|
||||
rubocop-factory_bot (2.28.0) sha256=4b17fc02124444173317e131759d195b0d762844a71a29fe8139c1105d92f0cb
|
||||
rubocop-openproject (0.3.0) sha256=9554496e7ef0a2cf65dc2b32bee1bfa223b4f9ae058a5c603489d34e9001a828
|
||||
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
|
||||
rubocop-rails (2.33.3) sha256=848c011b58c1292f3066246c9eb18abf6ffcfbce28bc57c4ab888bbec79af74b
|
||||
rubocop-rails (2.34.2) sha256=10ff246ee48b25ffeabddc5fee86d159d690bb3c7b9105755a9c7508a11d6e22
|
||||
rubocop-rspec (3.8.0) sha256=28440dccb3f223a9938ca1f946bd3438275b8c6c156dab909e2cb8bc424cab33
|
||||
rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100
|
||||
ruby-duration (3.2.3) sha256=eb3d13b1df85067a015a8fb2ed8f1eec842a3b721e47c9b6fd74d2f356069784
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ gem 'omniauth-openid_connect-providers',
|
||||
|
||||
gem 'omniauth-openid-connect',
|
||||
git: 'https://github.com/opf/omniauth-openid-connect.git',
|
||||
ref: 'f0c1ecdb26e39017a9e929af75a166c772d960bb'
|
||||
ref: '825d06235b64f6bc872bba709f1c2d48fd5cede4'
|
||||
|
||||
group :opf_plugins do
|
||||
# included so that engines can reference OpenProject::Version
|
||||
|
||||
@@ -38,7 +38,8 @@ module Users
|
||||
}
|
||||
attribute :firstname
|
||||
attribute :lastname
|
||||
attribute :mail
|
||||
attribute :mail,
|
||||
writable: ->(*) { model.new_record? || model.id == user.id || user.admin? }
|
||||
attribute :admin,
|
||||
writable: ->(*) { user.admin? && model.id != user.id }
|
||||
attribute :language
|
||||
|
||||
@@ -37,6 +37,7 @@ class LoginForm < ApplicationForm
|
||||
|
||||
f.text_field(
|
||||
name: :username,
|
||||
id: "username#{@id_suffix}",
|
||||
value: @username,
|
||||
autofocus: @username.blank?,
|
||||
label: User.human_attribute_name(:login),
|
||||
@@ -46,6 +47,7 @@ class LoginForm < ApplicationForm
|
||||
|
||||
f.text_field(
|
||||
name: :password,
|
||||
id: "password#{@id_suffix}",
|
||||
type: :password,
|
||||
autofocus: @username.present?,
|
||||
label: User.human_attribute_name(:password),
|
||||
@@ -55,6 +57,7 @@ class LoginForm < ApplicationForm
|
||||
|
||||
if Setting::Autologin.enabled?
|
||||
f.check_box name: "autologin",
|
||||
id: "autologin#{@id_suffix}",
|
||||
checked: false,
|
||||
value: 1,
|
||||
label: I18n.t("users.autologins.prompt",
|
||||
@@ -85,9 +88,10 @@ class LoginForm < ApplicationForm
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(back_url: nil, username: nil)
|
||||
def initialize(back_url: nil, username: nil, id_suffix: nil)
|
||||
super()
|
||||
@back_url = back_url
|
||||
@username = username
|
||||
@id_suffix = id_suffix
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,7 +33,14 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
url: { controller: "/account", action: "login" },
|
||||
method: :post
|
||||
) do |form_builder|
|
||||
render(LoginForm.new(form_builder, back_url: back_url_to_current_page, username: params[:username]))
|
||||
render(
|
||||
LoginForm.new(
|
||||
form_builder,
|
||||
back_url: back_url_to_current_page,
|
||||
username: params[:username],
|
||||
id_suffix: "-pulldown"
|
||||
)
|
||||
)
|
||||
end
|
||||
%>
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<div class="form--field -required">
|
||||
<%= f.text_field :mail,
|
||||
required: true,
|
||||
disabled: !(@user.new_record? || User.current.admin?),
|
||||
container_class: "-middle" %>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1227,7 +1227,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"
|
||||
@@ -1593,7 +1593,7 @@ cs:
|
||||
meeting:
|
||||
error_conflict: "Nelze uložit, protože schůzku mezitím aktualizoval někdo jiný. Znovu načtěte stránku."
|
||||
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í "
|
||||
@@ -1891,11 +1891,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"
|
||||
@@ -2943,7 +2943,7 @@ cs:
|
||||
instructions_after_error: "Zkuste se znovu přihlásit kliknutím na %{signin}. Pokud chyba přetrvává, požádejte správce o pomoc."
|
||||
menus:
|
||||
admin:
|
||||
mail_notification: "E-mailové notifikace"
|
||||
mail_notification: "E-mailová upozornění"
|
||||
mails_and_notifications: "E-maily a oznámení"
|
||||
aggregation: "Agregace"
|
||||
api_and_webhooks: "API & Webhooky"
|
||||
@@ -3007,7 +3007,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."
|
||||
@@ -3447,9 +3447,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"
|
||||
@@ -3473,7 +3473,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"
|
||||
@@ -3641,7 +3641,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"
|
||||
@@ -3749,28 +3749,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 <b>zmíněni v komentáři</b>"
|
||||
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"
|
||||
@@ -3779,7 +3779,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"
|
||||
@@ -4080,7 +4080,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"
|
||||
@@ -4550,7 +4550,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. <strong>Poznámka:</strong> Před povolením si prosím přečtěte <a href="%{link}" target="_blank">podrobnosti o odběru</a>.
|
||||
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"
|
||||
@@ -4727,7 +4727,7 @@ cs:
|
||||
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_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"
|
||||
|
||||
@@ -85,11 +85,11 @@ de:
|
||||
title: "Enterprise-Token hinzufügen"
|
||||
type_token_text: "Enterprise-Token Text"
|
||||
token_placeholder: "Enterprise-Token Text hier einfügen"
|
||||
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"
|
||||
@@ -866,10 +866,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:
|
||||
@@ -913,7 +913,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."
|
||||
@@ -1016,7 +1016,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"
|
||||
@@ -1257,7 +1257,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"
|
||||
@@ -1750,7 +1750,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:
|
||||
@@ -2541,7 +2541,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}"
|
||||
@@ -2814,7 +2814,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"
|
||||
@@ -3111,7 +3111,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"
|
||||
@@ -4171,7 +4171,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:
|
||||
@@ -4791,7 +4791,7 @@ de:
|
||||
warning_user_limit_reached_admin: >
|
||||
Das Hinzufügen zusätzlicher Benutzer überschreitet das aktuelle Benutzerlimit. Bitte <a href="%{upgrade_url}">aktualisieren Sie Ihr Abonnement</a> 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:
|
||||
|
||||
@@ -921,7 +921,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."
|
||||
|
||||
@@ -107,17 +107,17 @@ ja:
|
||||
jemalloc_allocator: Jemalloc メモリアロケータ
|
||||
journal_aggregation:
|
||||
explanation:
|
||||
text: "ユーザーの個々のアクション(例えば、ワークパッケージを2回更新する)は、それらの年齢差が指定されたタイムスパン未満である場合、単一のアクションに集約されます。これらはアプリケーション内で1つのアクションとして表示されます。これはまた、送信されるメールの数を減らし、 %{webhook_link} の遅延にも影響します。"
|
||||
text: "ユーザーの個々のアクション (例:ワークパッケージを2回更新する)は、指定された時間範囲よりも時間差が小さい場合、単一のアクションに集約されます。 これらはアプリケーション内で単一のアクションとして表示されます。 これにより、送信されるメールの数が減少し、 %{webhook_link} の遅延にも影響します。"
|
||||
link: "webhook"
|
||||
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: "トークンが生成されました"
|
||||
@@ -130,21 +130,21 @@ ja:
|
||||
edit:
|
||||
label_delete_scim_client: "SCIM クライアントを削除"
|
||||
form:
|
||||
auth_provider_description: "これは、SCIM プロバイダによって追加されたユーザが OpenProject で認証するために使用するサービスです。"
|
||||
authentication_method_description_html: "これは SCIM クライアントが OpenProject で認証する方法です。OAuth トークンに<code>scim_v2</code>スコープが含まれていることを確認してください。"
|
||||
description: "これらの設定オプションの詳細については、[SCIMクライアントの設定に関する文書](docs_url)を参照してください。"
|
||||
auth_provider_description: "これは、SCIMプロバイダが追加したユーザーがOpenProjectでの認証に使用するサービスです。"
|
||||
authentication_method_description_html: "これは SCIM クライアントが OpenProject で認証する方法です。OAuth トークンに <code>scim_v2</code> スコープが含まれていることを確認してください。"
|
||||
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クライアントがまだ設定されていません"
|
||||
@@ -657,26 +657,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:
|
||||
@@ -698,14 +698,14 @@ 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は無効です。'
|
||||
news:
|
||||
index:
|
||||
no_results_title_text: 現在、報告するニュースはありません。
|
||||
no_results_content_text: ニュース項目を追加
|
||||
users:
|
||||
autologins:
|
||||
prompt: "ログインしたまま %{num_days}"
|
||||
prompt: "%{num_days} のログインを維持"
|
||||
sessions:
|
||||
session_name: "%{browser_name} %{browser_version} の %{os_name}"
|
||||
browser: "ブラウザ"
|
||||
@@ -719,17 +719,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:
|
||||
@@ -786,7 +786,7 @@ ja:
|
||||
placeholder_users:
|
||||
right_to_manage_members_missing: >
|
||||
プレースホルダーユーザを削除する権限がありません。 プレースホルダー ユーザーがメンバーであるすべてのプロジェクトのメンバーを管理する権利はありません。
|
||||
delete_tooltip: "プレースホルダー・ユーザーの削除"
|
||||
delete_tooltip: "プレースホルダー ユーザーを削除"
|
||||
deletion_info:
|
||||
heading: "プレースホルダー ユーザー %{name} を削除"
|
||||
data_consequences: >
|
||||
@@ -804,11 +804,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: 現在、ステータス報告はありません。
|
||||
@@ -819,19 +819,20 @@ ja:
|
||||
このステータスの色を割り当てたり変更する場合にクリックします。
|
||||
ステータスボタンに表示され、テーブル内のワークパッケージを強調表示するために使用できます。
|
||||
status_default_text: |-
|
||||
新しいワークパッケージは、デフォルトでこのタイプに設定される。読み取り専用にはできない。
|
||||
新しいワークパッケージはデフォルトでこのタイプに設定されています。読み取り専用にすることはできません。
|
||||
status_excluded_from_totals_text: |-
|
||||
このステータスを持つワークパッケージを、階層内の「作業」、「
|
||||
残作業」、「完了率」の合計から除外するには、このオプションをオンにします。
|
||||
このオプションをオンにすると、このステータスのワークパッケージを合計作業量、
|
||||
残作業量、および階層構造で完了させることができます。
|
||||
status_percent_complete_text: |-
|
||||
<a href="%{href}">ステータスベースの進捗計算モードでは</a>、このステータスが選択されると、作業
|
||||
パッケージの「完了%」が自動的にこの値に設定される。
|
||||
ワークベースモードでは無視される。
|
||||
status_readonly_html: |
|
||||
このステータスを持つワークパッケージを読み取り専用としてマークするには、このオプションをチェックする。
|
||||
ステータス以外の属性は変更できません。
|
||||
ワークパッケージを読み取り専用としてマークするには、このオプションをオンにしてください。
|
||||
ステータスを除いて変更することはできません。
|
||||
|
||||
<br>
|
||||
<strong>注意</strong>: 継承された値 (子やリレーションなど) は適用されます。
|
||||
<strong>メモ</strong>: 継承された値 (例えば、子や関連) が適用されます。
|
||||
index:
|
||||
no_results_title_text: 現在、ワークパッケージのステータスはありません。
|
||||
no_results_content_text: 新しいステータスを追加
|
||||
@@ -841,7 +842,7 @@ ja:
|
||||
is_readonly: "読み取り専用"
|
||||
excluded_from_totals: "合計から除外"
|
||||
themes:
|
||||
dark: "暗い"
|
||||
dark: "ダーク"
|
||||
light: "ライト"
|
||||
sync_with_os: "自動(OSのテーマ設定に追従)"
|
||||
types:
|
||||
@@ -949,15 +950,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: "ワークパッケージの共有のためのワークフローがありません"
|
||||
@@ -982,9 +983,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} を追加"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
@@ -452,7 +452,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"
|
||||
|
||||
@@ -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: "子ページへのリンク"
|
||||
@@ -209,10 +209,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: "営業日とみなす曜日を変更すると、このサイト内のすべてのプロジェクトのすべてのワークパッケージの開始日と終了日に影響を与える可能性があります。"
|
||||
@@ -294,14 +294,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 <a href="%{myAccessTokensUrl}" target="_blank">access tokens</a> 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}"
|
||||
@@ -314,7 +314,7 @@ ja:
|
||||
label_add_row_before: "前に行を追加"
|
||||
label_add_selected_columns: "選択した列を追加"
|
||||
label_added_by: "追加した人"
|
||||
label_added_time_by: '<a href="%{authorLink}">%{author}</a> が %{age} に追加しました'
|
||||
label_added_time_by: '追加 <a href="%{authorLink}">%{author}</a> %{age}'
|
||||
label_ago: "○日前"
|
||||
label_all: "全て"
|
||||
label_all_projects: "すべてのプロジェクト"
|
||||
@@ -426,7 +426,7 @@ ja:
|
||||
label_report: "レポート"
|
||||
label_repository_plural: "リポジトリ"
|
||||
label_save_as: "名前をつけて保存"
|
||||
label_search_columns: "列を検索"
|
||||
label_search_columns: "列を検索する"
|
||||
label_select_watcher: "ウォッチャーを選択..."
|
||||
label_selected_filter_list: "選択されたフィルタ"
|
||||
label_show_attributes: "すべての属性を表示"
|
||||
@@ -464,8 +464,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: "アップロードした人"
|
||||
@@ -496,7 +496,7 @@ ja:
|
||||
label_version_plural: "バージョン"
|
||||
label_view_has_changed: "このビューには未保存の変更があります。 クリックすると保存します。"
|
||||
help_texts:
|
||||
show_modal: "ヘルプテキストを表示"
|
||||
show_modal: "ヘルプテキストを表示する"
|
||||
onboarding:
|
||||
buttons:
|
||||
skip: "スキップ"
|
||||
@@ -504,7 +504,7 @@ ja:
|
||||
got_it: "了承"
|
||||
steps:
|
||||
help_menu: "ヘルプ(?)メニューは、<b>その他のヘルプリソースを</b>提供します。ここでは、ユーザーガイド、役立つハウツービデオなどを見つけることができます。 <br> OpenProjectでの作業をお楽しみください!"
|
||||
members: "新しい <b>メンバー</b> をプロジェクトに招待します。"
|
||||
members: "新しい<b>メンバーを</b>プロジェクトに招待する。"
|
||||
quick_add_button: "ヘッダーナビゲーションにあるプラス(+)アイコンをクリックして、<b>新規プロジェクトを作成</b>したり、<b>同僚を招待</b>したりできます。"
|
||||
sidebar_arrow: "プロジェクトの<b>メインメニューに</b>戻るには、左上の矢印を使います。"
|
||||
welcome: "3分間のイントロダクションツアーで、最も<b>重要な</b>機能を学びましょう。 <br> 最後までステップを完了することをお勧めします。ツアーはいつでも再開できます。"
|
||||
@@ -611,36 +611,36 @@ 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: "プロジェクトの設定を削除する"
|
||||
password_confirmation:
|
||||
field_description: "この変更を確認するには、アカウントのパスワードを入力する必要があります。"
|
||||
title: "続行するにはパスワードを確認してください"
|
||||
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メールリマインダーを一時停止する"
|
||||
@@ -1170,7 +1170,7 @@ ja:
|
||||
toggle_title: "ベースライン"
|
||||
clear: "クリア"
|
||||
apply: "適用"
|
||||
header_description: "過去のいずれかの時点からこのリストに加えられた変更を強調する。"
|
||||
header_description: "過去の選択した時点からこのリストに加えられた変更をハイライト"
|
||||
show_changes_since: "以降の変更を表示する"
|
||||
help_description: "ベースラインの基準タイムゾーン。"
|
||||
time_description: "現地時間: %{datetime}"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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ă"
|
||||
|
||||
@@ -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: "Создать"
|
||||
|
||||
@@ -3152,7 +3152,7 @@ ro:
|
||||
label_duplicated_by: "dublat de"
|
||||
label_duplicate: "duplicat"
|
||||
label_duplicates: "dublează"
|
||||
label_edit: "Editează"
|
||||
label_edit: "Editare"
|
||||
label_edit_attribute: "Edit attribute"
|
||||
label_edit_x: "Editare: %{x}"
|
||||
label_enable_multi_select: "Comutare selecție multiplă"
|
||||
@@ -3210,7 +3210,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}"
|
||||
|
||||
@@ -2325,8 +2325,8 @@ sl:
|
||||
- "avgust"
|
||||
- "september"
|
||||
- "oktober"
|
||||
- "november"
|
||||
- "december"
|
||||
- "November"
|
||||
- "December"
|
||||
order:
|
||||
- :leto
|
||||
- :mesec
|
||||
|
||||
@@ -3278,7 +3278,7 @@ uk:
|
||||
label_index_by_title: "Індекс за назвою"
|
||||
label_information: "Інформація"
|
||||
label_information_plural: "Інформація"
|
||||
label_installation_guides: "Інструкції із встановлення"
|
||||
label_installation_guides: "Інструкції зі встановлення"
|
||||
label_integer: "Ціле число"
|
||||
label_interface: "Інтерфейс"
|
||||
label_internal: "Власне"
|
||||
|
||||
@@ -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 演示项目
|
||||
|
||||
@@ -87,7 +87,7 @@ zh-CN:
|
||||
token_placeholder: "在此处粘贴您的企业版支持令牌"
|
||||
add_token: "上传企业版支持令牌"
|
||||
replace_token: "替换您当前的支持令牌"
|
||||
order: "订购本地部署版的 Enterprise edition"
|
||||
order: "订购本地部署的 Enterprise edition"
|
||||
paste: "粘贴您企业版的支持令牌"
|
||||
required_for_feature: "此功能仅限具激活的企业版支持令牌的订阅者使用。"
|
||||
enterprise_link: "如需了解详细信息,请单击此处。"
|
||||
@@ -1163,7 +1163,7 @@ zh-CN:
|
||||
page: "页"
|
||||
row_count: "行数"
|
||||
column_count: "列数"
|
||||
widgets: "微件"
|
||||
widgets: "小部件"
|
||||
journal:
|
||||
notes: "备注"
|
||||
cause_type: "Cause 类型"
|
||||
@@ -3395,7 +3395,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} 超时"
|
||||
@@ -4118,7 +4118,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: "如果您在配置新文件存储方面需要帮助,请查看文档:"
|
||||
@@ -4316,7 +4316,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: "计算 完成% 层次结构总数"
|
||||
@@ -4738,7 +4738,7 @@ zh-CN:
|
||||
warning_user_limit_reached_admin: >
|
||||
添加额外的用户将超出当前限制。请<a href="%{upgrade_url}">升级您的计划</a>,以确保外部用户能够访问此实例。
|
||||
warning_user_limit_reached_instructions: >
|
||||
您已达到用户限制(%{current}/%{max} 活跃用户)。请联系 sales@openproject.com 升级您的企业版计划以添加额外用户。
|
||||
您达到了用户限制(%{current}/%{max}活跃用户)。 请联系sales@openproject.com以升级您的Enterprise edition计划并添加其他用户。
|
||||
warning_protocol_mismatch_html: >
|
||||
|
||||
warning_bar:
|
||||
|
||||
@@ -3096,7 +3096,7 @@ 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: "資料夾"
|
||||
@@ -3111,8 +3111,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}"
|
||||
@@ -3124,7 +3124,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: "在"
|
||||
@@ -3173,7 +3173,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: "清單"
|
||||
|
||||
@@ -1566,7 +1566,8 @@ en:
|
||||
not_a_datetime: "is not a valid date time."
|
||||
not_a_number: "is not a number."
|
||||
not_allowed: "is invalid because of missing permissions."
|
||||
not_json: "is not a valid JSON object."
|
||||
not_json: "is not parseable as JSON."
|
||||
not_json_object: "is not a JSON object."
|
||||
not_an_integer: "is not an integer."
|
||||
not_an_iso_date: "is not a valid date. Required format: YYYY-MM-DD."
|
||||
not_same_project: "doesn't belong to the same project."
|
||||
@@ -2234,6 +2235,7 @@ en:
|
||||
button_print: "Print"
|
||||
button_quote: "Quote"
|
||||
button_remove: Remove
|
||||
button_remove_permanently: "Remove permanently"
|
||||
button_remove_reminder: "Remove reminder"
|
||||
button_rename: "Rename"
|
||||
button_replace: "Replace"
|
||||
@@ -4949,6 +4951,7 @@ en:
|
||||
For more information on what the check provides, what data is needed to provide available updates, and how to disable this check, please visit <a href="%{more_info_url}">the configuration documentation</a>.
|
||||
text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?"
|
||||
text_permanent_delete_confirmation_checkbox_label: "I understand that this deletion cannot be reversed"
|
||||
text_permanent_remove_confirmation_checkbox_label: "I understand that this removal cannot be reversed"
|
||||
text_plugin_assets_writable: "Plugin assets directory writable"
|
||||
text_powered_by: "Powered by %{link}"
|
||||
text_project_identifier_info: "Only lower case letters (a-z), numbers, dashes and underscores are allowed, must start with a lower case letter."
|
||||
|
||||
Generated
+1202
-294
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,8 @@
|
||||
"@angular-eslint/template-parser": "20.7.0",
|
||||
"@angular/language-service": "20.3.14",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@html-eslint/eslint-plugin": "^0.49.0",
|
||||
"@html-eslint/parser": "^0.49.0",
|
||||
"@html-eslint/eslint-plugin": "^0.50.0",
|
||||
"@html-eslint/parser": "^0.50.0",
|
||||
"@jsdevtools/coverage-istanbul-loader": "3.0.5",
|
||||
"@stylistic/eslint-plugin": "^5.5.0",
|
||||
"@types/codemirror": "5.60.5",
|
||||
@@ -23,7 +23,7 @@
|
||||
"@types/dragula": "^3.7.5",
|
||||
"@types/flot": "^0.0.36",
|
||||
"@types/hammerjs": "^2.0.36",
|
||||
"@types/jasmine": "~5.1.12",
|
||||
"@types/jasmine": "~5.1.13",
|
||||
"@types/jquery": "^3.5.33",
|
||||
"@types/jqueryui": "^1.12.24",
|
||||
"@types/lodash": "^4.17.21",
|
||||
@@ -38,7 +38,7 @@
|
||||
"@types/webpack-env": "^1.16.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.3",
|
||||
"@typescript-eslint/parser": "8.46.2",
|
||||
"angular-eslint": "^20.5.1",
|
||||
"angular-eslint": "^21.0.1",
|
||||
"browserslist": "^4.27.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
@@ -59,7 +59,7 @@
|
||||
"source-map-explorer": "^2.5.2",
|
||||
"ts-node": "~10.9.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.46.2",
|
||||
"typescript-eslint": "^8.48.0",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"wscat": "^6.1.0"
|
||||
},
|
||||
@@ -134,14 +134,14 @@
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"codemirror": "^5.62.0",
|
||||
"copy-text-to-clipboard": "^3.2.2",
|
||||
"core-js": "^3.46.0",
|
||||
"core-js": "^3.47.0",
|
||||
"crossvent": "^1.5.4",
|
||||
"dom-autoscroller": "^2.2.8",
|
||||
"dom-plane": "^1.0.2",
|
||||
"dragula": "^3.7.3",
|
||||
"es6-slide-up-down": "^1.0.0",
|
||||
"flatpickr": "^4.6.13",
|
||||
"glob": "^12.0.0",
|
||||
"glob": "^13.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"i18n-js": "^4.3.0",
|
||||
"idiomorph": "^0.7.4",
|
||||
|
||||
@@ -51,6 +51,13 @@ export default class extends Controller {
|
||||
useMeta(this, { suffix: false });
|
||||
this.focusInput();
|
||||
this.addNotes();
|
||||
this.storeInitialValues();
|
||||
}
|
||||
|
||||
storeInitialValues():void {
|
||||
this.element.querySelectorAll('input[type="text"], input[type="number"]').forEach((input) => {
|
||||
(input as HTMLInputElement).dataset.initialValue = (input as HTMLInputElement).value;
|
||||
});
|
||||
}
|
||||
|
||||
focusInput():void {
|
||||
|
||||
@@ -41,8 +41,20 @@ export function appendCollapsedState(
|
||||
}
|
||||
|
||||
export function hasUnsavedChanges():boolean {
|
||||
const textInputs = Array.from(document.querySelectorAll('input[type="text"], input[type="number"]'));
|
||||
const allTextSaved = textInputs.every((input) => (input as HTMLInputElement).value.trim().length === 0);
|
||||
let hasTextChanges = false;
|
||||
|
||||
return !allTextSaved || window.OpenProject.pageWasEdited;
|
||||
document.querySelectorAll('input[type="text"], input[type="number"]').forEach((input) => {
|
||||
const currentValue = (input as HTMLInputElement).value;
|
||||
const initialValue = (input as HTMLInputElement).dataset.initialValue;
|
||||
|
||||
if (initialValue === undefined) {
|
||||
if (currentValue.trim().length > 0) {
|
||||
hasTextChanges = true;
|
||||
}
|
||||
} else if (currentValue !== initialValue) {
|
||||
hasTextChanges = true;
|
||||
}
|
||||
});
|
||||
|
||||
return hasTextChanges || window.OpenProject.pageWasEdited;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ require "open3"
|
||||
module OpenProject
|
||||
module VERSION # :nodoc:
|
||||
MAJOR = 17
|
||||
MINOR = 0
|
||||
MINOR = 1
|
||||
PATCH = 0
|
||||
|
||||
class << self
|
||||
|
||||
@@ -273,8 +273,9 @@ module OpenProject
|
||||
request_headers)
|
||||
|
||||
header = %{#{scheme} realm="#{scope_realm(scope)}"}
|
||||
header << %{, error="#{error}"} if error
|
||||
header << %{, error_description="#{error_description}"} if error && error_description
|
||||
header << %{, scope="#{escape_string scope}"} if scope && scheme == "Bearer"
|
||||
header << %{, error="#{escape_string error}"} if error
|
||||
header << %{, error_description="#{escape_string error_description}"} if error && error_description
|
||||
header
|
||||
end
|
||||
|
||||
@@ -285,6 +286,10 @@ module OpenProject
|
||||
.select { |_, info| scope.nil? or info.strategies.intersect?(strategies) }
|
||||
.keys
|
||||
end
|
||||
|
||||
def escape_string(string)
|
||||
string.to_s.dump[1..-2]
|
||||
end
|
||||
end
|
||||
|
||||
module AuthHeaders
|
||||
|
||||
@@ -37,6 +37,7 @@ module OpenProject
|
||||
headers(
|
||||
"WWW-Authenticate" => OpenProject::Authentication::WWWAuthenticate.response_header(
|
||||
default_auth_scheme: "Bearer",
|
||||
scope:,
|
||||
error:,
|
||||
error_description:
|
||||
)
|
||||
|
||||
@@ -52,7 +52,24 @@ module Redmine
|
||||
validate: false,
|
||||
autosave: true
|
||||
|
||||
validation_options = options[:validate_on] ? { on: options[:validate_on] } : {}
|
||||
validation_options = {}
|
||||
|
||||
if options[:validate_on]
|
||||
validation_options[:on] = options[:validate_on]
|
||||
end
|
||||
|
||||
if options[:validate_except_on]
|
||||
validation_options[:except_on] = options[:validate_except_on]
|
||||
end
|
||||
|
||||
if options[:validate_if]
|
||||
validation_options[:if] = options[:validate_if]
|
||||
end
|
||||
|
||||
if options[:validate_unless]
|
||||
validation_options[:unless] = options[:validate_unless]
|
||||
end
|
||||
|
||||
validate :validate_custom_values, **validation_options
|
||||
send :include, Redmine::Acts::Customizable::InstanceMethods
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ ro:
|
||||
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:
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#++
|
||||
zh-TW:
|
||||
plugin_openproject_backlogs:
|
||||
name: "OpenProject待辦事項"
|
||||
name: "OpenProject代辦事項"
|
||||
description: "此模組新增了讓敏捷團隊能夠在 Scrum 專案中使用 OpenProject 的功能。"
|
||||
activerecord:
|
||||
attributes:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -44,7 +44,7 @@ class TimeEntry < ApplicationRecord
|
||||
MAX_TIME = (60 * 24) - 1 # => 23:59
|
||||
SECONDS_PER_HOUR = 3600.0
|
||||
|
||||
acts_as_customizable
|
||||
acts_as_customizable validate_unless: ->(te) { te.new_record? && te.ongoing? }
|
||||
|
||||
acts_as_journalized
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ ja:
|
||||
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: "報告された時間とコストを削除する"
|
||||
|
||||
@@ -663,5 +663,64 @@ RSpec.describe TimeEntry do
|
||||
comments: "lorem")
|
||||
end
|
||||
let!(:custom_field) { create(:time_entry_custom_field, :string, is_required: false) }
|
||||
|
||||
context "with a required custom field" do
|
||||
let!(:custom_field) { create(:time_entry_custom_field, :string, is_required: true) }
|
||||
|
||||
before do
|
||||
new_model_instance.activate_custom_field_validations!
|
||||
end
|
||||
|
||||
context "for a new not-ongoing entry" do
|
||||
let!(:new_model_instance) do
|
||||
build(:time_entry, entity: work_package, spent_on: date, hours:, user:, ongoing: false)
|
||||
end
|
||||
|
||||
it "validates presence of the custom field" do
|
||||
new_model_instance.custom_field_values = { custom_field.id => nil }
|
||||
expect(new_model_instance).not_to be_valid
|
||||
|
||||
new_model_instance.custom_field_values = { custom_field.id => "Some value" }
|
||||
expect(new_model_instance).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "for a new ongoing entry" do
|
||||
let!(:new_model_instance) do
|
||||
build(:time_entry, entity: work_package, spent_on: date, hours:, user:, ongoing: true)
|
||||
end
|
||||
|
||||
it "does not validate presence of the custom field" do
|
||||
new_model_instance.custom_field_values = { custom_field.id => nil }
|
||||
expect(new_model_instance).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "for a persisted not-ongoing entry" do
|
||||
let!(:new_model_instance) do
|
||||
create(:time_entry, entity: work_package, spent_on: date, hours:, user:, ongoing: false)
|
||||
end
|
||||
|
||||
it "validates presence of the custom field" do
|
||||
new_model_instance.custom_field_values = { custom_field.id => nil }
|
||||
|
||||
expect(new_model_instance).not_to be_valid
|
||||
|
||||
new_model_instance.custom_field_values = { custom_field.id => "Some value" }
|
||||
expect(new_model_instance).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "for a persisted ongoing entry" do
|
||||
let!(:new_model_instance) do
|
||||
create(:time_entry, entity: work_package, spent_on: date, hours:, user:, ongoing: true)
|
||||
end
|
||||
|
||||
it "validates presence of the custom field" do
|
||||
new_model_instance.custom_field_values = { custom_field.id => nil }
|
||||
expect(new_model_instance).not_to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: "群組名字屬性"
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
<%=
|
||||
flex_layout(align_items: :center, justify_content: :space_between) do |row|
|
||||
row.with_column(flex: 1, classes: "ellipsis") do
|
||||
render(
|
||||
Users::AvatarComponent.new(
|
||||
user: @participant.user,
|
||||
classes: "op-principal_flex",
|
||||
hover_card: { active: true, target: :custom }
|
||||
)
|
||||
)
|
||||
end
|
||||
row.with_column do
|
||||
flex_layout(align_items: :center, justify_content: :space_between) do |actions|
|
||||
if @meeting.in_progress?
|
||||
actions.with_column(ml: 2) do
|
||||
render(Meetings::Participants::AttendedToggleButtonComponent.new(meeting: @meeting, participant: @participant))
|
||||
end
|
||||
end
|
||||
actions.with_column(ml: 2) do
|
||||
render(
|
||||
Primer::Beta::IconButton.new(
|
||||
scheme: :invisible,
|
||||
tag: :a,
|
||||
href: meeting_participant_path(@meeting, @participant),
|
||||
icon: :x,
|
||||
aria: { label: I18n.t("meeting.participants.label.remove_participant") },
|
||||
data: {
|
||||
turbo_method: :delete,
|
||||
test_selector: "remove_button_#{@participant.user_id}"
|
||||
}
|
||||
)
|
||||
component_wrapper do
|
||||
flex_layout(align_items: :center, justify_content: :space_between) do |row|
|
||||
row.with_column(flex: 1, classes: "ellipsis") do
|
||||
render(
|
||||
Users::AvatarComponent.new(
|
||||
user: @participant.user,
|
||||
classes: "op-principal_flex",
|
||||
hover_card: { active: true, target: :custom }
|
||||
)
|
||||
)
|
||||
end
|
||||
row.with_column do
|
||||
flex_layout(align_items: :center, justify_content: :space_between) do |actions|
|
||||
if @meeting.in_progress?
|
||||
actions.with_column(ml: 2) do
|
||||
render(Meetings::Participants::AttendedToggleButtonComponent.new(meeting: @meeting, participant: @participant))
|
||||
end
|
||||
end
|
||||
actions.with_column(ml: 2) do
|
||||
render(
|
||||
Primer::Beta::IconButton.new(
|
||||
scheme: :invisible,
|
||||
tag: :a,
|
||||
href: meeting_participant_path(@meeting, @participant),
|
||||
icon: :x,
|
||||
aria: { label: I18n.t("meeting.participants.label.remove_participant") },
|
||||
data: {
|
||||
turbo_method: :delete,
|
||||
test_selector: "remove_button_#{@participant.user_id}"
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,5 +41,9 @@ module Meetings
|
||||
@meeting = meeting
|
||||
@participant = participant
|
||||
end
|
||||
|
||||
def wrapper_uniq_by
|
||||
@participant.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -95,6 +95,12 @@ module Meetings
|
||||
)
|
||||
end
|
||||
|
||||
def update_box_row_component_via_turbo_stream(participant:, meeting: @meeting)
|
||||
update_via_turbo_stream(
|
||||
component: Meetings::Participants::BoxRowComponent.new(meeting:, participant:)
|
||||
)
|
||||
end
|
||||
|
||||
def update_show_items_via_turbo_stream(meeting: @meeting, current_occurrence: nil)
|
||||
meeting.sections.each do |meeting_section|
|
||||
update_show_items_of_section_via_turbo_stream(meeting_section:, current_occurrence:)
|
||||
|
||||
@@ -65,8 +65,7 @@ class MeetingParticipantsController < ApplicationController
|
||||
def toggle_attendance
|
||||
@participant.toggle!(:attended)
|
||||
|
||||
update_add_user_form_component_via_turbo_stream
|
||||
update_list_component_via_turbo_stream
|
||||
update_box_row_component_via_turbo_stream(participant: @participant)
|
||||
update_sidebar_participants_component_via_turbo_stream(meeting: @meeting)
|
||||
|
||||
respond_with_turbo_streams
|
||||
|
||||
@@ -35,7 +35,7 @@ module RecurringMeetings
|
||||
protected
|
||||
|
||||
def validate_params
|
||||
@old_schedule = model.full_schedule_in_words
|
||||
@old_schedule_model = model.dup
|
||||
@old_location = model.template.location
|
||||
super
|
||||
end
|
||||
@@ -159,11 +159,16 @@ module RecurringMeetings
|
||||
.participants
|
||||
.invited
|
||||
.find_each do |participant|
|
||||
# Generate old schedule in each participant's locale
|
||||
old_schedule = User.execute_as(participant.user) do
|
||||
@old_schedule_model.full_schedule_in_words
|
||||
end
|
||||
|
||||
MeetingSeriesMailer.updated(
|
||||
recurring_meeting,
|
||||
participant.user,
|
||||
User.current,
|
||||
changes: { old_schedule: @old_schedule, old_location: @old_location }
|
||||
changes: { old_schedule:, old_location: @old_location }
|
||||
).deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
@@ -466,7 +466,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"
|
||||
|
||||
@@ -227,9 +227,9 @@ ja:
|
||||
header_occurrence: "キャンセル: ミーティング発生'%{title}'"
|
||||
header_series: "キャンセル: ミーティングシリーズ '%{title}'"
|
||||
summary_occurrence: "%{title}'の発生は %{actor}によってキャンセルされました。"
|
||||
summary_series: "ミーティングシリーズ '%{title}' は %{actor}によりキャンセルされました。"
|
||||
summary: "'%{title}' は %{actor}によってキャンセルされた。"
|
||||
date_time: "予定日時"
|
||||
summary_series: "ミーティングシリーズ '%{title}' は、 %{actor} によってキャンセルされました。"
|
||||
summary: "'%{title}' は %{actor} によってキャンセルされました。"
|
||||
date_time: "スケジュールされた日時"
|
||||
updated:
|
||||
header: "Meeting '%{title}' has been updated"
|
||||
summary: "Meeting '%{title}' has been updated by %{actor}"
|
||||
@@ -256,7 +256,7 @@ ja:
|
||||
title: "ミーティングのキャンセル"
|
||||
heading: "このミーティングをキャンセルしますか?"
|
||||
confirmation_message_html: >
|
||||
テンプレートにない会議情報は失われます。 続行しますか?
|
||||
テンプレートにない会議情報は失われます。 続けますか?
|
||||
confirm_button: "発生をキャンセル"
|
||||
blankslate:
|
||||
title: "表示する会議がありません"
|
||||
@@ -440,7 +440,7 @@ ja:
|
||||
confirm_button: "この予定をキャンセル"
|
||||
end_series_dialog:
|
||||
title: "一連の会議を終了"
|
||||
notice_successful_notification: "参加者全員にカレンダー更新の電子メールを送信"
|
||||
notice_successful_notification: "すべての出席者にカレンダーの更新をメールしました"
|
||||
notice_timezone_missing: タイムゾーンが設定されていない場合、%{zone} が使用されます。タイムゾーンを選択するには、ここをクリックしてください。
|
||||
notice_meeting_updated: "このページは他の誰かによって更新されました。変更を表示するには再読み込みしてください。"
|
||||
permission_create_meetings: "会議を作成"
|
||||
@@ -519,7 +519,7 @@ ja:
|
||||
このバックログは、このワンタイムミーティングに固有のものです.アイテムをドラッグして追加またはミーティングの議題から削除することができます.
|
||||
label_agenda_backlog_clear_title: "議題のバックログをクリアしますか?"
|
||||
text_agenda_backlog_clear_description: >
|
||||
現在アジェンダバックログにあるすべての項目を削除してもよろしいですか?このアクションは元に戻せません。
|
||||
議題のバックログ内のすべての項目を削除してもよろしいですか?この操作は取り消せません。
|
||||
label_series_backlog: "シリーズバックログ"
|
||||
text_series_backlog: >
|
||||
バックログはこのシリーズのすべての出現と共有されます。 項目をドラッグして、特定のミーティングから項目を追加または削除できます。
|
||||
@@ -551,7 +551,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: "You are currently in draft mode. This meeting will not send out any calendar updates or invites, even if you change meeting details or add/remove participants."
|
||||
text_exit_draft_mode_dialog_title: "Open this meeting and send invites?"
|
||||
|
||||
@@ -87,4 +87,42 @@ RSpec.describe "Meetings edit agenda", :js do
|
||||
expect(show_page).to have_selector :rich_text, "Notes", text: "More notes..."
|
||||
end
|
||||
end
|
||||
|
||||
it "correctly tracks unsaved changes in agenda item forms (Bug #68654)" do
|
||||
show_page.add_agenda_item do
|
||||
fill_in "Title", with: "First Item"
|
||||
end
|
||||
show_page.add_agenda_item do
|
||||
fill_in "Title", with: "Second Item"
|
||||
end
|
||||
|
||||
show_page.expect_agenda_item title: "First Item"
|
||||
show_page.expect_agenda_item title: "Second Item"
|
||||
show_page.assert_agenda_order! "First Item", "Second Item"
|
||||
|
||||
first_item = MeetingAgendaItem.find_by(title: "First Item")
|
||||
second_item = MeetingAgendaItem.find_by(title: "Second Item")
|
||||
|
||||
show_page.select_action(second_item, "Edit")
|
||||
|
||||
# No confirmation when title isn't changed
|
||||
expect do
|
||||
accept_confirm do
|
||||
show_page.select_action(first_item, I18n.t(:label_sort_lowest))
|
||||
end
|
||||
end.to raise_error(Capybara::ModalNotFound)
|
||||
|
||||
show_page.assert_agenda_order! "Second Item", "First Item"
|
||||
|
||||
second_item.reload
|
||||
|
||||
show_page.edit_agenda_item(second_item, save: false) do
|
||||
fill_in "Title", with: "Modified Second Item"
|
||||
end
|
||||
|
||||
# Confirmation when title is changed
|
||||
dismiss_confirm do
|
||||
show_page.select_action(first_item, I18n.t(:label_sort_highest))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -192,6 +192,54 @@ RSpec.describe RecurringMeetings::UpdateService, "integration", type: :model do
|
||||
.to eq "[#{project.name}] Meeting series '#{series.title}' has been updated"
|
||||
end
|
||||
end
|
||||
|
||||
context "when updating only the location with recipients with different locales" do
|
||||
let(:german_author) do
|
||||
create(:user,
|
||||
language: "de",
|
||||
member_with_permissions: { project => %i(view_meetings edit_meetings) })
|
||||
end
|
||||
|
||||
let(:english_recipient) do
|
||||
create(:user,
|
||||
language: "en",
|
||||
member_with_permissions: { project => %i(view_meetings) })
|
||||
end
|
||||
|
||||
let(:german_recipient) do
|
||||
create(:user,
|
||||
language: "de",
|
||||
member_with_permissions: { project => %i(view_meetings) })
|
||||
end
|
||||
|
||||
let(:instance) { described_class.new(model: series, user: german_author) }
|
||||
|
||||
let(:params) { { location: "New location" } }
|
||||
|
||||
before do
|
||||
series.update!(author: german_author)
|
||||
series.template.participants.delete_all
|
||||
series.template.participants << MeetingParticipant.new(user: english_recipient, invited: true)
|
||||
series.template.participants << MeetingParticipant.new(user: german_recipient, invited: true)
|
||||
series.template.update!(location: "Old location")
|
||||
end
|
||||
|
||||
it "does not send a schedule update when not necessary (Bug #67287)" do
|
||||
expect(service_result).to be_success
|
||||
perform_enqueued_jobs
|
||||
|
||||
expect(ActionMailer::Base.deliveries.count).to eq(2)
|
||||
|
||||
english_mail = ActionMailer::Base.deliveries.find { |m| m.to.include?(english_recipient.mail) }
|
||||
german_mail = ActionMailer::Base.deliveries.find { |m| m.to.include?(german_recipient.mail) }
|
||||
|
||||
expect(english_mail.html_part.body).to include("Every day")
|
||||
expect(english_mail.html_part.body).not_to include("Jeden Tag")
|
||||
|
||||
expect(german_mail.html_part.body).to include("Jeden Tag")
|
||||
expect(german_mail.html_part.body).not_to include("Every day")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "rescheduling occurrences" do
|
||||
|
||||
@@ -44,7 +44,8 @@ module OpenIDConnect
|
||||
),
|
||||
disabled: provider.seeded_from_env?,
|
||||
required: false,
|
||||
input_width: :large
|
||||
input_width: :large,
|
||||
value: pretty_claims
|
||||
)
|
||||
|
||||
f.text_field(
|
||||
@@ -59,6 +60,15 @@ module OpenIDConnect
|
||||
input_width: :large
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def pretty_claims
|
||||
claims = model.claims || ""
|
||||
JSON.pretty_generate(JSON.parse(claims))
|
||||
rescue JSON::ParserError
|
||||
claims
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +32,8 @@ module OpenIDConnect
|
||||
class BaseContract < ModelContract
|
||||
include RequiresAdminGuard
|
||||
|
||||
VALID_CLAIMS_KEYS = %w[id_token userinfo].freeze
|
||||
|
||||
def self.model
|
||||
OpenIDConnect::Provider
|
||||
end
|
||||
@@ -81,11 +83,45 @@ module OpenIDConnect
|
||||
def claims_are_json
|
||||
return if claims.blank?
|
||||
|
||||
JSON.parse(claims)
|
||||
parsed = JSON.parse(claims)
|
||||
return errors.add(:claims, :not_json_object) unless parsed.is_a?(Hash)
|
||||
|
||||
validate_claims_json_structure(parsed)
|
||||
rescue JSON::ParserError
|
||||
errors.add(:claims, :not_json)
|
||||
end
|
||||
|
||||
def validate_claims_json_structure(parsed)
|
||||
invalid_keys = parsed.keys - VALID_CLAIMS_KEYS
|
||||
if invalid_keys.any?
|
||||
return errors.add(:claims,
|
||||
:invalid_claims_location,
|
||||
invalid: invalid_keys.join(", "),
|
||||
supported: VALID_CLAIMS_KEYS.join(", "))
|
||||
end
|
||||
|
||||
non_object_key, = parsed.find { |_, v| !v.is_a?(Hash) }
|
||||
return errors.add(:claims, :non_object_attribute, attribute: non_object_key) if non_object_key
|
||||
|
||||
parsed.each_key do |key|
|
||||
validate_nested_claims_structure(parsed, key)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_nested_claims_structure(parsed, base_key)
|
||||
claims = parsed.fetch(base_key)
|
||||
claims.each do |key, value|
|
||||
next if value.nil?
|
||||
return errors.add(:claims, :non_object_attribute, attribute: json_path(base_key, key)) unless value.is_a?(Hash)
|
||||
if key_violates_type?(value, "essential", TrueClass, FalseClass)
|
||||
return errors.add(:claims, :invalid_claims_essential, attribute: json_path(base_key, key, "essential"))
|
||||
end
|
||||
if key_violates_type?(value, "values", Array)
|
||||
return errors.add(:claims, :invalid_claims_values, attribute: json_path(base_key, key, "values"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def group_regexes_parseable
|
||||
invalid_lines = group_regexes.each_with_index.filter_map do |r, i|
|
||||
Regexp.new(r)
|
||||
@@ -96,6 +132,16 @@ module OpenIDConnect
|
||||
|
||||
errors.add(:group_regexes, :regex_list_invalid, invalid_lines: invalid_lines.to_sentence) if invalid_lines.any?
|
||||
end
|
||||
|
||||
def json_path(*elements)
|
||||
elements.join(".")
|
||||
end
|
||||
|
||||
def key_violates_type?(object, key, *types)
|
||||
return false unless object.key?(key)
|
||||
|
||||
types.all? { |t| !object.fetch(key).is_a?(t) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -44,6 +44,10 @@ en:
|
||||
response_is_not_successful: " responds with %{status}."
|
||||
response_is_not_json: " does not return JSON body."
|
||||
response_misses_required_attributes: " does not return required attributes. Missing attributes are: %{missing_attributes}."
|
||||
invalid_claims_essential: "does not define a boolean at %{attribute}."
|
||||
invalid_claims_location: "contain unsupported locations: %{invalid}. Supported locations are: %{supported}."
|
||||
invalid_claims_values: "does not define an array at %{attribute}."
|
||||
non_object_attribute: "does not define a JSON object at %{attribute}."
|
||||
|
||||
provider:
|
||||
delete_warning:
|
||||
|
||||
+68
@@ -53,6 +53,74 @@ RSpec.describe OpenIDConnect::Providers::UpdateContract do
|
||||
it_behaves_like "contract is valid"
|
||||
end
|
||||
end
|
||||
|
||||
describe "claims" do
|
||||
let(:provider) { build_stubbed(:oidc_provider, claims:) }
|
||||
let(:claims) { claims_object.to_json }
|
||||
let(:claims_object) { { id_token: { my_claim: { essential: true } } } }
|
||||
|
||||
it_behaves_like "contract is valid"
|
||||
|
||||
context "when claims are empty" do
|
||||
let(:claims) { "" }
|
||||
|
||||
it_behaves_like "contract is valid"
|
||||
end
|
||||
|
||||
context "when a voluntary claim without special needs is requested" do
|
||||
let(:claims_object) { { id_token: { my_claim: nil } } }
|
||||
|
||||
it_behaves_like "contract is valid"
|
||||
end
|
||||
|
||||
context "when an essential claim with specific values is requested" do
|
||||
let(:claims_object) { { id_token: { my_claim: { essential: true, values: %w[a b] } } } }
|
||||
|
||||
it_behaves_like "contract is valid"
|
||||
end
|
||||
|
||||
context "when claims are not JSON" do
|
||||
let(:claims) { "foobar" }
|
||||
|
||||
it_behaves_like "contract is invalid", claims: :not_json
|
||||
end
|
||||
|
||||
context "when claims are not a JSON object" do
|
||||
let(:claims_object) { "a JSON string" }
|
||||
|
||||
it_behaves_like "contract is invalid", claims: :not_json_object
|
||||
end
|
||||
|
||||
context "when claims contain unsupported root key" do
|
||||
let(:claims_object) { { something: { my_claim: nil } } }
|
||||
|
||||
it_behaves_like "contract is invalid", claims: :invalid_claims_location
|
||||
end
|
||||
|
||||
context "when claims don't contain object at root key" do
|
||||
let(:claims_object) { { id_token: "not allowed to be a string" } }
|
||||
|
||||
it_behaves_like "contract is invalid", claims: :non_object_attribute
|
||||
end
|
||||
|
||||
context "when definition of a claim is not a hash" do
|
||||
let(:claims_object) { { id_token: { my_claim: "essential" } } }
|
||||
|
||||
it_behaves_like "contract is invalid", claims: :non_object_attribute
|
||||
end
|
||||
|
||||
context "when essential attribute of a claim is not a boolean" do
|
||||
let(:claims_object) { { id_token: { my_claim: { essential: "false" } } } }
|
||||
|
||||
it_behaves_like "contract is invalid", claims: :invalid_claims_essential
|
||||
end
|
||||
|
||||
context "when values attribute of a claim is not an array" do
|
||||
let(:claims_object) { { id_token: { my_claim: { values: "foobar" } } } }
|
||||
|
||||
it_behaves_like "contract is invalid", claims: :invalid_claims_values
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when non-admin" do
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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: "否"
|
||||
|
||||
@@ -131,14 +131,17 @@ class OpenProject::Reporting::CostEntryXlsTable < OpenProject::XlsExport::XlsVie
|
||||
%i[user_id activity_id entity_gid comments project_id]
|
||||
end
|
||||
|
||||
# Returns the results of the query sorted by date the time was spent on and by id
|
||||
# Returns the results of the query sorted by date the time was spent on and name
|
||||
def sorted_results
|
||||
query
|
||||
.each_direct_result
|
||||
.map(&:itself)
|
||||
results = query.each_direct_result.map(&:itself)
|
||||
users_by_id = load_users_for_results(results)
|
||||
|
||||
results
|
||||
.group_by { |r| r.fields["spent_on"] }
|
||||
.sort
|
||||
.flat_map { |_, date_results| date_results.sort_by { |r| r.fields["id"] } }
|
||||
.flat_map do |_, date_results|
|
||||
date_results.sort_by { |r| user_name_for_sorting(r, users_by_id) }
|
||||
end
|
||||
end
|
||||
|
||||
def labour_query?
|
||||
@@ -148,4 +151,13 @@ class OpenProject::Reporting::CostEntryXlsTable < OpenProject::XlsExport::XlsVie
|
||||
def with_times_column?
|
||||
Setting.allow_tracking_start_and_end_times && labour_query?
|
||||
end
|
||||
|
||||
def load_users_for_results(results)
|
||||
user_ids = results.map { |r| r.fields["user_id"] }.uniq
|
||||
User.where(id: user_ids).index_by(&:id)
|
||||
end
|
||||
|
||||
def user_name_for_sorting(result, users_by_id)
|
||||
users_by_id[result.fields["user_id"]]&.name&.downcase || ""
|
||||
end
|
||||
end
|
||||
|
||||
+22
-18
@@ -30,25 +30,29 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<%=
|
||||
render(
|
||||
Primer::OpenProject::DangerDialog.new(
|
||||
id:,
|
||||
title:,
|
||||
test_selector: id,
|
||||
title: I18n.t("storages.page_titles.file_storages.delete"),
|
||||
form_arguments:,
|
||||
size: :large,
|
||||
form_arguments: {
|
||||
model: @project_storage,
|
||||
action: admin_settings_storage_project_storage_path(id: @project_storage, page: current_page),
|
||||
data: {
|
||||
turbo: true
|
||||
},
|
||||
method: :delete
|
||||
},
|
||||
confirm_button_text: I18n.t("button_remove")
|
||||
test_selector: TEST_SELECTOR,
|
||||
confirm_button_text: I18n.t("button_remove_permanently")
|
||||
)
|
||||
) do |dialog|
|
||||
dialog.with_confirmation_message do |message|
|
||||
message.with_heading(tag: :h2) { heading }
|
||||
message.with_description_content(text)
|
||||
end
|
||||
dialog.with_confirmation_check_box_content(confirmation_text)
|
||||
end
|
||||
%>
|
||||
<% dialog.with_confirmation_message do |message|
|
||||
message.with_heading(tag: :h2) { I18n.t("storages.page_titles.project_storages.delete") }
|
||||
end %>
|
||||
<% dialog.with_additional_details do %>
|
||||
<% render(Primer::OpenProject::FlexLayout.new) do |flex| %>
|
||||
<% flex.with_row do %>
|
||||
<%= t("storages.delete_warning.project_storage", file_storage: "<strong>#{h(@project_storage.storage.name)}</strong>").html_safe %>
|
||||
<% end %>
|
||||
<% flex.with_row do %>
|
||||
<ul>
|
||||
<li> <%= t("storages.delete_warning.project_storage_delete_result_1") %>
|
||||
<li> <%= t("storages.delete_warning.project_storage_delete_result_2") %>
|
||||
</ul>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% dialog.with_confirmation_check_box_content(I18n.t(:text_permanent_remove_confirmation_checkbox_label)) %>
|
||||
<% end %>
|
||||
|
||||
+34
-38
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
@@ -26,49 +26,45 @@
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
# ++
|
||||
|
||||
# This components renders a dialog to confirm the deletion of a project from a storage.
|
||||
module Storages::ProjectStorages
|
||||
class DestroyConfirmationDialogComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
include OpPrimer::ComponentHelpers
|
||||
# Destroy confirmation dialog used when removing a storage from a project from within the project
|
||||
# by going to "Some project" -> Project settings -> Files.
|
||||
module Storages
|
||||
module ProjectStorages
|
||||
class DestroyConfirmationDialogComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
|
||||
def initialize(storage:, project_storage:, params: {})
|
||||
super
|
||||
TEST_SELECTOR = "op-project-storages--delete-dialog"
|
||||
|
||||
@storage = storage
|
||||
@project_storage = project_storage
|
||||
@params = params
|
||||
end
|
||||
|
||||
def id
|
||||
"project-storage-#{@project_storage.id}-destroy-confirmation-dialog"
|
||||
end
|
||||
|
||||
def title
|
||||
I18n.t("project_storages.remove_project.dialog.title")
|
||||
end
|
||||
|
||||
def heading
|
||||
I18n.t("project_storages.remove_project.dialog.heading_text", storage: @storage.name)
|
||||
end
|
||||
|
||||
def text
|
||||
text = I18n.t("project_storages.remove_project.dialog.text")
|
||||
if @project_storage.project_folder_mode == "automatic"
|
||||
text << " "
|
||||
text << I18n.t("project_storages.remove_project.dialog.automatically_managed_appendix")
|
||||
# @param target [Symbol] The submission target of the dialog's form. One of :project or :storage
|
||||
# @param target_page [String, Integer] An optional page query parameter for the target that the form will submit to.
|
||||
def initialize(project_storage:, target:, target_page: nil)
|
||||
super
|
||||
@project_storage = project_storage
|
||||
@target = target
|
||||
@target_page = target_page
|
||||
end
|
||||
text
|
||||
end
|
||||
|
||||
def current_page
|
||||
@params[:page]
|
||||
end
|
||||
private
|
||||
|
||||
def confirmation_text
|
||||
I18n.t("project_storages.remove_project.dialog.confirmation_text")
|
||||
def form_arguments
|
||||
{
|
||||
action: target_path,
|
||||
method: :delete
|
||||
}
|
||||
end
|
||||
|
||||
def target_path
|
||||
case @target
|
||||
when :project
|
||||
project_settings_project_storage_path(project_id: @project_storage.project, id: @project_storage, page: @target_page)
|
||||
when :storage
|
||||
admin_settings_storage_project_storage_path(id: @project_storage, page: @target_page)
|
||||
else
|
||||
raise ArgumentError, "Unsupported target #{@target} for #{self.class}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) the OpenProject GmbH
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version 3.
|
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
Copyright (C) 2010-2013 the ChiliProject Team
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%=
|
||||
render(
|
||||
Primer::OpenProject::DangerDialog.new(
|
||||
title: I18n.t("storages.page_titles.file_storages.delete"),
|
||||
form_arguments:,
|
||||
size: :large,
|
||||
test_selector: TEST_SELECTOR
|
||||
)
|
||||
) do |dialog|
|
||||
%>
|
||||
<% dialog.with_confirmation_message do |message|
|
||||
message.with_heading(tag: :h2) { I18n.t("storages.page_titles.file_storages.delete") }
|
||||
end %>
|
||||
<% dialog.with_additional_details do %>
|
||||
<% render(Primer::OpenProject::FlexLayout.new) do |flex| %>
|
||||
<% flex.with_row do %>
|
||||
<%= t("storages.delete_warning.storage", file_storage: "<strong>#{h(@project_storage.storage.name)}</strong>").html_safe %>
|
||||
<% end %>
|
||||
<% flex.with_row do %>
|
||||
<ul>
|
||||
<li> <%= t("storages.delete_warning.project_storage_delete_result_1") %>
|
||||
<li> <%= t("storages.delete_warning.project_storage_delete_result_2") %>
|
||||
</ul>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% dialog.with_confirmation_check_box_content(I18n.t(:text_permanent_delete_confirmation_checkbox_label)) %>
|
||||
<% end %>
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
# 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.
|
||||
# ++
|
||||
|
||||
# Destroy confirmation dialog used when removing a storage from a project from within the project
|
||||
# by going to "Some project" -> Project settings -> Files.
|
||||
module Storages
|
||||
module ProjectStorages
|
||||
module Projects
|
||||
class DestroyConfirmationDialogComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
|
||||
TEST_SELECTOR = "op-project-storages--delete-dialog"
|
||||
|
||||
def initialize(storage:)
|
||||
super
|
||||
@project_storage = storage
|
||||
end
|
||||
|
||||
def form_arguments
|
||||
{
|
||||
action: project_settings_project_storage_path(project_id: @project_storage.project, id: @project_storage),
|
||||
method: :delete
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -128,7 +128,10 @@ class Storages::Admin::ProjectStoragesController < Projects::SettingsController
|
||||
end
|
||||
|
||||
def destroy_info
|
||||
respond_with_dialog Storages::ProjectStorages::Projects::DestroyConfirmationDialogComponent.new(storage: @object)
|
||||
respond_with_dialog Storages::ProjectStorages::DestroyConfirmationDialogComponent.new(
|
||||
project_storage: @object,
|
||||
target: :project
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
+2
-2
@@ -115,9 +115,9 @@ class Storages::Admin::Storages::ProjectStoragesController < ApplicationControll
|
||||
|
||||
def destroy_confirmation_dialog
|
||||
respond_with_dialog Storages::ProjectStorages::DestroyConfirmationDialogComponent.new(
|
||||
storage: @storage,
|
||||
project_storage: @project_storage,
|
||||
params:
|
||||
target: :storage,
|
||||
target_page: params[:page]
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -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,21 +86,21 @@ 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}
|
||||
dialog:
|
||||
automatically_managed_appendix: また、この場合、このストレージには自動的に管理されるプロジェクトフォルダがあり、このフォルダとそのファイルは永久に削除される。
|
||||
automatically_managed_appendix: また、この場合、このストレージには自動的に管理されたプロジェクトフォルダがあり、そのファイルは永久に削除されます。
|
||||
confirmation_text: このプロジェクトからこのファイルストレージを削除したいことを確認してください
|
||||
heading_text: '%{storage}からプロジェクトを削除する'
|
||||
text: このアクションは不可逆的で、このプロジェクトのワークパッケージから、そのストレージのファイルやフォルダへのリンクをすべて削除します。
|
||||
heading_text: '%{storage} からプロジェクトを削除'
|
||||
text: この操作は元に戻すことができず、このプロジェクトの作業パッケージからそのストレージのファイルやフォルダへのすべてのリンクを削除します。
|
||||
title: ストレージからプロジェクトを削除
|
||||
label: プロジェクトを削除
|
||||
services:
|
||||
@@ -116,7 +116,7 @@ ja:
|
||||
one_drive_sync_service:
|
||||
create_folder: 'プロジェクトフォルダの作成を管理:'
|
||||
ensure_root_folder_permissions: 'ベースフォルダの権限を設定:'
|
||||
hide_inactive_folders: '非アクティブフォルダを隠す ステップ:'
|
||||
hide_inactive_folders: '非アクティブフォルダを隠す ステップ'
|
||||
remote_folders: 'グループフォルダの内容を読む:'
|
||||
rename_project_folder: '管理プロジェクトフォルダの名前を変更します:'
|
||||
sharepoint_sync_service:
|
||||
@@ -127,15 +127,15 @@ ja:
|
||||
rename_project_folder: '管理プロジェクトフォルダの名前を変更します:'
|
||||
errors:
|
||||
messages:
|
||||
error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください
|
||||
error: 予期しないエラーが発生しました。OpenProject のログを確認するか、管理者に連絡してください。
|
||||
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:
|
||||
@@ -156,31 +156,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.
|
||||
@@ -301,41 +301,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_group_folder_not_found: グループフォルダが見つかりません。
|
||||
nc_host_not_found: 設定されたホストURLにNextcloudサーバーが見つかりません。設定を確認してください。
|
||||
nc_oauth_request_not_found: 現在接続しているユーザーを取得するエンドポイントが見つかりませんでした。詳細については、サーバーのログを確認してください。
|
||||
nc_oauth_request_unauthorized: 現在のユーザーにはリモートファイルストレージにアクセスする権限がありません。サーバーのログを確認してください。
|
||||
nc_oauth_token_missing: OpenProjectでは、ユーザーがNextcloudアカウントをリンクしていないため、ユーザーレベルのNextcloudとの通信をテストできません。
|
||||
nc_unexpected_content: 管理グループフォルダに予期しないコンテンツが見つかりました。
|
||||
nc_oauth_token_missing: OpenProject は、Nextcloudアカウントへのリンクがまだないため、Nextcloudとのユーザーレベルの通信をテストできません。
|
||||
nc_unexpected_content: 管理対象グループフォルダに予期しないコンテンツが見つかりました。
|
||||
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設定に問題があるようです。設定を確認し、再度お試しください。
|
||||
@@ -350,7 +350,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: 健康的
|
||||
@@ -358,55 +358,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: 詳しくは、<a target='_blank' href='%{provider_link}'> %{provider_name} ファイルストレージ</a>統合の設定に関するドキュメントをお読みください。
|
||||
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}、目的のアプリケーションからこれらの値をコピーします。'
|
||||
@@ -478,13 +478,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: ファイルストレージセットアップの状態の状態
|
||||
@@ -503,8 +503,8 @@ ja:
|
||||
subtitle_short: OpenProjectにプロジェクトごとにフォルダを自動的に作成させます。
|
||||
title: 自動的に管理されるプロジェクトフォルダ
|
||||
project_settings:
|
||||
edit: このプロジェクトのファイル・ストレージを編集する
|
||||
members_connection_status: メンバーの接続状況
|
||||
edit: このプロジェクトのファイルストレージを編集
|
||||
members_connection_status: 会員の接続状況
|
||||
new: このプロジェクトにファイルストレージを追加する
|
||||
project_storage_members:
|
||||
subtitle: 全プロジェクトメンバーのストレージ %{storage_name_link} の接続状態を確認する。
|
||||
@@ -513,14 +513,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
|
||||
@@ -530,18 +530,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:
|
||||
|
||||
@@ -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: "このファイルを閲覧する権限がありません。"
|
||||
|
||||
@@ -97,12 +97,6 @@ en:
|
||||
manual: Existing folder manually managed
|
||||
remove_project:
|
||||
deletion_failure_flash: Failed to remove the project from the storage. %{error}
|
||||
dialog:
|
||||
automatically_managed_appendix: Also, in this case this storage has an automatically managed project folder, this and its files will be deleted forever.
|
||||
confirmation_text: Please, confirm you understand and want to remove this file storage from this project
|
||||
heading_text: Remove project from %{storage}
|
||||
text: This action is irreversible and will remove all links from work packages of this project to files and folders of that storage.
|
||||
title: Remove project from storage
|
||||
label: Remove project
|
||||
services:
|
||||
attributes:
|
||||
@@ -227,12 +221,13 @@ en:
|
||||
confirm_replace_oauth_application: This action will reset the current OAuth credentials. After confirming you will have to reenter the credentials at the storage provider and all remote users will have to authorize against OpenProject again. Are you sure you want to proceed?
|
||||
confirm_replace_oauth_client: This action will reset the current OAuth credentials. After confirming you will have to enter new credentials from the storage provider and all users will have to authorize against %{provider_type} again. Are you sure you want to proceed?
|
||||
delete_warning:
|
||||
project_storage_delete_result_1: Remove all links from work packages of this project to files and folders of that storage.
|
||||
project_storage_delete_result_2: In case this storage has an automatically managed project folder, this and its files will be deleted forever.
|
||||
storage: 'Are you sure you want to delete %{file_storage}? This will:'
|
||||
storage_delete_result_1: Remove all storage setups for all projects using this storage.
|
||||
storage_delete_result_2: Remove all links from work packages of all projects to files and folders of that storage.
|
||||
storage_delete_result_3: In case this storage has automatically managed project folders, those and their contained files will be deleted forever.
|
||||
project_storage: Are you sure you want to remove %{file_storage} from this project?
|
||||
project_storage_delete_result_1: All links to corresponding files and folders will be removed
|
||||
project_storage_delete_result_2: The automatically-managed project folder and all files in it will be deleted
|
||||
storage: Are you sure you want to delete %{file_storage} as an external file storage?
|
||||
storage_delete_result_1: The storage will be removed from all projects currently using it
|
||||
storage_delete_result_2: All links to corresponding files and folders will be removed
|
||||
storage_delete_result_3: The automatically-managed project folder and all files in it will be deleted
|
||||
dependencies:
|
||||
nextcloud:
|
||||
group_folders_app: Group Folders
|
||||
@@ -497,7 +492,7 @@ en:
|
||||
title: We are setting up your permissions on the project folder.
|
||||
page_titles:
|
||||
file_storages:
|
||||
delete: Delete file storage
|
||||
delete: Delete file storage?
|
||||
subtitle: Add an external file storage in order to upload, link and manage files in work packages.
|
||||
managed_project_folders:
|
||||
subtitle: |-
|
||||
@@ -512,6 +507,8 @@ en:
|
||||
project_storage_members:
|
||||
subtitle: Check the connection status for the storage %{storage_name_link} of all project members.
|
||||
title: Members connection status
|
||||
project_storages:
|
||||
delete: Remove file storage from project?
|
||||
permission_header_explanation: File permissions on external storages are applied only to folders and files within automatically managed project folders. Note that not all file permissions are supported by all storage providers. Please check the documentation on <a target='_blank' href='https://www.openproject.org/docs/system-admin-guide/users-permissions/roles-permissions/#file-storages-permissions'>file storage permissions</a> for more information.
|
||||
provider_types:
|
||||
label: Provider type
|
||||
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
# 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_module_spec_helper
|
||||
|
||||
RSpec.describe Storages::ProjectStorages::DestroyConfirmationDialogComponent,
|
||||
type: :component do
|
||||
describe "#heading" do
|
||||
it "contains the storage name" do
|
||||
storage = build_stubbed(:one_drive_storage)
|
||||
project_storage = build_stubbed(:project_storage, storage:)
|
||||
|
||||
component = described_class.new(storage:, project_storage:)
|
||||
expect(component.heading).to include(storage.name)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#text" do
|
||||
let(:storage) { build_stubbed(:storage, :as_generic) }
|
||||
|
||||
context "for a project with automatically managed project folder" do
|
||||
it "includes an additional hint for data loss" do
|
||||
project_storage = build_stubbed(:project_storage, storage:, project_folder_mode: "automatic")
|
||||
component = described_class.new(storage:, project_storage:)
|
||||
expect(component.text).to include("irreversible")
|
||||
expect(component.text).to include("files will be deleted forever")
|
||||
end
|
||||
end
|
||||
|
||||
context "for a project with manual managed folder" do
|
||||
it "includes only the basic hint" do
|
||||
project_storage = build_stubbed(:project_storage, storage:, project_folder_mode: "manual")
|
||||
component = described_class.new(storage:, project_storage:)
|
||||
expect(component.text).to include("irreversible")
|
||||
expect(component.text).not_to include("files will be deleted forever")
|
||||
end
|
||||
end
|
||||
|
||||
context "for a project with unmanaged folder" do
|
||||
it "includes only the basic hint" do
|
||||
project_storage = build_stubbed(:project_storage, storage:, project_folder_mode: "inactive")
|
||||
component = described_class.new(storage:, project_storage:)
|
||||
expect(component.text).to include("irreversible")
|
||||
expect(component.text).not_to include("files will be deleted forever")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -75,11 +75,11 @@ RSpec.describe "Delete ProjectStorage with FileLinks", :js, :webmock do
|
||||
# Press Delete icon to remove the storage from the project
|
||||
page.find(".icon.icon-delete").click
|
||||
|
||||
# Danger zone confirmation flow
|
||||
# Danger dialog confirmation flow
|
||||
within_test_selector("op-project-storages--delete-dialog") do
|
||||
expect(page).to have_text("Delete file storage")
|
||||
expect(page).to have_unchecked_field("I understand that this deletion cannot be reversed")
|
||||
expect(page).to have_button("Delete permanently", disabled: true)
|
||||
expect(page).to have_unchecked_field("I understand that this removal cannot be reversed")
|
||||
expect(page).to have_button("Remove permanently", disabled: true)
|
||||
|
||||
# Cancel Confirmation
|
||||
page.click_button("Cancel")
|
||||
@@ -93,8 +93,8 @@ RSpec.describe "Delete ProjectStorage with FileLinks", :js, :webmock do
|
||||
|
||||
within_test_selector("op-project-storages--delete-dialog") do
|
||||
# Approve Confirmation
|
||||
page.check "I understand that this deletion cannot be reversed"
|
||||
page.click_button("Delete permanently")
|
||||
page.check "I understand that this removal cannot be reversed"
|
||||
page.click_button("Remove permanently")
|
||||
end
|
||||
|
||||
# List of ProjectStorages empty again
|
||||
|
||||
@@ -178,8 +178,8 @@ RSpec.describe("Activation of storages in projects",
|
||||
|
||||
within_test_selector("op-project-storages--delete-dialog") do
|
||||
expect(page).to have_text("Delete file storage")
|
||||
expect(page).to have_unchecked_field("I understand that this deletion cannot be reversed")
|
||||
expect(page).to have_button("Delete permanently", disabled: true)
|
||||
expect(page).to have_unchecked_field("I understand that this removal cannot be reversed")
|
||||
expect(page).to have_button("Remove permanently", disabled: true)
|
||||
|
||||
# Cancel Confirmation
|
||||
page.click_button("Cancel")
|
||||
@@ -192,8 +192,8 @@ RSpec.describe("Activation of storages in projects",
|
||||
|
||||
within_test_selector("op-project-storages--delete-dialog") do
|
||||
# Approve Confirmation
|
||||
page.check "I understand that this deletion cannot be reversed"
|
||||
page.click_button("Delete permanently")
|
||||
page.check "I understand that this removal cannot be reversed"
|
||||
page.click_button("Remove permanently")
|
||||
end
|
||||
|
||||
# List of ProjectStorages empty again
|
||||
|
||||
@@ -367,8 +367,7 @@ RSpec.describe "Admin lists project mappings for a storage",
|
||||
project_storages_index_page.click_menu_item_of("Remove project", project)
|
||||
|
||||
page.within("dialog") do
|
||||
expect(page).to have_text("Remove project from #{storage.name}")
|
||||
expect(page).to have_text("this storage has an automatically managed project folder")
|
||||
expect(page).to have_text("Are you sure you want to remove #{storage.name} from this project?")
|
||||
click_on "Cancel"
|
||||
end
|
||||
|
||||
@@ -398,9 +397,9 @@ RSpec.describe "Admin lists project mappings for a storage",
|
||||
page.within("dialog") do
|
||||
expect(page).to have_button("Remove", disabled: true)
|
||||
Retryable.repeat_until_success do
|
||||
check "Please, confirm you understand and want to remove this file storage from this project", allow_label_click: true
|
||||
check "I understand that this removal cannot be reversed", allow_label_click: true
|
||||
expect(page).to have_button("Remove", disabled: false) # ensure button is clickable
|
||||
click_on "Remove"
|
||||
click_on "Remove permanently"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -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: 'Cannot move the work package due to permissions or editing restrictions.'
|
||||
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...'
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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 <strong>%{device_name}</strong>. Якщо це USB-пристрій, переконайтеся, що його підключено, і торкніться його. Потім натисніть кнопку входу.
|
||||
webauthn:
|
||||
title: "WebAuthn"
|
||||
|
||||
@@ -13,4 +13,4 @@ zh-CN:
|
||||
xls_with_relations: "带关系的 XLS"
|
||||
xls_export:
|
||||
child_of: 此项的子项
|
||||
parent_of: 此项的父级
|
||||
parent_of: 此项的父项
|
||||
|
||||
@@ -82,7 +82,28 @@ RSpec.describe Users::UpdateContract do
|
||||
end
|
||||
end
|
||||
|
||||
context "when global user" do
|
||||
context "when user is an admin" do
|
||||
let(:current_user) { create(:admin) }
|
||||
|
||||
describe "can update the email" do
|
||||
before do
|
||||
user.mail = "a.new@email.address"
|
||||
end
|
||||
|
||||
it_behaves_like "contract is valid"
|
||||
end
|
||||
|
||||
describe "can update the password" do
|
||||
before do
|
||||
user.password = "newpassword"
|
||||
user.password_confirmation = "newpassword"
|
||||
end
|
||||
|
||||
it_behaves_like "contract is valid"
|
||||
end
|
||||
end
|
||||
|
||||
context "when user with global manage_user permission" do
|
||||
let(:current_user) { create(:user, global_permissions: :manage_user) }
|
||||
|
||||
describe "can lock the user" do
|
||||
@@ -98,6 +119,14 @@ RSpec.describe Users::UpdateContract do
|
||||
|
||||
it_behaves_like "contract is invalid"
|
||||
end
|
||||
|
||||
describe "cannot update the email" do
|
||||
before do
|
||||
user.mail = "a.new@email.address"
|
||||
end
|
||||
|
||||
it_behaves_like "contract is invalid", mail: :error_readonly
|
||||
end
|
||||
end
|
||||
|
||||
context "when updated user is current user" do
|
||||
@@ -113,6 +142,14 @@ RSpec.describe Users::UpdateContract do
|
||||
|
||||
it_behaves_like "contract is invalid", status: :error_readonly
|
||||
end
|
||||
|
||||
describe "can update the email" do
|
||||
before do
|
||||
user.mail = "a.new@email.address"
|
||||
end
|
||||
|
||||
it_behaves_like "contract is valid"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -771,7 +771,7 @@ RSpec.describe UsersController do
|
||||
value: "another_email@example.com",
|
||||
edited_user: :some_admin,
|
||||
current_user: :admin
|
||||
include_examples "it can update field",
|
||||
include_examples "it cannot update field",
|
||||
field: :mail,
|
||||
value: "another_email@example.com",
|
||||
edited_user: :some_user,
|
||||
|
||||
@@ -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.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Redmine::Acts::Customizable, "validation skipping" do
|
||||
let(:klass) do
|
||||
Class.new(ApplicationRecord) do
|
||||
include Redmine::Acts::Customizable
|
||||
|
||||
# Just setting it to some existing table to avoid problems
|
||||
self.table_name = "users"
|
||||
def self.name = "CustomizableModelForSpec"
|
||||
end
|
||||
end
|
||||
|
||||
describe ".acts_as_customizable" do
|
||||
# rubocop:disable RSpec/MessageSpies
|
||||
context "with :validate_on option" do
|
||||
it "calls the validation with on: option" do
|
||||
expect(klass).to receive(:validate).with(:validate_custom_values, on: :update)
|
||||
klass.acts_as_customizable validate_on: :update
|
||||
end
|
||||
end
|
||||
|
||||
context "with :validate_except_on option" do
|
||||
it "calls the validation with except: option" do
|
||||
expect(klass).to receive(:validate).with(:validate_custom_values, except_on: :create)
|
||||
klass.acts_as_customizable validate_except_on: :create
|
||||
end
|
||||
end
|
||||
|
||||
context "with :validate_if option" do
|
||||
it "calls the validation with if: option" do
|
||||
condition = ->(model) { model.some_condition? }
|
||||
expect(klass).to receive(:validate).with(:validate_custom_values, if: condition)
|
||||
klass.acts_as_customizable validate_if: condition
|
||||
end
|
||||
end
|
||||
|
||||
context "with :validate_unless option" do
|
||||
it "calls the validation with unless: option" do
|
||||
condition = ->(model) { model.some_condition? }
|
||||
expect(klass).to receive(:validate).with(:validate_custom_values, unless: condition)
|
||||
klass.acts_as_customizable validate_unless: condition
|
||||
end
|
||||
end
|
||||
# rubocop:enable RSpec/MessageSpies
|
||||
end
|
||||
end
|
||||
@@ -23,7 +23,7 @@
|
||||
#
|
||||
# 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.
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
@@ -64,10 +64,11 @@ RSpec.describe "API V3 Authentication" do
|
||||
|
||||
context "with an invalid access token" do
|
||||
let(:oauth_access_token) { "1337" }
|
||||
let(:expected_www_auth_header) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
|
||||
|
||||
it "returns unauthorized" do
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
@@ -75,10 +76,11 @@ RSpec.describe "API V3 Authentication" do
|
||||
context "with a revoked access token" do
|
||||
let(:token) { create(:oauth_access_token, resource_owner: user, revoked_at: DateTime.now) }
|
||||
let(:oauth_access_token) { token.plaintext_token }
|
||||
let(:expected_www_auth_header) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
|
||||
|
||||
it "returns unauthorized" do
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
@@ -86,10 +88,11 @@ RSpec.describe "API V3 Authentication" do
|
||||
context "when the token's application is disabled" do
|
||||
let(:token) { create(:oauth_access_token, resource_owner: user, application: create(:oauth_application, enabled: false)) }
|
||||
let(:oauth_access_token) { token.plaintext_token }
|
||||
let(:expected_www_auth_header) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
|
||||
|
||||
it "returns unauthorized" do
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
@@ -97,6 +100,7 @@ RSpec.describe "API V3 Authentication" do
|
||||
context "with an expired access token" do
|
||||
let(:token) { create(:oauth_access_token, resource_owner: user) }
|
||||
let(:oauth_access_token) { token.plaintext_token }
|
||||
let(:expected_www_auth_header) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
|
||||
|
||||
around do |ex|
|
||||
Timecop.freeze(Time.current + (token.expires_in + 5).seconds) do
|
||||
@@ -106,7 +110,7 @@ RSpec.describe "API V3 Authentication" do
|
||||
|
||||
it "returns unauthorized" do
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
@@ -114,10 +118,11 @@ RSpec.describe "API V3 Authentication" do
|
||||
context "with wrong scope" do
|
||||
let(:token) { create(:oauth_access_token, resource_owner: user, scopes: "unknown_scope") }
|
||||
let(:oauth_access_token) { token.plaintext_token }
|
||||
let(:expected_www_auth_header) { 'Bearer realm="OpenProject API", scope="api_v3", error="insufficient_scope"' }
|
||||
|
||||
it "returns forbidden" do
|
||||
expect(last_response).to have_http_status :forbidden
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="insufficient_scope"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
@@ -126,6 +131,7 @@ RSpec.describe "API V3 Authentication" do
|
||||
let(:token) { create(:oauth_access_token, resource_owner: user, application:) }
|
||||
let(:application) { create(:oauth_application) }
|
||||
let(:oauth_access_token) { token.plaintext_token }
|
||||
let(:expected_www_auth_header) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
|
||||
|
||||
around do |ex|
|
||||
user.destroy
|
||||
@@ -134,7 +140,7 @@ RSpec.describe "API V3 Authentication" do
|
||||
|
||||
it "returns unauthorized" do
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
|
||||
@@ -143,7 +149,7 @@ RSpec.describe "API V3 Authentication" do
|
||||
|
||||
it "returns unauthorized" do
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
@@ -153,10 +159,11 @@ RSpec.describe "API V3 Authentication" do
|
||||
let(:token) { create(:oauth_access_token, resource_owner: user) }
|
||||
let(:oauth_access_token) { token.plaintext_token }
|
||||
let(:user) { create(:user, :locked) }
|
||||
let(:expected_www_auth_header) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
|
||||
|
||||
it "returns unauthorized" do
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
@@ -189,10 +196,11 @@ RSpec.describe "API V3 Authentication" do
|
||||
context "and the client credentials user is locked" do
|
||||
let(:user) { create(:user, :locked) }
|
||||
let(:expected_message) { "You did not provide the correct credentials." }
|
||||
let(:expected_www_auth_header) { 'Bearer realm="OpenProject API", scope="api_v3", error="invalid_token"' }
|
||||
|
||||
it "returns unauthorized" do
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq('Bearer realm="OpenProject API", error="invalid_token"')
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
@@ -491,6 +499,11 @@ RSpec.describe "API V3 Authentication" do
|
||||
let(:token_issuer) { "https://keycloak.local/realms/master" }
|
||||
let(:token_scope) { "email profile api_v3" }
|
||||
let(:expected_message) { "You did not provide the correct credentials." }
|
||||
let(:expected_www_auth_header) do
|
||||
"Bearer realm=\"OpenProject API\", scope=\"api_v3\", error=\"#{expected_error}\", " \
|
||||
"error_description=\"#{expected_error_description}\""
|
||||
end
|
||||
let(:expected_error) { "invalid_token" }
|
||||
let(:keys_request_stub) do
|
||||
stub_request(:get, "https://keycloak.local/realms/master/protocol/openid-connect/certs")
|
||||
.to_return(status: 200, body: JWT::JWK::Set.new(jwk_response).export.to_json, headers: {})
|
||||
@@ -514,66 +527,64 @@ RSpec.describe "API V3 Authentication" do
|
||||
|
||||
context "when token is issued by provider not configured in OP" do
|
||||
let(:token_issuer) { "https://eve.example.com" }
|
||||
let(:expected_error_description) { "The access token issuer is unknown" }
|
||||
|
||||
it "fails with HTTP 401 Unauthorized" do
|
||||
get resource
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"])
|
||||
.to eq(%{Bearer realm="OpenProject API", error="invalid_token", error_description="The access token issuer is unknown"})
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
|
||||
context "when token signature algorithm is not supported" do
|
||||
let(:token) { JWT.encode(payload, "secret", "HS256", { kid: "97AmyvoS8BFFRfm585GPgA16G1H2V22EdxxuAYUuoKk" }) }
|
||||
let(:expected_error_description) { "Token signature algorithm HS256 is not supported" }
|
||||
|
||||
it "fails with HTTP 401 Unauthorized" do
|
||||
get resource
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
error = "Token signature algorithm HS256 is not supported"
|
||||
expect(last_response.header["WWW-Authenticate"])
|
||||
.to eq(%{Bearer realm="OpenProject API", error="invalid_token", error_description="#{error}"})
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
|
||||
context "when aud does not contain client_id" do
|
||||
let(:token_aud) { ["Lisa", "Bart"] }
|
||||
let(:expected_error_description) { 'Invalid audience. Expected https://openproject.local, received [\"Lisa\", \"Bart\"]' }
|
||||
|
||||
it "fails with HTTP 401 Unauthorized" do
|
||||
get resource
|
||||
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
error = 'Invalid audience. Expected https://openproject.local, received ["Lisa", "Bart"]'
|
||||
expect(last_response.header["WWW-Authenticate"])
|
||||
.to eq(%{Bearer realm="OpenProject API", error="invalid_token", error_description="#{error}"})
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the scope does not permit access to APIv3" do
|
||||
let(:token_scope) { "profile email" }
|
||||
let(:expected_error) { "insufficient_scope" }
|
||||
let(:expected_error_description) { "Requires scope api_v3 to access this resource." }
|
||||
|
||||
it "fails with HTTP 403 Forbidden" do
|
||||
get resource
|
||||
|
||||
expect(last_response).to have_http_status :forbidden
|
||||
error = "Requires scope api_v3 to access this resource."
|
||||
expect(last_response.header["WWW-Authenticate"])
|
||||
.to eq(%{Bearer realm="OpenProject API", error="insufficient_scope", error_description="#{error}"})
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
end
|
||||
|
||||
context "when access token has expired already" do
|
||||
let(:token_exp) { 5.minutes.ago }
|
||||
let(:expected_error_description) { "Signature has expired" }
|
||||
|
||||
it "fails with HTTP 401 Unauthorized" do
|
||||
get resource
|
||||
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"])
|
||||
.to eq(%{Bearer realm="OpenProject API", error="invalid_token", error_description="Signature has expired"})
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
end
|
||||
|
||||
@@ -590,40 +601,37 @@ RSpec.describe "API V3 Authentication" do
|
||||
|
||||
context "when kid is absent in keycloak keys response" do
|
||||
let(:jwk_response) { JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), kid: "your-kid", use: "sig", alg: "RS256") }
|
||||
let(:expected_error_description) { "The signature key ID is unknown" }
|
||||
|
||||
it "fails with HTTP 401 Unauthorized" do
|
||||
get resource
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
error = "The signature key ID is unknown"
|
||||
expect(last_response.header["WWW-Authenticate"])
|
||||
.to eq(%{Bearer realm="OpenProject API", error="invalid_token", error_description="#{error}"})
|
||||
end
|
||||
end
|
||||
|
||||
context "when user identified by token is not known" do
|
||||
let(:user) { create(:user, authentication_provider: create(:oidc_provider)) }
|
||||
let(:expected_error_description) { "The user identified by the token is not known" }
|
||||
|
||||
it "fails with HTTP 401 Unauthorized" do
|
||||
get resource
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
error = "The user identified by the token is not known"
|
||||
expect(last_response.header["WWW-Authenticate"])
|
||||
.to eq(%{Bearer realm="OpenProject API", error="invalid_token", error_description="#{error}"})
|
||||
end
|
||||
end
|
||||
|
||||
context "when user identified by token is locked" do
|
||||
let(:user) { create(:user, :locked, authentication_provider: create(:oidc_provider), external_id: token_sub) }
|
||||
let(:expected_error_description) { "The user account is locked" }
|
||||
|
||||
it "fails with HTTP 401 Unauthorized" do
|
||||
get resource
|
||||
expect(last_response).to have_http_status :unauthorized
|
||||
expect(last_response.header["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(JSON.parse(last_response.body)).to eq(error_response_body)
|
||||
error = "The user account is locked"
|
||||
expect(last_response.header["WWW-Authenticate"])
|
||||
.to eq(%{Bearer realm="OpenProject API", error="invalid_token", error_description="#{error}"})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -73,8 +73,8 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
expect(response.body).to be_json_eql("Form".to_json).at_path("_type")
|
||||
|
||||
expect(body)
|
||||
.to be_json_eql(user.mail.to_json)
|
||||
.at_path("_embedded/payload/email")
|
||||
.to be_json_eql(user.login.to_json)
|
||||
.at_path("_embedded/payload/login")
|
||||
|
||||
expect(body)
|
||||
.to be_json_eql(user.firstname.to_json)
|
||||
@@ -127,8 +127,8 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
expect(response.body).to be_json_eql("Form".to_json).at_path("_type")
|
||||
|
||||
expect(body)
|
||||
.to be_json_eql(user.mail.to_json)
|
||||
.at_path("_embedded/payload/email")
|
||||
.to be_json_eql(user.login.to_json)
|
||||
.at_path("_embedded/payload/login")
|
||||
|
||||
expect(body)
|
||||
.not_to have_json_path("_embedded/payload/firstName")
|
||||
@@ -174,7 +174,7 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
context "when no custom field value is provided" do
|
||||
let(:payload) do
|
||||
{
|
||||
email: "updated@example.org"
|
||||
login: "new.login"
|
||||
}
|
||||
end
|
||||
|
||||
@@ -182,8 +182,8 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(body).to have_json_size(0).at_path("_embedded/validationErrors")
|
||||
expect(body)
|
||||
.to be_json_eql("updated@example.org".to_json)
|
||||
.at_path("_embedded/payload/email")
|
||||
.to be_json_eql("new.login".to_json)
|
||||
.at_path("_embedded/payload/login")
|
||||
expect(body)
|
||||
.to be_json_eql(api_v3_paths.user(user.id).to_json)
|
||||
.at_path("_links/commit/href")
|
||||
@@ -193,7 +193,7 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
context "when the custom field is provided but empty" do
|
||||
let(:payload) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
required_custom_field.attribute_name(:camel_case) => {
|
||||
raw: ""
|
||||
}
|
||||
@@ -213,7 +213,7 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
context "when the custom field value is provided and valid" do
|
||||
let(:payload) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
required_custom_field.attribute_name(:camel_case) => {
|
||||
raw: "Engineering"
|
||||
}
|
||||
@@ -240,7 +240,7 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
|
||||
let(:payload) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
visible_custom_field.attribute_name(:camel_case) => {
|
||||
raw: "CF text"
|
||||
}
|
||||
@@ -269,7 +269,7 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
let(:current_user) { create(:admin) }
|
||||
let(:payload) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
admin_only_custom_field.attribute_name(:camel_case) => {
|
||||
raw: "CF text"
|
||||
}
|
||||
@@ -291,7 +291,7 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
context "with non-admin permissions" do
|
||||
let(:payload) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
admin_only_custom_field.attribute_name(:camel_case) => {
|
||||
raw: "CF text"
|
||||
}
|
||||
@@ -312,7 +312,7 @@ RSpec.describe API::V3::Users::UpdateFormAPI, content_type: :json do
|
||||
let(:is_required) { true }
|
||||
let(:payload) do
|
||||
{
|
||||
email: "updated@example.org"
|
||||
login: "new.login"
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -69,14 +69,14 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
end
|
||||
|
||||
describe "attribute change" do
|
||||
let(:parameters) { { email: "foo@example.org", language: "de" } }
|
||||
let(:parameters) { { login: "new.login", language: "de" } }
|
||||
|
||||
it_behaves_like "successful update", mail: "foo@example.org", language: "de"
|
||||
it_behaves_like "successful update", login: "new.login", language: "de"
|
||||
end
|
||||
|
||||
describe "attribute collision" do
|
||||
let(:parameters) { { email: "foo@example.org" } }
|
||||
let(:collision) { create(:user, mail: "foo@example.org") }
|
||||
let(:parameters) { { login: "new.login" } }
|
||||
let(:collision) { create(:user, login: "new.login") }
|
||||
|
||||
before do
|
||||
collision
|
||||
@@ -88,7 +88,7 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
expect(last_response).to have_http_status(:unprocessable_entity)
|
||||
|
||||
expect(last_response.body)
|
||||
.to be_json_eql("email".to_json)
|
||||
.to be_json_eql("login".to_json)
|
||||
.at_path("_embedded/details/attribute")
|
||||
|
||||
expect(last_response.body)
|
||||
@@ -127,11 +127,11 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
context "when no custom field value is provided" do
|
||||
let(:parameters) do
|
||||
{
|
||||
email: "updated@example.org"
|
||||
login: "new.login"
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like "successful update", mail: "updated@example.org"
|
||||
it_behaves_like "successful update", login: "new.login"
|
||||
|
||||
it "keeps the custom field value empty" do
|
||||
send_request
|
||||
@@ -143,7 +143,7 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
context "when the custom field is provided but empty" do
|
||||
let(:parameters) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
required_custom_field.attribute_name(:camel_case) => {
|
||||
raw: ""
|
||||
}
|
||||
@@ -165,14 +165,14 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
context "when the custom field value is provided and valid" do
|
||||
let(:parameters) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
required_custom_field.attribute_name(:camel_case) => {
|
||||
raw: "Engineering"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like "successful update", mail: "updated@example.org"
|
||||
it_behaves_like "successful update", login: "new.login"
|
||||
|
||||
it "updates the user with the custom field value" do
|
||||
send_request
|
||||
@@ -189,14 +189,14 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
|
||||
let(:parameters) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
custom_field.attribute_name(:camel_case) => {
|
||||
raw: "CF text"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like "successful update", mail: "updated@example.org"
|
||||
it_behaves_like "successful update", login: "new.login"
|
||||
|
||||
it "sets the cf value" do
|
||||
send_request
|
||||
@@ -215,14 +215,14 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
let(:current_user) { create(:admin) }
|
||||
let(:parameters) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
admin_only_custom_field.attribute_name(:camel_case) => {
|
||||
raw: "CF text"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like "successful update", mail: "updated@example.org"
|
||||
it_behaves_like "successful update", login: "new.login"
|
||||
|
||||
it "sets the cf value" do
|
||||
send_request
|
||||
@@ -235,14 +235,14 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
let(:current_user) { create(:user, global_permissions: %i[manage_user view_all_principals]) }
|
||||
let(:parameters) do
|
||||
{
|
||||
email: "updated@example.org",
|
||||
login: "new.login",
|
||||
admin_only_custom_field.attribute_name(:camel_case) => {
|
||||
raw: "CF text"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like "successful update", mail: "updated@example.org"
|
||||
it_behaves_like "successful update", login: "new.login"
|
||||
|
||||
it "does not set the cf value" do
|
||||
send_request
|
||||
@@ -254,11 +254,11 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
let(:is_required) { true }
|
||||
let(:parameters) do
|
||||
{
|
||||
email: "updated@example.org"
|
||||
login: "new.login"
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like "successful update", mail: "updated@example.org"
|
||||
it_behaves_like "successful update", login: "new.login"
|
||||
|
||||
it "does not set the cf value" do
|
||||
send_request
|
||||
@@ -290,8 +290,21 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
end
|
||||
end
|
||||
|
||||
describe "email update" do
|
||||
let(:email) { "this.is.a.new@email.address" }
|
||||
let(:parameters) { { email: email } }
|
||||
|
||||
it "updates the users email correctly" do
|
||||
send_request
|
||||
expect(last_response).to have_http_status(:ok)
|
||||
|
||||
updated_user = User.find(user.id)
|
||||
expect(updated_user.mail).to eq(email)
|
||||
end
|
||||
end
|
||||
|
||||
describe "unknown user" do
|
||||
let(:parameters) { { email: "foo@example.org" } }
|
||||
let(:parameters) { { login: "new.login" } }
|
||||
let(:path) { api_v3_paths.user(666) }
|
||||
|
||||
it "responds with 404" do
|
||||
@@ -324,6 +337,24 @@ RSpec.describe API::V3::Users::UsersAPI do
|
||||
.at_path("errorIdentifier")
|
||||
end
|
||||
end
|
||||
|
||||
describe "email update" do
|
||||
let(:email) { "this.is.a.new@email.address" }
|
||||
let(:parameters) { { email: email } }
|
||||
|
||||
it "rejects the users email update" do
|
||||
send_request
|
||||
expect(last_response).to have_http_status(:unprocessable_entity)
|
||||
|
||||
expect(last_response.body)
|
||||
.to be_json_eql("email".to_json)
|
||||
.at_path("_embedded/details/attribute")
|
||||
|
||||
expect(last_response.body)
|
||||
.to be_json_eql("urn:openproject-org:api:v3:errors:PropertyIsReadOnly".to_json)
|
||||
.at_path("errorIdentifier")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "unauthorized user" do
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#
|
||||
# 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.
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
@@ -125,16 +125,25 @@ RSpec.describe "SCIM API Authentication" do
|
||||
|
||||
context "when scim_v2 scope is missing in token" do
|
||||
let(:token_scope) { "api_v3" }
|
||||
let(:expected_www_auth_header) do
|
||||
'Bearer realm="OpenProject API", scope="scim_v2", error="insufficient_scope", ' \
|
||||
'error_description="Requires scope scim_v2 to access this resource."'
|
||||
end
|
||||
|
||||
it do
|
||||
get "/scim_v2/ServiceProviderConfig", {}, headers
|
||||
expect(last_response.body).to eq("insufficient_scope")
|
||||
expect(last_response.headers["WWW-Authenticate"]).to eq("Bearer realm=\"OpenProject API\", error=\"insufficient_scope\", error_description=\"Requires scope scim_v2 to access this resource.\"")
|
||||
expect(last_response.headers["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
expect(last_response).to have_http_status(401)
|
||||
end
|
||||
end
|
||||
|
||||
context "when token_sub does not match a service_account" do
|
||||
let(:expected_www_auth_header) do
|
||||
'Bearer realm="OpenProject API", scope="scim_v2", error="invalid_token", ' \
|
||||
'error_description="The user identified by the token is not known"'
|
||||
end
|
||||
|
||||
before { service_account.user_auth_provider_links.delete_all }
|
||||
|
||||
it do
|
||||
@@ -142,7 +151,7 @@ RSpec.describe "SCIM API Authentication" do
|
||||
|
||||
expect(last_response).to have_http_status(401)
|
||||
expect(last_response.body).to eq("invalid_token")
|
||||
expect(last_response.headers["WWW-Authenticate"]).to eq("Bearer realm=\"OpenProject API\", error=\"invalid_token\", error_description=\"The user identified by the token is not known\"")
|
||||
expect(last_response.headers["WWW-Authenticate"]).to eq(expected_www_auth_header)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -205,11 +205,11 @@ RSpec.describe Projects::CreateArtifactWorkPackageService do
|
||||
date = Date.current.iso8601
|
||||
expect(Storages::UploadFileService)
|
||||
.to have_received(:call)
|
||||
.with(container: artifact_work_package,
|
||||
project_storage:,
|
||||
file_path: "Project mandate",
|
||||
filename: /#{project.identifier}_Project_mandate_#{artifact_work_package.status.name}_#{date}_\d+-\d+.pdf/,
|
||||
file_data: instance_of(StringIO))
|
||||
.with(container: artifact_work_package,
|
||||
project_storage:,
|
||||
file_path: "Project mandate",
|
||||
filename: /#{project.identifier}_Project_mandate_#{artifact_work_package.status.name}_#{date}_\d+-\d+.pdf/,
|
||||
file_data: instance_of(StringIO))
|
||||
end
|
||||
|
||||
context "with another default language", with_settings: { default_language: :de } do
|
||||
|
||||
Reference in New Issue
Block a user