Merge branch 'feature/62107-add-scim-server-api' into scim-authentication

This commit is contained in:
Pavel Balashou
2025-07-01 10:03:06 +02:00
committed by GitHub
956 changed files with 13573 additions and 4103 deletions
+2 -1
View File
@@ -35,7 +35,8 @@ jobs:
echo "OPENPROJECT_HSTS=false" >> .env.pullpreview
echo "OPENPROJECT_NOTIFICATIONS_POLLING_INTERVAL=10000" >> .env.pullpreview
echo "OPENPROJECT_ENTERPRISE__CHARGEBEE__SITE=openproject-enterprise-test" >> .env.pullpreview
echo "OPENPROJECT_ENTERPRISE__TRIAL__CREATION__HOST=start.openproject-edge.com" >> .env.pullpreview
echo "OPENPROJECT_ENTERPRISE__TRIAL__CREATION__HOST=https://start.openproject-edge.com" >> .env.pullpreview
echo "OPENPROJECT_FEATURE_BLOCK_NOTE_EDITOR=true" >> .env.pullpreview
- name: Boot as BIM edition
if: contains(github.ref, 'bim/') || contains(github.head_ref, 'bim/')
run: |
+4 -6
View File
@@ -25,17 +25,15 @@ jobs:
name: Units + Features
if: github.repository == 'opf/openproject'
runs-on:
labels:
- runs-on
- runner=32cpu-linux-x64
- family=m7+c7+r7+i7
- ram=128
- run-id=${{ github.run_id }}
labels: "runs-on=${{ github.run_id }}/runner=32cpu-linux-x64/family=m7+c7+r7+i7/ram=128"
timeout-minutes: 40
env:
DOCKER_BUILDKIT: 1
CI_RETRY_COUNT: 3
steps:
- uses: runs-on/action@v2
with:
metrics: cpu,memory,disk,io,network
- uses: actions/checkout@v4
- name: Cache DOCKER
id: cache_docker
+2 -2
View File
@@ -160,7 +160,7 @@ gem "structured_warnings", "~> 0.5.0"
gem "airbrake", "~> 13.0.0", require: false
gem "markly", "~> 0.13" # another markdown parser like commonmarker, but with AST support used in PDF export
gem "md_to_pdf", git: "https://github.com/opf/md-to-pdf", ref: "be1ab00e2c49ce2e5e08a4edb4733438081c53ab"
gem "md_to_pdf", git: "https://github.com/opf/md-to-pdf", ref: "9961752e4d1e990ec1d4bf48436de9277838763f"
gem "prawn", "~> 2.4"
gem "ttfunk", "~> 1.7.0" # remove after https://github.com/prawnpdf/prawn/issues/1346 resolved.
@@ -419,4 +419,4 @@ end
gem "openproject-octicons", "~>19.25.0"
gem "openproject-octicons_helper", "~>19.25.0"
gem "openproject-primer_view_components", "~>0.70.1"
gem "openproject-primer_view_components", "~>0.70.2"
+35 -35
View File
@@ -8,10 +8,10 @@ GIT
GIT
remote: https://github.com/opf/md-to-pdf
revision: be1ab00e2c49ce2e5e08a4edb4733438081c53ab
ref: be1ab00e2c49ce2e5e08a4edb4733438081c53ab
revision: 9961752e4d1e990ec1d4bf48436de9277838763f
ref: 9961752e4d1e990ec1d4bf48436de9277838763f
specs:
md_to_pdf (0.2.3)
md_to_pdf (0.2.4)
base64 (~> 0.2)
bigdecimal (~> 3.1)
color_conversion (~> 0.1)
@@ -344,8 +344,8 @@ GEM
awesome_nested_set (3.8.0)
activerecord (>= 4.0.0, < 8.1)
aws-eventstream (1.4.0)
aws-partitions (1.1118.0)
aws-sdk-core (3.226.0)
aws-partitions (1.1120.0)
aws-sdk-core (3.226.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -355,7 +355,7 @@ GEM
aws-sdk-kms (1.105.0)
aws-sdk-core (~> 3, >= 3.225.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.190.0)
aws-sdk-s3 (1.191.0)
aws-sdk-core (~> 3, >= 3.225.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
@@ -429,14 +429,14 @@ GEM
descendants_tracker (~> 0.0.1)
color_conversion (0.1.2)
colored2 (4.0.3)
commonmarker (2.3.0)
commonmarker (2.3.1)
rb_sys (~> 0.9)
commonmarker (2.3.0-aarch64-linux)
commonmarker (2.3.0-aarch64-linux-musl)
commonmarker (2.3.0-arm64-darwin)
commonmarker (2.3.0-x86_64-darwin)
commonmarker (2.3.0-x86_64-linux)
commonmarker (2.3.0-x86_64-linux-musl)
commonmarker (2.3.1-aarch64-linux)
commonmarker (2.3.1-aarch64-linux-musl)
commonmarker (2.3.1-arm64-darwin)
commonmarker (2.3.1-x86_64-darwin)
commonmarker (2.3.1-x86_64-linux)
commonmarker (2.3.1-x86_64-linux-musl)
compare-xml (0.66)
nokogiri (~> 1.8)
concurrent-ruby (1.3.5)
@@ -496,7 +496,7 @@ GEM
concurrent-ruby (~> 1.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
dry-monads (1.8.3)
dry-monads (1.9.0)
concurrent-ruby (~> 1.0)
dry-core (~> 1.1)
zeitwerk (~> 2.6)
@@ -857,7 +857,7 @@ GEM
actionview
openproject-octicons (= 19.25.0)
railties
openproject-primer_view_components (0.70.1)
openproject-primer_view_components (0.70.2)
actionview (>= 7.1.0)
activesupport (>= 7.1.0)
openproject-octicons (>= 19.25.0)
@@ -1077,7 +1077,7 @@ GEM
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.4)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
@@ -1096,7 +1096,7 @@ GEM
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.13.4)
rspec-wait (1.0.1)
rspec-wait (1.0.2)
rspec (>= 3.4)
rubocop (1.77.0)
json (~> 2.3)
@@ -1165,7 +1165,7 @@ GEM
securerandom (0.4.1)
selenium-devtools (0.137.0)
selenium-webdriver (~> 4.2)
selenium-webdriver (4.33.0)
selenium-webdriver (4.34.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
@@ -1448,7 +1448,7 @@ DEPENDENCIES
openproject-octicons (~> 19.25.0)
openproject-octicons_helper (~> 19.25.0)
openproject-openid_connect!
openproject-primer_view_components (~> 0.70.1)
openproject-primer_view_components (~> 0.70.2)
openproject-recaptcha!
openproject-reporting!
openproject-storages!
@@ -1582,10 +1582,10 @@ CHECKSUMS
auto_strip_attributes (2.6.0) sha256=a7e2e0cf744de2bcd947fd68014220702bcc88c81274c1cd9ce6f7316aae39b0
awesome_nested_set (3.8.0) sha256=469daff411d80291dbb80d1973133e498048a7afc2519c545f62d2cdebc60eda
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
aws-partitions (1.1118.0) sha256=1af3c8814245ed0571012f67f8a23e3feea357291471727a8f4107aea68aad20
aws-sdk-core (3.226.0) sha256=6b2dca6576d965b8c7ddf393fe49dac6aea2b6f4a07ead0c35306bba2b0c90f5
aws-partitions (1.1120.0) sha256=9dd2c4de7780ad0b51f6314a9cefb405e7e5cb678c67c5fecdc8b60367226d6b
aws-sdk-core (3.226.1) sha256=6e0fcee9d5fb8792c785ce5ae6a7af266e16e09e24bc623f13db7e91047ff5eb
aws-sdk-kms (1.105.0) sha256=d7756c1c2e3a79afaceb7c590f3846d2814cf217e28971c243608c41d65bae4f
aws-sdk-s3 (1.190.0) sha256=75c9e609796713b28c6428bd1d1be1c3f65b1dc5a541590c9b515183c1d85a2b
aws-sdk-s3 (1.191.0) sha256=62b541dbaee2925cbc9d21d2003cd4270f778ddbdb7a7dd58a5343514004dfd3
aws-sdk-sns (1.100.0) sha256=706bb13c5da789a5b9c6219b397980645536dd7f7301c09fbb8d3e8613f65a96
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
axe-core-api (4.10.3) sha256=6e10f3ed1c031804f16e8154d9d5dc658564d10850cee860e125fe665c3f0148
@@ -1617,13 +1617,13 @@ CHECKSUMS
coercible (1.0.0) sha256=5081ad24352cc8435ce5472bc2faa30260c7ea7f2102cc6a9f167c4d9bffaadc
color_conversion (0.1.2) sha256=99bea5fa412e1527a11389975aa6ad445ff8528ebae202c11d08c45ea2b94c96
colored2 (4.0.3) sha256=63e1038183976287efc43034f5cca17fb180b4deef207da8ba78d051cbce2b37
commonmarker (2.3.0) sha256=74fb85e4ae59a9fc166dd1813ad791805d09dec1a4e79c81a7be0a6a8dc5dc63
commonmarker (2.3.0-aarch64-linux) sha256=63e6c599ed2b1a01c012e5524ed25f60ab1132c6b582bcef0aa542770d3087ee
commonmarker (2.3.0-aarch64-linux-musl) sha256=2be68fc9106fe5b0a92928cf2903c0924ee2ef862fefd2b5c7943292902e79ab
commonmarker (2.3.0-arm64-darwin) sha256=fb5a6416395cbac63bb7741887c74e17141be75f407a292ab98cd6c8ab4894a2
commonmarker (2.3.0-x86_64-darwin) sha256=65919fe63bc1e01319d958351ae611cc913bd2a8aa3f1a4f3fb3d12e82fbc568
commonmarker (2.3.0-x86_64-linux) sha256=a6f2b523be0c3e4752aaf55cc5026239080b81f91bd2f72226b6e71919d204d0
commonmarker (2.3.0-x86_64-linux-musl) sha256=af895be1dfe723a1ae2671fd147d082e1be107be732b7a52e2e0d5daa1ae687a
commonmarker (2.3.1) sha256=8943ef0731a4205765b1ab8f25a7a9b9f62acb28b0054c7d60f06720a23cadc7
commonmarker (2.3.1-aarch64-linux) sha256=43b940cb5a4d59378c68d1dcf4480b4223817aaf53acebe262467eca8dd14807
commonmarker (2.3.1-aarch64-linux-musl) sha256=57971a5429973823f315a861210883640c864182cd622e3691b3d71321c9b3aa
commonmarker (2.3.1-arm64-darwin) sha256=e1c8991b92ea971b8933621124f6461ef06ea64c031429d8b8ebd297dab790dc
commonmarker (2.3.1-x86_64-darwin) sha256=75475606be508f0e3dbae03612516e89e8cbf8d0913c172729b53d0c30853d5e
commonmarker (2.3.1-x86_64-linux) sha256=afa0df3f64076f0fe996120783db6af28b6d634019ff3a954155884d409caf2a
commonmarker (2.3.1-x86_64-linux-musl) sha256=70556fce0bc3f67026e5a20ea9881080755cae80d165374c88498402ba9536a5
compare-xml (0.66) sha256=e21aa5c0f69ef1177eced997c688fd4df989084e74a1b612257af32e1dd05319
concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6
connection_pool (2.5.3) sha256=cfd74a82b9b094d1ce30c4f1a346da23ee19dc8a062a16a85f58eab1ced4305b
@@ -1656,7 +1656,7 @@ CHECKSUMS
dry-inflector (1.2.0) sha256=22f5d0b50fd57074ae57e2ca17e3b300e57564c218269dcf82ff3e42d3f38f2e
dry-initializer (3.2.0) sha256=37d59798f912dc0a1efe14a4db4a9306989007b302dcd5f25d0a2a20c166c4e3
dry-logic (1.6.0) sha256=da6fedbc0f90fc41f9b0cc7e6f05f5d529d1efaef6c8dcc8e0733f685745cea2
dry-monads (1.8.3) sha256=5fbc06ae4ff76ae081922a902be998673703304d10b46b08931696f2c8decc06
dry-monads (1.9.0) sha256=9348a67b5c862c7a876342dbd94737fdf3fb3c17978382cf6801a85b27215816
dry-schema (1.14.1) sha256=2fcd7539a7099cacae6a22f6a3a2c1846fe5afeb1c841cde432c89c6cb9b9ff1
dry-types (1.8.2) sha256=c84e9ada69419c727c3b12e191e0ed7d2c6d58d040d55e79ea16e0ebf8b3ec0f
dry-validation (1.11.1) sha256=70900bb5a2d911c8aab566d3e360c6bff389b8bf92ea8e04885ce51c41ff8085
@@ -1763,7 +1763,7 @@ CHECKSUMS
marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4
markly (0.13.0) sha256=326a232c876cd95093d110b8d184be8effeadd776c3ffcefd59bdc8de7f59e0d
matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b
md_to_pdf (0.2.3)
md_to_pdf (0.2.4)
messagebird-rest (1.4.2) sha256=1501e16ad76c87558f20f3b26cb39d05f587b349b44b8bf8056b040e9b495301
meta-tags (2.22.1) sha256=e5ae1febbd320d396c7226d7edb868e5d63466c14b9c8b06622a1a74e6dce354
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
@@ -1821,7 +1821,7 @@ CHECKSUMS
openproject-octicons (19.25.0) sha256=16fc221375e693f0e893b1c208286f2d7719ae4dfe080c5415642b221f51f550
openproject-octicons_helper (19.25.0) sha256=9b1778a67b0015ebe84ca0471f74e31004b985a8dcaaa443f7a2ac365b0a4e2d
openproject-openid_connect (1.0.0)
openproject-primer_view_components (0.70.1) sha256=6ba2eee9308d15e45b8fa5aac5082bad8da78f38ce948eedb5feb3ea48841705
openproject-primer_view_components (0.70.2) sha256=b08bc35f1edb00c583544e8757badf8914ba7e32c88a2eb187de53a6d688ba0a
openproject-recaptcha (1.0.0)
openproject-reporting (1.0.0)
openproject-storages (1.0.0)
@@ -1911,13 +1911,13 @@ CHECKSUMS
rotp (6.3.0) sha256=75d40087e65ed0d8022c33055a6306c1c400d1c12261932533b5d6cbcd868854
rouge (4.5.2) sha256=034233fb8a69d0ad0e0476943184e04cb971b68e3c2239724e02f428878b68a3
rspec (3.13.1) sha256=b9f9a58fa915b8d94a1d6b3195fe6dd28c4c34836a6097015142c4a9ace72140
rspec-core (3.13.4) sha256=f9da156b7b775c82610a7b580624df51a55102f8c8e4a103b98f5d7a9fa23958
rspec-core (3.13.5) sha256=ab3f682897c6131c67f9a17cfee5022a597f283aebe654d329a565f9937a4fa3
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
rspec-mocks (3.13.5) sha256=e4338a6f285ada9fe56f5893f5457783af8194f5d08884d17a87321d5195ea81
rspec-rails (8.0.1) sha256=0c3700b10ab6d7c648c4cd554023d8c2b5b07e7f01205f7608f0c511cf686505
rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858
rspec-support (3.13.4) sha256=184b1814f6a968102b57df631892c7f1990a91c9a3b9e80ef892a0fc2a71a3f7
rspec-wait (1.0.1) sha256=50ce9cc7cd20b8bb67b95a4ed56b59a16d4af7e86ae91b28dbf20eeaa2481d1f
rspec-wait (1.0.2) sha256=865f921239325d3d26fc10ded4bdd485d8b58bcaaad1a28dd85ed15266b5a912
rubocop (1.77.0) sha256=1f360b4575ef7a124be27b0dfffa227a2b2d9420d22d4fd8bf179d702bcc88c0
rubocop-ast (1.45.1) sha256=94042e49adc17f187ba037b33f941ba7398fede77cdf4bffafba95190a473a3e
rubocop-capybara (2.22.1) sha256=ced88caef23efea53f46e098ff352f8fc1068c649606ca75cb74650970f51c0c
@@ -1943,7 +1943,7 @@ CHECKSUMS
secure_headers (7.1.0) sha256=6b1f9d5f9507af2948f4636452c41c09371927836396c2185438ffdf0a731124
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
selenium-devtools (0.137.0) sha256=ea29581fe0502f10eb1e45492a62e2228398822180655ea94d146050fc44fa3c
selenium-webdriver (4.33.0) sha256=720cad35cb7c2fdd87706940ed107bf2ec6873e50fa45b26f497fa28a8f97e1d
selenium-webdriver (4.34.0) sha256=ec7bb718cbe66fe2b247d8ca5e6ba26caed0976d76579d7cb2fadd8dae8b271e
semantic (1.6.1) sha256=3cdbb48f59198ebb782a3fdfb87b559e0822a311610db153bae22777a7d0c163
shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34
shoulda-matchers (6.5.0) sha256=ef6b572b2bed1ac4aba6ab2c5ff345a24b6d055a93a3d1c3bfc86d9d499e3f44
+2 -1
View File
@@ -6,6 +6,7 @@
@import "open_project/common/attribute_help_text_component"
@import "open_project/common/attribute_label_component"
@import "open_project/common/submenu_component"
@import "open_project/common/main_menu_toggle_component"
@import "projects/row_component"
@import "projects/phases/hover_card_component"
@import "shares/invite_user_form_component"
@@ -13,6 +14,7 @@
@import "users/hover_card_component"
@import "work_package_relations_tab/index_component"
@import "work_package_relations_tab/relation_component"
@import "work_package_types/pattern_input"
@import "work_packages/activities_tab/index_component"
@import "work_packages/activities_tab/journals/new_component"
@import "work_packages/activities_tab/journals/index_component"
@@ -29,5 +31,4 @@
@import "work_packages/hover_card_component"
@import "work_packages/progress/modal_body_component"
@import "work_packages/reminder/modal_body_component"
@import "work_packages/types/pattern_input"
@import "work_packages/split_view_component"
@@ -12,7 +12,7 @@
end
grid.with_area(:content) do
flex_layout do |flex|
flex_layout(h: :full) do |flex|
flex.with_row do
if medium? || large?
flex_layout(
@@ -73,6 +73,7 @@ $op-enterprise-banner-fixed-height-large: 320px
grid-template-columns: 1fr 1fr
&--content
height: $op-enterprise-banner-fixed-height-medium
z-index: 1
align-self: center
padding: var(--base-size-16)
@@ -127,6 +128,8 @@ $op-enterprise-banner-fixed-height-large: 320px
top: var(--base-size-24)
right: var(--base-size-24)
&--description
flex-grow: 1
&--content
padding: var(--base-size-6) var(--base-size-8)
@@ -0,0 +1,48 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module EnterpriseEdition
class BuyNowButtonComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
def call
render(Primer::Beta::Button.new(
classes: "upsell-colored-background hidden-for-mobile",
tag: :a,
href: OpenProject::Enterprise.upgrade_path,
align_self: :center
)) do
I18n.t(:button_buy_now)
end
end
end
end
@@ -0,0 +1,31 @@
<%=
render(Primer::BaseComponent.new(tag: :div, classes: "op-enterprise-banner-container")) do
grid_layout(
"op-enterprise-banner",
**@system_arguments
) do |grid|
grid.with_area(:visual) do
render(Primer::Beta::Octicon.new(icon: :"op-enterprise-addons", classes: "op-enterprise-banner--icon"))
end
grid.with_area(:content) do
flex_layout do |flex|
flex.with_row do
render(Primer::Beta::Text.new(font_weight: :bold)) { title }
end
flex.with_row(classes: "op-enterprise-banner--description") do
concat render(Primer::Beta::Text.new(tag: :p)) { description }
end
flex.with_row(mt: 1) do
render EnterpriseEdition::UpsellButtonsComponent.new(
:teaser,
justify_content: :flex_start
)
end
end
end
end
end
%>
@@ -0,0 +1,79 @@
# frozen_string_literal: true
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
module EnterpriseEdition
class TrialTeaserComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
def initialize(**system_arguments)
set_system_arguments(system_arguments)
super
end
private
def set_system_arguments(system_arguments)
@system_arguments = system_arguments
@system_arguments[:tag] = :div
@system_arguments[:mx] = 2
@system_arguments[:mt] = 2
@system_arguments[:mb] = 3
@system_arguments[:id] = "op-enterprise-banner-teaser"
@system_arguments[:test_selector] = "op-enterprise-banner"
@system_arguments[:classes] = class_names(
@system_arguments[:classes],
"op-enterprise-banner"
)
end
def render?
User.current.admin? && EnterpriseToken.trial_only?
end
def token
@token ||= EnterpriseToken.active_trial_token
end
def title
I18n.t("ee.teaser.title", count: token.days_left, trial_plan: token.plan)
end
def description
I18n.t("ee.teaser.description", trial_plan: plan_name).html_safe
end
def plan_name
render(Primer::Beta::Text.new(font_weight: :bold, classes: "upsell-colored")) do
I18n.t("ee.upsell.plan_name", plan: token.plan.capitalize)
end
end
end
end
@@ -75,14 +75,7 @@ module EnterpriseEdition
return unless EnterpriseToken.active?
return unless User.current.admin?
render(Primer::Beta::Button.new(
classes: "upsell-colored-background",
tag: :a,
href: OpenProject::Enterprise.upgrade_path,
align_self: :center
)) do
I18n.t("ee.upsell.buy_now_button")
end
render(EnterpriseEdition::BuyNowButtonComponent.new)
end
# Allow providing a custom upgrade now button
@@ -108,6 +101,8 @@ module EnterpriseEdition
end
def more_info_button
return if @feature_key == :teaser
render(Primer::Beta::Link.new(href: enterprise_link)) do |link|
link.with_trailing_visual_icon(icon: "link-external")
link_title
@@ -25,22 +25,26 @@
end
%>
<p class="widget-box--blocks--upsell-description">
<% if @trial_key %>
<%= I18n.t("ee.trial.confirmation_info",
date: helpers.format_date(@trial_key.created_at),
email: @trial_key.data[:email]) %>
<% else %>
<%= t("ee.upsell.homescreen_description") %>
<% end %>
</p>
<p class="widget-box--blocks--upsell-description">
<% if @trial_key %>
<%= t("ee.trial.confirmation_subline") %>
<% else %>
<%= t("ee.upsell.homescreen_subline") %>
<% end %>
</p>
<div class="widget-box--blocks--upsell-description">
<p>
<% if @trial_key %>
<%= I18n.t(
"ee.trial.confirmation_info",
date: helpers.format_date(@trial_key.created_at),
email: @trial_key.data[:email]
) %>
<% else %>
<%= t("ee.upsell.homescreen_description") %>
<% end %>
</p>
<p>
<% if @trial_key %>
<%= t("ee.trial.confirmation_subline") %>
<% else %>
<%= t("ee.upsell.homescreen_subline") %>
<% end %>
</p>
</div>
<%= render EnterpriseEdition::UpsellButtonsComponent.new(nil, show_buy_now: true) %>
</div>
@@ -0,0 +1,78 @@
# frozen_string_literal: true
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
#
module OpenProject
module Common
class MainMenuToggleComponent < ApplicationComponent
def initialize(expanded:)
super()
@expanded = expanded
end
def call
render(Primer::Beta::IconButton.new(icon:,
id:,
aria: { label: aria_label },
scheme:,
size:,
data:))
end
private
def icon
@expanded ? "sidebar-expand" : :"sidebar-collapse"
end
def id
"menu-toggle--#{@expanded ? 'collapse-button' : 'expand-button'}"
end
def aria_label
@expanded ? I18n.t("js.label_hide_project_menu") : I18n.t("js.label_expand_project_menu")
end
def scheme
:invisible
end
def size
@expanded ? :medium : :small
end
def data
{
action: "click->menus--main-toggle#toggleNavigation"
}
end
end
end
end
@@ -0,0 +1,50 @@
//-- 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.
//++
#menu-toggle--expand-button
position: absolute
left: 11px
z-index: 1
@at-root .nosidebar &
display: none
#menu-toggle--collapse-button
.Button-visual
color: var(--main-menu-font-color)
#wrapper
.hidden-navigation
// Push the breadcrumb to the right when the sidebar is collapsed
.PageHeader-contextBar,
.op-breadcrumbs
margin-left: 30px
&:not(.hidden-navigation)
#menu-toggle--expand-button
display: none
@@ -2,10 +2,13 @@
height: 100%
display: flex
flex-direction: column
overflow: hidden
&--search
margin: 12px var(--main-menu-x-spacing)
position: sticky
top: 0
z-index: 1
background: var(--main-menu-bg-color)
padding: 12px var(--main-menu-x-spacing)
color: var(--main-menu-font-color)
display: flex
flex-direction: column
@@ -41,17 +44,18 @@
background: transparent
color: var(--main-menu-fieldset-header-color)
border: 1px solid transparent
border-radius: var(--borderRadius-medium)
text-transform: uppercase
padding: 8px var(--main-menu-x-spacing) 8px var(--main-menu-x-spacing)
margin-top: 12px
margin-bottom: 2px
font-size: 12px
cursor: pointer
width: 100%
&:hover
background: var(--main-menu-bg-hover-background)
color: var(--main-menu-hover-font-color)
background: var(--control-transparent-bgColor-hover)
color: var(--main-menu-font-color)
border-color: var(--main-menu-hover-border-color)
&--items
@@ -60,6 +64,9 @@
&_collapsed
display: none
&--item
margin-bottom: 2px !important
&--item-action
display: flex
align-items: center
@@ -67,8 +74,8 @@
padding: 8px 12px 8px 32px
&:hover
background: var(--main-menu-bg-hover-background)
color: var(--main-menu-hover-font-color)
background: var(--control-transparent-bgColor-hover)
color: var(--main-menu-font-color)
&_active
background: var(--main-menu-bg-selected-background)
@@ -25,20 +25,25 @@
id: "dialog-show-project-custom-field-section-dialog",
tag: :a,
href: new_link_admin_settings_project_custom_field_sections_path,
content_arguments: { data: { controller: "async-dialog" } }
content_arguments: { data: { controller: "async-dialog", test_selector: "add-project-custom-field-section" } }
)
menu.with_sub_menu_item(label: t("settings.project_attributes.label_new_attribute")) do |sub_menu|
OpenProject::CustomFieldFormat.available_for_class_name("Project")
.sort_by(&:name)
.map do |format|
sub_menu.with_item(
label: helpers.label_for_custom_field_format(format.name),
tag: :a,
href: new_admin_settings_project_custom_field_path(field_format: format.name),
content_arguments: { data: { turbo: "false",
test_selector: "new-project-custom-field-button" } }
)
if allow_custom_field_creation?
menu.with_sub_menu_item(
label: t("settings.project_attributes.label_new_attribute"),
content_arguments: { data: { test_selector: "add-project-custom-field-attribute" } }
) do |sub_menu|
OpenProject::CustomFieldFormat.available_for_class_name("Project")
.sort_by(&:name)
.map do |format|
sub_menu.with_item(
label: helpers.label_for_custom_field_format(format.name),
tag: :a,
href: new_admin_settings_project_custom_field_path(field_format: format.name),
content_arguments: { data: { turbo: "false",
test_selector: "new-project-custom-field-button" } }
)
end
end
end
end
@@ -34,11 +34,21 @@ module Settings
include OpTurbo::Streamable
include CustomFieldsHelper
def initialize(allow_custom_field_creation:)
super
@allow_custom_field_creation = allow_custom_field_creation
end
def breadcrumbs_items
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_project_custom_fields_path, text: t("label_project_plural") },
t("settings.project_attributes.heading")]
end
def allow_custom_field_creation?
@allow_custom_field_creation
end
end
end
end
@@ -27,7 +27,7 @@
#++
module Shares
class ManageSharesComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
class ManageSharesComponent < ApplicationComponent
include ApplicationHelper
include MemberHelper
include OpPrimer::ComponentHelpers
@@ -106,7 +106,7 @@ module Shares
return false if role_filter_value.nil?
selected_role = strategy.available_roles.find { _1[:value] == option[:value] }
selected_role = strategy.available_roles.find { it[:value] == option[:value] }
selected_role[:value] == role_filter_value.to_i
end
@@ -28,7 +28,7 @@
module Users
module Profile
class AttributesComponent < ApplicationComponent # rubocop:disable OpenProject/AddPreviewForViewComponent
class AttributesComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
@@ -40,7 +40,7 @@ module Users
end
def render?
user_is_allowed_to_see_email || @user.visible_custom_field_values.any? { _1.value.present? }
user_is_allowed_to_see_email || @user.visible_custom_field_values.any? { it.value.present? }
end
def visible_custom_fields
@@ -19,22 +19,22 @@ class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
].freeze
FIRST_LEVEL_RELATION_MENU_TYPES = [
*ADD_CHILD_MENU_TYPES,
Relation::TYPE_RELATES,
Relation::TYPE_FOLLOWS,
Relation::TYPE_PRECEDES,
Relation::TYPE_PARENT,
Relation::TYPE_RELATES
*ADD_CHILD_MENU_TYPES,
Relation::TYPE_PARENT
].freeze
SECOND_LEVEL_RELATION_MENU_TYPES = [
Relation::TYPE_DUPLICATES,
Relation::TYPE_DUPLICATED,
Relation::TYPE_BLOCKS,
Relation::TYPE_BLOCKED,
Relation::TYPE_INCLUDES,
Relation::TYPE_PARTOF,
Relation::TYPE_REQUIRES,
Relation::TYPE_REQUIRED,
Relation::TYPE_DUPLICATES,
Relation::TYPE_DUPLICATED
Relation::TYPE_REQUIRED
].freeze
include ApplicationHelper
@@ -28,4 +28,4 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<p><%= I18n.t("types.edit.export_configuration.intro") %></p>
<%= render(WorkPackages::Types::ExportTemplateListComponent.new(type: model)) %>
<%= render(WorkPackageTypes::ExportTemplateListComponent.new(type: model)) %>
@@ -28,9 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackages
module Types
class ExportConfigurationComponent < ApplicationComponent
end
module WorkPackageTypes
class ExportConfigurationComponent < ApplicationComponent
end
end
@@ -76,7 +76,7 @@ See COPYRIGHT and LICENSE files for more details.
@type.pdf_export_templates.list.each do |template|
component.with_row(data: draggable_item_config(template)) do
render(
WorkPackages::Types::ExportTemplateRowComponent.new(
WorkPackageTypes::ExportTemplateRowComponent.new(
type: @type,
template:
)
@@ -0,0 +1,67 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class ExportTemplateListComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
def initialize(type:)
super
@type = type
end
def wrapper_data_attributes
{
controller: "generic-drag-and-drop"
}
end
def drag_and_drop_target_config
{
"is-drag-and-drop-target": true,
"target-container-accessor": "& > ul",
"target-allowed-drag-type": "template",
test_selector: "pdf-export-template-rows"
}
end
def draggable_item_config(template)
{
"draggable-id": template.id,
"draggable-type": "template",
"drop-url": drop_type_pdf_export_template_path(type_id: @type.id, id: template.id),
test_selector: "pdf-export-template-row-#{template.id}"
}
end
end
end
@@ -45,16 +45,18 @@ See COPYRIGHT and LICENSE files for more details.
end
end
item_information.with_column(py: 1, mr: 2) do
render(Primer::Alpha::ToggleSwitch.new(
src: toggle_type_pdf_export_template_path(type_id: @type.id, id: @template.id),
csrf_token: form_authenticity_token,
data: { "turbo-method": :post, "turbo-stream": true },
checked: @template.enabled,
size: :small,
status_label_position: :start,
test_selector: "toggle-pdf-export-template-row-#{@template.id}",
classes: "op-primer-adjustments__toggle-switch--hidden-loading-indicator"
))
render(
Primer::Alpha::ToggleSwitch.new(
src: toggle_type_pdf_export_template_path(type_id: @type.id, id: @template.id),
csrf_token: form_authenticity_token,
data: { "turbo-method": :post, "turbo-stream": true },
checked: @template.enabled,
size: :small,
status_label_position: :start,
test_selector: "toggle-pdf-export-template-row-#{@template.id}",
classes: "op-primer-adjustments__toggle-switch--hidden-loading-indicator"
)
)
end
end
end
@@ -28,18 +28,17 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackages
module Types
class ExportTemplateRowComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
def initialize(type:, template:)
super
module WorkPackageTypes
class ExportTemplateRowComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
@template = template
@type = type
end
def initialize(type:, template:)
super
@template = template
@type = type
end
end
end
@@ -0,0 +1,57 @@
# frozen_string_literal: true
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
module WorkPackageTypes
class PatternInput < Primer::Forms::BaseComponent
prepend Primer::OpenProject::Forms::WrappedInput
delegate :name, to: :@input
def initialize(input:, value:, suggestions:)
super()
@input = input
@value = value
@suggestions = suggestions
end
def suggestions_for_stimulus
@suggestions_for_stimulus ||= @suggestions.to_json
end
def suggestions_list_component
@suggestions_list_component ||= Primer::Alpha::ActionList.new(
role: :list,
scheme: :inset,
ml: 0,
"data-pattern-input-target": "suggestions"
)
end
end
end
@@ -0,0 +1,61 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
settings_primer_form_with(**form_options) do |form|
component_wrapper do
flex_layout do |container|
container.with_row(mb: 3) do
render(
Primer::OpenProject::TreeView.new(
expanded: true,
data: {
"admin--work-package-type-projects-target": "treeView",
action: "click->admin--work-package-type-projects#updateSelectedProjects"
},
select_variant: :multiple
)
) do |tree_view|
build_project_tree(tree_view)
end
end
container.with_row do
form.hidden_field :project_ids,
value: "[]",
data: { "admin--work-package-type-projects-target": "selectedProjects" }
end
container.with_row do
render(Primer::Beta::Button.new(scheme: :primary, type: :submit)) { t(:button_save) }
end
end
end
end
%>
@@ -0,0 +1,82 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class ProjectsComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
def form_options
{
url: type_projects_path(type_id: model.id),
method: :put,
model:,
data: {
controller: "admin--work-package-type-projects",
"admin--work-package-type-projects-initially-selected-projects-value": model.projects.pluck(:id).join(",")
}
}
end
def projects = options[:projects]
def build_project_tree(tree)
nested_project_list = Project.build_projects_hierarchy(projects)
add_sub_tree(tree, nested_project_list)
end
private
def add_sub_tree(tree, project_list)
project_list.each do |project_hash|
if project_hash[:children].empty?
tree.with_leaf(**item_options(project_hash[:project]))
else
tree.with_sub_tree(expanded: true,
select_strategy: :self,
**item_options(project_hash[:project])) do |sub_tree|
add_sub_tree(sub_tree, project_hash[:children])
end
end
end
end
def item_options(item)
{
select_variant: :multiple,
label: item.name,
data: { project_id: item.id },
checked: model.projects.include?(item)
}
end
end
end
@@ -29,6 +29,6 @@ See COPYRIGHT and LICENSE files for more details.
<%=
settings_primer_form_with(**form_options) do |f|
render(WorkPackages::Types::SettingsForm.new(f))
render(WorkPackageTypes::SettingsForm.new(f))
end
%>
@@ -0,0 +1,62 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class SettingsComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
def initialize(model, copy_workflow_from: nil, **)
@copy_workflow_from = copy_workflow_from
super(model, **)
end
def form_options
if model.new_record?
create_form_options
else
update_form_options
end
end
private
attr_reader :copy_workflow_from
def create_form_options
{ url: types_path, method: :post, model:, copy_workflow_from: }
end
def update_form_options
{ url: update_tab_type_path(id: model.id, tab: :settings), method: :patch, model: }
end
end
end
@@ -33,6 +33,6 @@ See COPYRIGHT and LICENSE files for more details.
<%=
settings_primer_form_with(**form_options) do |f|
render(WorkPackages::Types::SubjectConfigurationForm.new(f))
render(WorkPackageTypes::SubjectConfigurationForm.new(f))
end
%>
@@ -0,0 +1,110 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class SubjectConfigurationComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
def initialize(model, subject_configuration_form_data: nil, **)
@subject_configuration_form_data = subject_configuration_form_data
super(model, **)
end
def form_options
form_model = subject_form_object
{
url: subject_configuration_type_path(id: model.id),
method: :put,
model: form_model,
data: {
controller: "admin--subject-configuration",
admin__subject_configuration_hide_pattern_input_value: form_model.subject_configuration == :manual
}
}
end
private
def subject_form_object
values = subject_configuration_form_values
Forms::SubjectConfigurationFormModel.new(
subject_configuration: values[:subject_configuration],
pattern: values[:pattern],
suggestions: sort_attributes(supported_attributes),
validation_errors: model.errors
)
end
def supported_attributes
attribute_tokens = Patterns::TokenPropertyMapper.new.tokens_for_type(model)
result = {
work_package: {
title: I18n.t("types.edit.subject_configuration.token.context.work_package"),
tokens: []
},
parent: {
title: I18n.t("types.edit.subject_configuration.token.context.parent"),
tokens: []
},
project: {
title: I18n.t("types.edit.subject_configuration.token.context.project"),
tokens: []
}
}
attribute_tokens.each_with_object(result) do |token, obj|
token_hash = { key: token.key, label: token.label, label_with_context: token.label_with_context }
obj[token.context][:tokens] << token_hash
end
end
def sort_attributes(attributes)
attributes.each_value { |group| group[:tokens] = group[:tokens].sort_by { |a| a[:label] } }
attributes
end
def subject_configuration_form_values
if @subject_configuration_form_data.present?
@subject_configuration_form_data
else
persisted_subject_pattern = model.patterns.subject || Pattern.new(blueprint: "", enabled: false)
subject_configuration = persisted_subject_pattern.enabled ? :generated : :manual
pattern = persisted_subject_pattern.blueprint
{ subject_configuration:, pattern: }
end
end
end
end
@@ -1,112 +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.
#++
module WorkPackages
module Types
class SubjectConfigurationComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
include OpTurbo::Streamable
def initialize(model, subject_configuration_form_data: nil, **)
@subject_configuration_form_data = subject_configuration_form_data
super(model, **)
end
def form_options
form_model = subject_form_object
{
url: subject_configuration_type_path(id: model.id),
method: :put,
model: form_model,
data: {
controller: "admin--subject-configuration",
admin__subject_configuration_hide_pattern_input_value: form_model.subject_configuration == :manual
}
}
end
private
def subject_form_object
values = subject_configuration_form_values
::Types::Forms::SubjectConfigurationFormModel.new(
subject_configuration: values[:subject_configuration],
pattern: values[:pattern],
suggestions: sort_attributes(supported_attributes),
validation_errors: model.errors
)
end
def supported_attributes
attribute_tokens = ::Types::Patterns::TokenPropertyMapper.new.tokens_for_type(model)
result = {
work_package: {
title: I18n.t("types.edit.subject_configuration.token.context.work_package"),
tokens: []
},
parent: {
title: I18n.t("types.edit.subject_configuration.token.context.parent"),
tokens: []
},
project: {
title: I18n.t("types.edit.subject_configuration.token.context.project"),
tokens: []
}
}
attribute_tokens.each_with_object(result) do |token, obj|
token_hash = { key: token.key, label: token.label, label_with_context: token.label_with_context }
obj[token.context][:tokens] << token_hash
end
end
def sort_attributes(attributes)
attributes.each_value { |group| group[:tokens] = group[:tokens].sort_by { |a| a[:label] } }
attributes
end
def subject_configuration_form_values
if @subject_configuration_form_data.present?
@subject_configuration_form_data
else
persisted_subject_pattern = model.patterns.subject || ::Types::Pattern.new(blueprint: "", enabled: false)
subject_configuration = persisted_subject_pattern.enabled ? :generated : :manual
pattern = persisted_subject_pattern.blueprint
{ subject_configuration:, pattern: }
end
end
end
end
end
+12 -12
View File
@@ -68,29 +68,29 @@ module Attachments
end
##
# Validates the content type, if a whitelist is set
# Validates the content type, if a allowlist is set
def validate_content_type
# If the whitelist is empty, assume all files are allowed
# If the allowlist is empty, assume all files are allowed
# as before
unless matches_whitelist?(attachment_whitelist)
Rails.logger.info { "Uploaded file #{model.filename} with type #{model.content_type} does not match whitelist" }
errors.add :content_type, :not_whitelisted, value: model.content_type
unless matches_allowlist?(attachment_allowlist)
Rails.logger.info { "Uploaded file #{model.filename} with type #{model.content_type} does not match allowlist" }
errors.add :content_type, :not_allowlisted, value: model.content_type
end
end
##
# Get the user-defined whitelist or a custom whitelist
# Get the user-defined allowlist or a custom allowlist
# defined for this invocation
def attachment_whitelist
Array(options.fetch(:whitelist, Setting.attachment_whitelist))
def attachment_allowlist
Array(options.fetch(:allowlist, Setting.attachment_whitelist))
end
##
# Returns whether the attachment matches the whitelist
def matches_whitelist?(whitelist)
return true if whitelist.empty?
# Returns whether the attachment matches the allowlist
def matches_allowlist?(allowlist)
return true if allowlist.empty?
whitelist.include?(model.content_type) || whitelist.include?("*#{model.extension}")
allowlist.include?(model.content_type) || allowlist.include?("*#{model.extension}")
end
end
end
+1 -1
View File
@@ -54,7 +54,7 @@ module Shares
end
def role_grantable
errors.add(:roles, :ungrantable) unless active_roles.all? { _1.is_a?(assignable_role_class) }
errors.add(:roles, :ungrantable) unless active_roles.all? { it.is_a?(assignable_role_class) }
end
def active_roles
+3 -3
View File
@@ -112,9 +112,9 @@ module Types
return if blueprint.nil?
valid_tokens = flat_valid_token_list
invalid_tokens = blueprint.scan(PatternResolver::TOKEN_REGEX)
invalid_tokens = blueprint.scan(::WorkPackageTypes::PatternResolver::TOKEN_REGEX)
.reduce([]) do |acc, match|
token = Patterns::PatternToken.build(match).key
token = ::WorkPackageTypes::Patterns::PatternToken.build(match).key
valid_tokens.include?(token) ? acc : acc << token
end
@@ -123,6 +123,6 @@ module Types
end
end
def flat_valid_token_list = Patterns::TokenPropertyMapper.new.tokens_for_type(model).map(&:key)
def flat_valid_token_list = ::WorkPackageTypes::Patterns::TokenPropertyMapper.new.tokens_for_type(model).map(&:key)
end
end
@@ -0,0 +1,51 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class CopyWorkflowAttributeContract < Dry::Validation::Contract
config.messages.backend = :i18n
params do
optional(:copy_workflow_from).filled(:string)
end
rule(:copy_workflow_from) do
next unless key?
type = Type.find_by(id: value)
if type.nil?
key.failure(:not_found)
next
end
key.failure(:workflow_missing) if type.workflows.empty?
end
end
end
@@ -0,0 +1,87 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class UpdateFormConfigurationContract < BaseContract
attribute :attribute_groups
validate :validate_attribute_group_names
validate :validate_attribute_groups
private
def validate_attribute_group_names
return unless model.attribute_groups_changed?
seen = Set.new
model.attribute_groups.each do |group|
errors.add(:attribute_groups, :group_without_name) if group.key.blank?
errors.add(:attribute_groups, :duplicate_group, group: group.key) if seen.add?(group.key).nil?
end
end
def validate_attribute_groups
return unless model.attribute_groups_changed?
model.attribute_groups_objects.each do |group|
if group.is_a?(Type::QueryGroup)
validate_query_group(group)
else
validate_attribute_group(group)
end
end
end
def validate_query_group(group)
query = group.query
contract_class = query.persisted? ? Queries::UpdateContract : Queries::CreateContract
contract = contract_class.new(query, user)
unless contract.validate
errors.add(:attribute_groups, :query_invalid, group: group.key, details: contract.errors.full_messages.join)
end
end
def validate_attribute_group(group)
valid_attributes = model.work_package_attributes.keys
group.attributes.each do |key|
if key.is_a?(String) && valid_attributes.exclude?(key)
errors.add(
:attribute_groups,
I18n.t("activerecord.errors.models.type.attributes.attribute_groups.attribute_unknown_name",
attribute: key)
)
end
end
end
end
end
@@ -0,0 +1,35 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class UpdateProjectsContract < BaseContract
attribute :projects
end
end
@@ -41,9 +41,9 @@ module WorkPackageTypes
return if blueprint.nil?
valid_tokens = flat_valid_token_list
invalid_tokens = blueprint.scan(Types::PatternResolver::TOKEN_REGEX)
invalid_tokens = blueprint.scan(WorkPackageTypes::PatternResolver::TOKEN_REGEX)
.reduce([]) do |acc, match|
token = Types::Patterns::PatternToken.build(match).key
token = WorkPackageTypes::Patterns::PatternToken.build(match).key
valid_tokens.include?(token) ? acc : acc << token
end
@@ -52,6 +52,6 @@ module WorkPackageTypes
end
end
def flat_valid_token_list = Types::Patterns::TokenPropertyMapper.new.tokens_for_type(model).map(&:key)
def flat_valid_token_list = WorkPackageTypes::Patterns::TokenPropertyMapper.new.tokens_for_type(model).map(&:key)
end
end
+15 -1
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -286,6 +288,7 @@ module WorkPackages
def validate_parent_not_self
if model.parent == model
errors.delete(:parent_id) # remove the error added by closure_tree's cycle detection
errors.add :parent, :cannot_be_self_assigned
end
end
@@ -301,11 +304,22 @@ module WorkPackages
if model.parent_id_changed? &&
model.parent_id &&
errors.exclude?(:parent) &&
WorkPackage.relatable(model, Relation::TYPE_PARENT).where(id: model.parent_id).empty?
current_parent_unrelatable?
# closure_tree adds an error on :parent_id because of the cycle
# detection, and active_record sees the error when saving the children
# association and adds an error on :children as well. We need to remove
# them.
errors.delete(:parent_id) # remove the error added by closure_tree
errors.delete(:children) # remove the error added by active_record
# add our own error
errors.add :parent, :cant_link_a_work_package_with_a_descendant
end
end
def current_parent_unrelatable?
WorkPackage.relatable(model, Relation::TYPE_PARENT).where(id: model.parent_id).empty?
end
def validate_status_exists
errors.add :status, :does_not_exist if model.status && !status_exists?
end
@@ -40,7 +40,7 @@ module Admin::Settings
)
if call.success?
update_header_via_turbo_stream # required to closed the dialog
update_header_via_turbo_stream(allow_custom_field_creation: allow_custom_field_creation?)
update_sections_via_turbo_stream(project_custom_field_sections: ProjectCustomFieldSection.all)
else
update_section_dialog_body_form_via_turbo_stream(project_custom_field_section: call.result)
@@ -67,6 +67,7 @@ module Admin::Settings
call = ::ProjectCustomFieldSections::DeleteService.new(user: current_user, model: @project_custom_field_section).call
if call.success?
update_header_via_turbo_stream(allow_custom_field_creation: allow_custom_field_creation?)
update_sections_via_turbo_stream(project_custom_field_sections: ProjectCustomFieldSection.all)
else
# TODO: show error message
@@ -95,6 +96,7 @@ module Admin::Settings
)
if call.success?
update_header_via_turbo_stream(allow_custom_field_creation: allow_custom_field_creation?)
update_sections_via_turbo_stream(project_custom_field_sections: ProjectCustomFieldSection.all)
else
# TODO: show error message
@@ -112,6 +114,10 @@ module Admin::Settings
@project_custom_field_section = ProjectCustomFieldSection.find(params[:id])
end
def allow_custom_field_creation?
ProjectCustomFieldSection.any?
end
def project_custom_field_section_params
params.require(:project_custom_field_section).permit(:name)
end
@@ -48,6 +48,8 @@ module Admin::Settings
# rubocop:enable Rails/LexicallyScopedActionFilter
def index
@allow_custom_field_creation = @project_custom_field_sections.any?
respond_to :html
end
@@ -33,9 +33,11 @@ module Admin
extend ActiveSupport::Concern
included do
def update_header_via_turbo_stream
def update_header_via_turbo_stream(allow_custom_field_creation:)
update_via_turbo_stream(
component: ::Settings::ProjectCustomFields::HeaderComponent.new
component: ::Settings::ProjectCustomFields::HeaderComponent.new(
allow_custom_field_creation:
)
)
end
+5 -1
View File
@@ -59,7 +59,11 @@ class TypesController < ApplicationController
end
def create
additional_params = { copy_workflow_from: params.dig(:type, :copy_workflow_from) }
additional_params = {}
if (value = params.dig(:type, :copy_workflow_from))
additional_params[:copy_workflow_from] = value
end
service_call = WorkPackageTypes::CreateService
.new(user: current_user)
.call(permitted_type_params.merge(additional_params))
@@ -0,0 +1,91 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class PdfExportTemplateController < ApplicationController
include OpTurbo::ComponentStream
before_action :require_admin
before_action :find_type, only: %i[toggle drop enable_all disable_all]
before_action :find_template, only: %i[toggle drop]
def enable_all
return render_404_turbo_stream if @type.nil?
@type.pdf_export_templates.enable_all
@type.save!
respond_section_with_turbo_streams
end
def disable_all
return render_404_turbo_stream if @type.nil?
@type.pdf_export_templates.disable_all
@type.save!
respond_section_with_turbo_streams
end
def toggle
return render_404_turbo_stream if @template.nil?
@type.pdf_export_templates.toggle(@template.id)
@type.save!
respond_with_turbo_streams
end
def drop
return render_404_turbo_stream if @template.nil?
@type.pdf_export_templates.move(@template.id, params[:position].to_i - 1) # drop index starts at 1
@type.save!
respond_to_with_turbo_streams
end
protected
def respond_section_with_turbo_streams
replace_via_turbo_stream(
component: ::WorkPackageTypes::ExportTemplateListComponent.new(type: @type)
)
respond_to_with_turbo_streams
end
def render_404_turbo_stream
render_error_flash_message_via_turbo_stream(message: t(:notice_file_not_found))
end
def find_type
@type = ::Type.find(params[:type_id])
end
def find_template
@template = @type.pdf_export_templates.find(params[:id])
end
end
end
@@ -0,0 +1,78 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class ProjectsTabController < ApplicationController
layout "admin"
before_action :require_admin
before_action :find_type
before_action :load_projects, only: :edit
current_menu_item [:edit] do
:types
end
def edit; end
def update
result = UpdateService.new(user: current_user, model: @type, contract_class: UpdateProjectsContract)
.call(permitted_project_params)
if result.success?
redirect_to edit_type_projects_path(type_id: @type.id), notice: I18n.t(:notice_successful_update)
else
flash_error(result)
load_projects
render :edit, status: :unprocessable_entity
end
end
private
def flash_error(result)
flash.now[:error] = result.errors.messages_for(:project_ids).to_sentence
end
def load_projects
@projects = Project.all
end
def find_type
@type = ::Type.find(params[:type_id])
end
def permitted_project_params
# TODO: once the input is correctly delivered just return: params.expect(type: [:project_ids])
{ project_ids: JSON.parse(params.expect(type: [:project_ids])[:project_ids]) }
end
end
end
@@ -0,0 +1,91 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class SubjectConfigurationTabController < ApplicationController
layout "admin"
before_action :require_admin
before_action :find_type, only: %i[update_subject_configuration]
def update_subject_configuration
form_params = params.expect(
work_package_types_forms_subject_configuration_form_model: %i[subject_configuration pattern]
).to_h
UpdateTypeService.new(@type, current_user)
.call({ patterns: pattern_collection_update(form_params) }) do |call|
call.on_success do
redirect_to tab_path, notice: I18n.t(:notice_successful_update)
end
call.on_failure do
params[:tab] = "subject_configuration"
render template: "types/edit", status: :unprocessable_entity
end
end
end
private
def find_type
@type = ::Type.find(params[:id])
end
def tab_path = edit_tab_type_path(id: @type.id, tab: :subject_configuration)
def pattern_collection_update(form_params)
patterns = @type.patterns.to_h.symbolize_keys
subject_pattern =
case form_params
in { subject_configuration: "generated", pattern: String => blueprint }
{ subject: { blueprint:, enabled: true } }
in { subject_configuration: "manual", pattern: String => blueprint }
if blueprint.empty?
# Submitting the form with an empty blueprint and manual subject configuration will
# remove the subject pattern from the collection
nil
else
{ subject: { blueprint:, enabled: false } }
end
else
nil
end
if subject_pattern.nil?
patterns.delete(:subject)
patterns
else
patterns.merge(subject_pattern)
end
end
end
end
@@ -111,7 +111,6 @@ class WorkPackages::ActivitiesTabController < ApplicationController
call = create_journal_service_call
if call.success? && call.result
claim_journal_attachments_for(call.result)
set_last_server_timestamp_to_headers
handle_successful_create_call(call)
else
@@ -129,7 +128,6 @@ class WorkPackages::ActivitiesTabController < ApplicationController
call = update_journal_service_call
if call.success? && call.result
claim_journal_attachments_for(call.result)
update_item_show_component(journal: call.result, grouped_emoji_reactions: grouped_emoji_reactions_for_journal)
else
handle_failed_create_or_update_call(call)
@@ -324,12 +322,6 @@ class WorkPackages::ActivitiesTabController < ApplicationController
Journals::UpdateService.new(model: @journal, user: User.current).call(notes:)
end
def claim_journal_attachments_for(journal)
WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService
.new(user: User.current, model: journal)
.call
end
def generate_time_based_update_streams(last_update_timestamp)
journals = @work_package
.journals
@@ -126,7 +126,7 @@ class WorkPackages::MovesController < ApplicationController
hierarchies = WorkPackageHierarchy
.includes(:ancestor)
.where(ancestor_id: @work_packages.select(:id))
Type.where(id: hierarchies.map { _1.ancestor.type_id })
Type.where(id: hierarchies.map { it.ancestor.type_id })
.select("distinct id")
.pluck(:id)
.difference(@types.pluck(:id))
@@ -142,7 +142,7 @@ class WorkPackages::ProgressController < ApplicationController
"remaining_hours_touched",
"done_ratio_touched",
"status_id_touched")
.transform_values { _1 == "true" }
.transform_values { it == "true" }
.permit!
end
@@ -153,7 +153,7 @@ class WorkPackages::ProgressController < ApplicationController
end
def allowed_touched_params
allowed_params.filter { touched?(_1) }
allowed_params.filter { touched?(it) }
end
def allowed_params
@@ -109,7 +109,8 @@ class WorkPackages::RemindersController < ApplicationController
def reminder_chosen_time(reminder)
OpPrimer::RelativeTimeComponent.new(
datetime: in_user_zone(reminder.remind_at)
datetime: in_user_zone(reminder.remind_at),
month: :long
).render_in(view_context)
end
@@ -1,93 +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.
#++
module WorkPackages
module Types
class PdfExportTemplateController < ApplicationController
include OpTurbo::ComponentStream
before_action :require_admin
before_action :find_type, only: %i[toggle drop enable_all disable_all]
before_action :find_template, only: %i[toggle drop]
def enable_all
return render_404_turbo_stream if @type.nil?
@type.pdf_export_templates.enable_all
@type.save!
respond_section_with_turbo_streams
end
def disable_all
return render_404_turbo_stream if @type.nil?
@type.pdf_export_templates.disable_all
@type.save!
respond_section_with_turbo_streams
end
def toggle
return render_404_turbo_stream if @template.nil?
@type.pdf_export_templates.toggle(@template.id)
@type.save!
respond_with_turbo_streams
end
def drop
return render_404_turbo_stream if @template.nil?
@type.pdf_export_templates.move(@template.id, params[:position].to_i - 1) # drop index starts at 1
@type.save!
respond_to_with_turbo_streams
end
protected
def respond_section_with_turbo_streams
replace_via_turbo_stream(
component: ::WorkPackages::Types::ExportTemplateListComponent.new(type: @type)
)
respond_to_with_turbo_streams
end
def render_404_turbo_stream
render_error_flash_message_via_turbo_stream(message: t(:notice_file_not_found))
end
def find_type
@type = ::Type.find(params[:type_id])
end
def find_template
@template = @type.pdf_export_templates.find(params[:id])
end
end
end
end
@@ -1,93 +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.
#++
module WorkPackages
module Types
class SubjectConfigurationTabController < ApplicationController
layout "admin"
before_action :require_admin
before_action :find_type, only: %i[update_subject_configuration]
def update_subject_configuration
form_params = params.require(:types_forms_subject_configuration_form_model)
.permit(:subject_configuration, :pattern)
.to_h
UpdateTypeService.new(@type, current_user)
.call({ patterns: pattern_collection_update(form_params) }) do |call|
call.on_success do
redirect_to tab_path, notice: I18n.t(:notice_successful_update)
end
call.on_failure do
params[:tab] = "subject_configuration"
render template: "types/edit", status: :unprocessable_entity
end
end
end
private
def find_type
@type = ::Type.find(params[:id])
end
def tab_path = edit_tab_type_path(id: @type.id, tab: :subject_configuration)
def pattern_collection_update(form_params)
patterns = @type.patterns.to_h.symbolize_keys
subject_pattern =
case form_params
in { subject_configuration: "generated", pattern: String => blueprint }
{ subject: { blueprint:, enabled: true } }
in { subject_configuration: "manual", pattern: String => blueprint }
if blueprint.empty?
# Submitting the form with an empty blueprint and manual subject configuration will
# remove the subject pattern from the collection
nil
else
{ subject: { blueprint:, enabled: false } }
end
else
nil
end
if subject_pattern.nil?
patterns.delete(:subject)
patterns
else
patterns.merge(subject_pattern)
end
end
end
end
end
@@ -0,0 +1,117 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class SettingsForm < ApplicationForm
form do |settings_form|
settings_form.text_field(
name: :name,
label: label(:name),
placeholder: I18n.t(:label_name),
required: true,
disabled: model.is_standard?
)
settings_form.color_select_list(
name: :color_id,
label: Color.model_name.human,
input_width: :medium,
caption: I18n.t("types.edit.settings.type_color_text")
)
if show_work_flow_copy?
settings_form.select_list(
name: :copy_workflow_from,
input_width: :medium,
label: I18n.t(:label_copy_workflow_from),
include_blank: true,
validation_message: validation_message_for(:copy_workflow_from)
) do |other_types|
work_package_types.each do |type|
other_types.option(
value: type.id,
label: type.name,
selected: type.id == prefilled_copy_workflow_from
)
end
end
end
settings_form.rich_text_area(
name: :description,
label: label(:description),
rich_text_options: { showAttachments: false }
)
settings_form.check_box(
name: :is_milestone,
label: label(:is_milestone)
)
settings_form.check_box(
name: :is_in_roadmap,
label: label(:is_in_roadmap)
)
settings_form.check_box(
name: :is_default,
label: label(:is_default)
)
settings_form.submit(
name: :submit,
label: I18n.t(:button_save),
scheme: :primary
)
end
private
def label(attribute)
model.class.human_attribute_name(attribute)
end
def show_work_flow_copy?
model.new_record?
end
def work_package_types
Type.all
end
def validation_message_for(attribute)
model.errors.messages_for(attribute).to_sentence.presence
end
def prefilled_copy_workflow_from
@builder.options[:copy_workflow_from]
end
end
end
@@ -0,0 +1,99 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module WorkPackageTypes
class SubjectConfigurationForm < ApplicationForm
form do |subject_form|
subject_form.radio_button_group(name: :subject_configuration) do |group|
group.radio_button(
value: :manual,
checked: subject_configuration_manual?,
label: I18n.t("types.edit.subject_configuration.manually_editable_subjects.label"),
caption: I18n.t("types.edit.subject_configuration.manually_editable_subjects.caption"),
data: { action: "admin--subject-configuration#hidePatternInput" }
)
group.radio_button(
value: :generated,
checked: !subject_configuration_manual?,
label: I18n.t("types.edit.subject_configuration.automatically_generated_subjects.label"),
caption: I18n.t("types.edit.subject_configuration.automatically_generated_subjects.caption"),
disabled: !enterprise? && subject_configuration_manual?,
data: { action: "admin--subject-configuration#showPatternInput" }
)
end
subject_form.group(data: { "admin--subject-configuration-target": "patternInput" }) do |toggleable_group|
toggleable_group.pattern_input(
name: :pattern,
value: model.pattern,
suggestions: model.suggestions,
label: I18n.t("types.edit.subject_configuration.pattern.label"),
caption: pattern_input_caption,
required: true,
validation_message: validation_message_for(:patterns)
)
end
subject_form.submit(
name: :submit,
label: I18n.t(:button_save),
scheme: :primary
)
end
private
def subject_configuration_manual?
model.subject_configuration == :manual
end
def enterprise?
EnterpriseToken.allows_to?(:work_package_subject_generation)
end
def validation_message_for(attribute)
model.validation_errors.messages_for(attribute).to_sentence.presence
end
def pattern_input_caption
I18n.t(
"types.edit.subject_configuration.pattern.caption_with_supported_attributes_link",
link: make_link(
::OpenProject::Static::Links.url_for(:enterprise_features, :work_package_subject_generation),
I18n.t("types.edit.subject_configuration.pattern.supported_attributes_link")
)
).html_safe
end
def make_link(href, link_text)
render(Primer::Beta::Link.new(href:, target: "_blank")) { link_text }
end
end
end
+1 -1
View File
@@ -173,7 +173,7 @@ class WorkPackages::ProgressForm < ApplicationForm
def field_value(name)
errors = @work_package.errors.where(name)
if (user_value = errors.map { |error| error.options[:value] }.find { !_1.nil? })
if (user_value = errors.map { |error| error.options[:value] }.find { !it.nil? })
user_value
elsif name == :done_ratio
as_percent(@work_package.public_send(name))
@@ -1,114 +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.
#++
module WorkPackages
module Types
class SettingsForm < ApplicationForm
form do |settings_form|
settings_form.text_field(
name: :name,
label: label(:name),
placeholder: I18n.t(:label_name),
required: true,
disabled: model.is_standard?
)
settings_form.color_select_list(
name: :color_id,
label: Color.model_name.human,
input_width: :medium,
caption: I18n.t("types.edit.settings.type_color_text")
)
if show_work_flow_copy?
settings_form.select_list(
name: :copy_workflow_from,
input_width: :medium,
label: I18n.t(:label_copy_workflow_from),
include_blank: true
) do |other_types|
work_package_types.each do |type|
other_types.option(
value: type.id,
label: type.name,
selected: type.id == prefilled_copy_workflow_from
)
end
end
end
settings_form.rich_text_area(
name: :description,
label: label(:description),
rich_text_options: { showAttachments: false }
)
settings_form.check_box(
name: :is_milestone,
label: label(:is_milestone)
)
settings_form.check_box(
name: :is_in_roadmap,
label: label(:is_in_roadmap)
)
settings_form.check_box(
name: :is_default,
label: label(:is_default)
)
settings_form.submit(
name: :submit,
label: I18n.t(:button_save),
scheme: :primary
)
end
private
def label(attribute)
model.class.human_attribute_name(attribute)
end
def show_work_flow_copy?
model.new_record?
end
def work_package_types
Type.all
end
def prefilled_copy_workflow_from
@builder.options[:copy_workflow_from]
end
end
end
end
@@ -1,101 +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.
#++
module WorkPackages
module Types
class SubjectConfigurationForm < ApplicationForm
form do |subject_form|
subject_form.radio_button_group(name: :subject_configuration) do |group|
group.radio_button(
value: :manual,
checked: subject_configuration_manual?,
label: I18n.t("types.edit.subject_configuration.manually_editable_subjects.label"),
caption: I18n.t("types.edit.subject_configuration.manually_editable_subjects.caption"),
data: { action: "admin--subject-configuration#hidePatternInput" }
)
group.radio_button(
value: :generated,
checked: !subject_configuration_manual?,
label: I18n.t("types.edit.subject_configuration.automatically_generated_subjects.label"),
caption: I18n.t("types.edit.subject_configuration.automatically_generated_subjects.caption"),
disabled: !enterprise? && subject_configuration_manual?,
data: { action: "admin--subject-configuration#showPatternInput" }
)
end
subject_form.group(data: { "admin--subject-configuration-target": "patternInput" }) do |toggleable_group|
toggleable_group.pattern_input(
name: :pattern,
value: model.pattern,
suggestions: model.suggestions,
label: I18n.t("types.edit.subject_configuration.pattern.label"),
caption: pattern_input_caption,
required: true,
validation_message: validation_message_for(:patterns)
)
end
subject_form.submit(
name: :submit,
label: I18n.t(:button_save),
scheme: :primary
)
end
private
def subject_configuration_manual?
model.subject_configuration == :manual
end
def enterprise?
EnterpriseToken.allows_to?(:work_package_subject_generation)
end
def validation_message_for(attribute)
model.validation_errors.messages_for(attribute).to_sentence.presence
end
def pattern_input_caption
I18n.t(
"types.edit.subject_configuration.pattern.caption_with_supported_attributes_link",
link: make_link(
::OpenProject::Static::Links.url_for(:enterprise_features, :work_package_subject_generation),
I18n.t("types.edit.subject_configuration.pattern.supported_attributes_link")
)
).html_safe
end
def make_link(href, link_text)
render(Primer::Beta::Link.new(href:, target: "_blank")) { link_text }
end
end
end
end
+1 -1
View File
@@ -30,7 +30,7 @@ module BreadcrumbHelper
def nested_breadcrumb_element(section_header, title)
output = "".html_safe
output << "#{section_header}: "
output << content_tag(:strong, title)
output << content_tag(:b, title)
output
end
+5 -5
View File
@@ -36,7 +36,7 @@ module ::TypesHelper
name: "settings",
path: edit_tab_type_path(id: @type.id, tab: :settings),
label: I18n.t("types.edit.settings.tab"),
view_component: WorkPackages::Types::SettingsComponent
view_component: WorkPackageTypes::SettingsComponent
},
{
name: "form_configuration",
@@ -48,20 +48,20 @@ module ::TypesHelper
name: "subject_configuration",
path: edit_tab_type_path(id: @type.id, tab: :subject_configuration),
label: I18n.t("types.edit.subject_configuration.tab"),
view_component: WorkPackages::Types::SubjectConfigurationComponent,
view_component: WorkPackageTypes::SubjectConfigurationComponent,
enterprise_feature: :work_package_subject_generation
},
{
name: "projects",
partial: "types/form/projects",
path: edit_tab_type_path(id: @type.id, tab: :projects),
path: edit_type_projects_path(type_id: @type.id),
view_component: WorkPackageTypes::ProjectsComponent,
label: I18n.t("types.edit.projects.tab")
},
{
name: "export_configuration",
path: edit_tab_type_path(id: @type.id, tab: :export_configuration),
label: I18n.t("types.edit.export_configuration.tab"),
view_component: WorkPackages::Types::ExportConfigurationComponent
view_component: WorkPackageTypes::ExportConfigurationComponent
}
]
end
+3 -1
View File
@@ -30,7 +30,9 @@ class AttributeHelpText < ApplicationRecord
acts_as_attachable viewable_by_all_users: true
def self.cached(user)
OpenProject::Cache.fetch([name, user]) { visible(user).select(:id, :attribute_name).index_by(&:attribute_name) }
RequestStore.fetch(name) do
visible(user).select(:id, :attribute_name).index_by(&:attribute_name)
end
end
def self.for(model)
+7 -3
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -75,9 +77,7 @@ class CustomActions::Actions::Base
end
end
def key
self.class.key
end
delegate :key, to: :class
def required?
false
@@ -98,6 +98,10 @@ class CustomActions::Actions::Base
private
def deconstruct_keys(*)
{ type:, custom_field_based: respond_to?(:custom_field) }
end
def validate_value_required(errors)
if required? && values.empty?
errors.add :actions,
+7 -3
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -30,9 +32,11 @@ class CustomValue::DateStrategy < CustomValue::FormatStrategy
include Redmine::I18n
def typed_value
if value.present?
Date.iso8601(value)
end
return if value.blank?
Date.iso8601(value)
rescue Date::Error
nil
end
def formatted_value
+2
View File
@@ -45,5 +45,7 @@ class CustomValue::LinkStrategy < CustomValue::FormatStrategy
def parsed_url(val)
Addressable::URI.heuristic_parse(val, scheme: "http")
rescue Addressable::URI::InvalidURIError
nil
end
end
+19 -4
View File
@@ -45,8 +45,8 @@ class EnterpriseToken < ApplicationRecord
active_tokens.reject(&:trial?)
end
def active_trial_tokens
active_tokens.select(&:trial?)
def active_trial_token
active_tokens.find(&:trial?)
end
def table_exists?
@@ -61,6 +61,10 @@ class EnterpriseToken < ApplicationRecord
active_tokens.any?
end
def trial_only?
active_non_trial_tokens.empty? && active_trial_token.present?
end
def available_features
active_tokens.map(&:available_features).flatten.uniq
end
@@ -84,8 +88,8 @@ class EnterpriseToken < ApplicationRecord
def user_limit
if active_non_trial_tokens.any?
get_user_limit_of(active_non_trial_tokens)
else
get_user_limit_of(active_trial_tokens)
elsif active_trial_token
get_user_limit_of([active_trial_token])
end
end
@@ -118,6 +122,7 @@ class EnterpriseToken < ApplicationRecord
uniqueness: { message: I18n.t("activerecord.errors.models.enterprise_token.already_added") }
validate :valid_token_object
validate :valid_domain
validate :one_trial_token
before_validation :strip_encoded_token
before_save :extract_validity_from_token
@@ -218,6 +223,10 @@ class EnterpriseToken < ApplicationRecord
[expires_at || FAR_FUTURE_DATE, starts_at || FAR_FUTURE_DATE]
end
def days_left
(expires_at.to_date - Time.zone.today).to_i
end
private
def strip_encoded_token
@@ -239,6 +248,12 @@ class EnterpriseToken < ApplicationRecord
errors.add :domain, :invalid if invalid_domain?
end
def one_trial_token
if self.class.active_trial_token.present?
errors.add :base, :only_one_trial
end
end
def extract_validity_from_token
return unless token_object
@@ -35,6 +35,13 @@ module Exports
export_format == :pdf && attribute.start_with?("cf_")
end
def format_value(value, options)
# avoid the value transformed in to a string by the super method
return value if value.is_a?(::Exports::Formatters::LinkFormatter)
super
end
##
# Print the value meant for PDF export.
#
@@ -44,6 +51,8 @@ module Exports
if custom_field.field_format == "bool"
value = object.typed_custom_value_for(custom_field)
value ? I18n.t(:general_text_Yes) : I18n.t(:general_text_No)
elsif custom_field.field_format == "link"
LinkFormatter.new(object, custom_field)
else
super
end
@@ -38,14 +38,10 @@ module Exports
def format_hierarchy_item_for_export(item_value)
item = ::CustomField::Hierarchy::Item.find_by(id: item_value.to_s)
return "#{item_value} #{I18n.t(:label_not_found)}" if item.nil?
return "#{item_value} #{I18n.t(:label_not_found)}" unless item
item.ancestry_path
end
def hierarchy_item_service
::CustomFields::Hierarchy::HierarchicalItemService.new
end
end
end
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
# -- copyright
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
@@ -26,33 +26,21 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
#++
module WorkPackages
module Types
class PatternInput < Primer::Forms::BaseComponent
prepend Primer::OpenProject::Forms::WrappedInput
delegate :name, to: :@input
def initialize(input:, value:, suggestions:)
super()
@input = input
@value = value
@suggestions = suggestions
module Exports
module Formatters
class LinkFormatter
def initialize(object, custom_field)
@href = object.custom_value_for(custom_field).to_s
end
def suggestions_for_stimulus
@suggestions_for_stimulus ||= @suggestions.to_json
def to_html
ApplicationController.helpers.link_to(@href, @href)
end
def suggestions_list_component
@suggestions_list_component ||= Primer::Alpha::ActionList.new(
role: :list,
scheme: :inset,
ml: 0,
"data-pattern-input-target": "suggestions"
)
def to_s
@href
end
end
end
+1 -1
View File
@@ -63,7 +63,7 @@ class Members::RolesDiff
def user_previous_member_roles_ids
Set.new(user_member.member_roles
.reject { group_member.member_roles.map(&:id).include?(_1.inherited_from) }
.reject { group_member.member_roles.map(&:id).include?(it.inherited_from) }
.map(&:role_id).uniq)
end
+1 -1
View File
@@ -130,7 +130,7 @@ class Principal < ApplicationRecord
def self.columns_for_name(formatter = nil)
raise NotImplementedError, "Redefine in subclass" unless self == Principal
[User, Group, PlaceholderUser].map { _1.columns_for_name(formatter) }.inject(:|)
[User, Group, PlaceholderUser].map { it.columns_for_name(formatter) }.inject(:|)
end
# Select columns for formatting the user's name.
@@ -34,6 +34,6 @@ class Queries::Projects::Selects::Default < Queries::Selects::Base
end
def self.all_available
KEYS.map { new(_1) }
KEYS.map { new(it) }
end
end
+1 -1
View File
@@ -36,7 +36,7 @@ class Type < ApplicationRecord
include ::Scopes::Scoped
attribute :patterns, Types::Patterns::CollectionType.new
attribute :patterns, WorkPackageTypes::Patterns::CollectionType.new
store_attribute :pdf_export_templates_config, :export_templates_disabled, :json
store_attribute :pdf_export_templates_config, :export_templates_order, :json
@@ -162,6 +162,9 @@ module WorkPackage::Exports
def self.format_attribute_value(ar_name, model, obj)
formatter = Exports::Register.formatter_for(model, ar_name, :pdf)
value = formatter.format(obj)
# do NOT escape a tag for custom field link
return value.to_html if value.is_a?(::Exports::Formatters::LinkFormatter)
# important NOT to return empty string as this could change meaning of markdown
# e.g. **to_be_replaced** could be rendered as **** (horizontal line and a *)
value.blank? ? " " : escape_tags(value)
@@ -48,10 +48,6 @@ module WorkPackage::Exports
[WorkPackagesLinkHandler]
end
def self.html_replacement?
true
end
# Faster inclusion check before the full regex is being applied
def self.applicable?(content)
/#\d/.match(content)
@@ -361,4 +361,17 @@ module WorkPackage::PDFExport::Common::Common
end
end
end
def get_cf_link_cell(custom_url)
make_link_href_cell(custom_url.to_s, custom_url.to_s)
end
def get_value_cell_by_column(work_package, column_name, format_subject)
value = get_column_value(work_package, column_name)
return get_cf_link_cell(value) if value.is_a?(::Exports::Formatters::LinkFormatter)
return get_id_column_cell(work_package, value) if column_name == :id
return get_subject_column_cell(work_package, value) if format_subject && column_name == :subject
escape_tags(value)
end
end
@@ -70,17 +70,13 @@ module WorkPackage::PDFExport::Common::Macro
end
def apply_macros_node_text(node, context)
formatted, applied_macros = apply_macro_text(node.string_content, context)
formatted = apply_macro_text(node.string_content, context)
return if formatted == node.string_content
if has_html_macro?(applied_macros)
fragment = Markly::Node.new(:inline_html)
fragment.string_content = formatted
node.insert_before(fragment)
node.delete
else
node.string_content = formatted
end
fragment = Markly::Node.new(:inline_html)
fragment.string_content = formatted
node.insert_before(fragment)
node.delete
end
def apply_macros_node_html(node, context)
@@ -100,7 +96,7 @@ module WorkPackage::PDFExport::Common::Macro
macro.process_match(Regexp.last_match, matched_string, context)
end
end
[text, applicable_macros]
text
end
def apply_macro_html(html, context)
@@ -111,20 +107,10 @@ module WorkPackage::PDFExport::Common::Macro
doc.to_html
end
def has_html_macro?(macros)
macros.any? { |macro| macro.respond_to?(:html_replacement?) && macro.html_replacement? }
end
def apply_macro_html_node(node, context)
if node.text?
formatted, applied_macros = apply_macro_text(node.content, context)
if formatted != node.content
if has_html_macro?(applied_macros)
node.replace(formatted)
else
node.content = formatted
end
end
formatted = apply_macro_text(node.content, context)
node.replace(formatted) if formatted != node.content
elsif PREFORMATTED_BLOCKS.exclude?(node.name)
node.children.each { |child| apply_macro_html_node(child, context) }
end
@@ -173,6 +173,8 @@ module WorkPackage::PDFExport::Export::Markdown
end
def write_markdown!(markdown, styling_yml)
return if markdown.blank?
markdown_writer(styling_yml)
.draw_markdown(markdown, pdf, ->(src) {
with_images? ? attachment_image_filepath(src) : nil
@@ -81,4 +81,8 @@ module WorkPackage::PDFExport::Export::Report::Attributes
attribute_data[:value]
]
end
def get_column_value_cell(work_package, column_name)
get_value_cell_by_column(work_package, column_name, wants_report?)
end
end
@@ -242,10 +242,6 @@ module WorkPackage::PDFExport::Export::Wp::Attributes
end
def get_column_value_cell(work_package, column_name)
value = get_column_value(work_package, column_name)
return get_id_column_cell(work_package, value) if column_name == :id
return get_subject_column_cell(work_package, value) if column_name == :subject
escape_tags(value)
get_value_cell_by_column(work_package, column_name, true)
end
end
@@ -301,12 +301,4 @@ class WorkPackage::PDFExport::WorkPackageListToPdf < WorkPackage::Exports::Query
def get_total_sums
query.display_sums? ? (query.results.all_total_sums || {}) : {}
end
def get_column_value_cell(work_package, column_name)
value = get_column_value(work_package, column_name)
return get_id_column_cell(work_package, value) if column_name == :id
return get_subject_column_cell(work_package, value) if wants_report? && column_name == :subject
escape_tags(value)
end
end
@@ -28,7 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Types
module WorkPackageTypes
module Forms
class SubjectConfigurationFormModel
extend ActiveModel::Naming
@@ -28,7 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Types
module WorkPackageTypes
Pattern = Data.define(:blueprint, :enabled) do
def enabled? = !!enabled
@@ -28,7 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Types
module WorkPackageTypes
class PatternResolver
TOKEN_REGEX = /{{[0-9A-Za-z_]+}}/
ATTRIBUTE_PLACEHOLDER = "N/A"
@@ -23,12 +23,12 @@
#
# 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.
#++
module Types
module WorkPackageTypes
module Patterns
AttributeToken = Data.define(:key, :label_fn, :resolve_fn) do
def label_with_context
@@ -28,7 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Types
module WorkPackageTypes
module Patterns
Collection = Data.define(:patterns) do
extend Dry::Monads[:result]
@@ -45,7 +45,7 @@ module Types
end
def initialize(patterns:)
transformed = patterns.transform_values { Pattern.new(**_1) }.freeze
transformed = patterns.transform_values { Pattern.new(**it) }.freeze
super(patterns: transformed)
end
@@ -28,7 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Types
module WorkPackageTypes
module Patterns
class CollectionContract < Dry::Validation::Contract
config.messages.backend = :i18n
@@ -28,7 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Types
module WorkPackageTypes
module Patterns
class CollectionType < ActiveModel::Type::Value
def assert_valid_value(value)
@@ -28,7 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Types
module WorkPackageTypes
module Patterns
PatternToken = Data.define(:pattern, :key) do
private_class_method :new
@@ -28,7 +28,7 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module Types
module WorkPackageTypes
module Patterns
class TokenPropertyMapper
# rubocop:disable Layout/LineLength
+1 -1
View File
@@ -30,7 +30,7 @@ class CompositeSeeder < Seeder
seed_with(data_seeders)
if discovered_seeders.any?
print_status "Loading discovered seeders: #{discovered_seeders.map { seeder_name(_1) }.join(', ')}"
print_status "Loading discovered seeders: #{discovered_seeders.map { seeder_name(it) }.join(', ')}"
seed_with(discovered_seeders)
end
end
+2 -2
View File
@@ -65,8 +65,8 @@ module Source::Translate
def translatable_keys(hash)
hash.keys
.filter { translatable?(_1) }
.map { remove_translatable_prefix(_1) }
.filter { translatable?(it) }
.map { remove_translatable_prefix(it) }
end
def translate_value(value, i18n_key)
+39 -13
View File
@@ -46,21 +46,47 @@ class AddWorkPackageNoteService
def call(notes, send_notifications: nil, internal: false)
in_context(work_package, send_notifications:) do
work_package.add_journal(user:, notes:, internal:)
add_journal_to_work_package(notes, internal)
success, errors = save_journal_with_validation
success, errors = validate_and_yield(work_package, user) do
work_package.save_journals
end
journal = fetch_latest_journal if success
if success
# In test environment, because of the difference in the way of handling transactions,
# the journal needs to be actively loaded without SQL caching in place.
journal = Journal.connection.uncached do
work_package.journals.last
end
end
ServiceResult.new(success:, result: journal, errors:)
build_service_result(success, journal, errors)
end
end
private
def add_journal_to_work_package(notes, internal)
work_package.add_journal(user:, notes:, internal:)
end
def save_journal_with_validation
validate_and_yield(work_package, user) do
work_package.save_journals
end
end
def fetch_latest_journal
# In test environment, because of the difference in the way of handling transactions,
# the journal needs to be actively loaded without SQL caching in place.
Journal.connection.uncached do
work_package.journals.last
end
end
def build_service_result(success, result, errors)
ServiceResult.new(success:, result:, errors:).on_success { add_attachment_claims_to(it) }
end
def add_attachment_claims_to(service_result)
attachments_claims = claim_attachments_for(service_result.result)
service_result.add_dependent!(attachments_claims)
end
def claim_attachments_for(journal)
WorkPackages::ActivitiesTab::CommentAttachmentsClaims::ClaimsService
.new(user: User.current, model: journal)
.call
end
end
+2 -2
View File
@@ -36,8 +36,8 @@ module Attachments
# @param whitelist A custom whitelist to validate with, or empty to disable validation
#
# Warning: When passing an empty whitelist, this results in no validations on the content type taking place.
def self.bypass_whitelist(user:, whitelist: [])
new(user:, contract_options: { whitelist: whitelist.map(&:to_s) })
def self.bypass_allowlist(user:, allowlist: [])
new(user:, contract_options: { allowlist: allowlist.map(&:to_s) })
end
end
end
+2 -1
View File
@@ -30,8 +30,9 @@ class Attachments::DeleteService < BaseServices::Delete
include Attachments::TouchContainer
def call(params = {})
self.params = params
in_context(model.container || model) do
perform(params)
perform
end
end
@@ -0,0 +1,140 @@
# frozen_string_literal: true
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
module Attachments
class FinishDirectUploadService < BaseServices::BaseContracted
def initialize(user:, model:, contract_class: nil, contract_options: {})
self.model = model
super(user:, contract_class:, contract_options:)
end
protected
alias_method :attachment, :model
def service_context(send_notifications:, &)
# Overwriting the call to in_context to place the semaphore on the container and not on the attachment.
# Since the service will write a journal on the container, there should not be another
# process that modifies the container while this service is running.
in_context(attachment.container, send_notifications:, &)
end
def validate_params
super.tap do |call|
validate_local_file_exists(call)
end
end
def before_perform(_)
super.tap do
set_attachment_parameters
end
end
def persist(call)
super.tap do |service_result|
unless attachment.save
service_result.errors = attachment.errors
service_result.success = false
end
end
end
def after_perform(service_call)
super.tap do
journalize_container
attachment_created_event
schedule_jobs
end
end
def validate_local_file_exists(call)
unless local_file
call.errors.add(:base, "File for attachment #{attachment.filename} was not uploaded.")
call.success = false
end
end
def set_attachment_parameters
attachment.extend(OpenProject::ChangedBySystem)
attachment.change_by_system do
attachment.status = :uploaded
attachment.file = local_file
end
end
def schedule_jobs
attachment.extract_fulltext
end
def journalize_container
journable = attachment.container
return unless journable&.class&.journaled?
# Touching the journable will lead to the journal created next having its own timestamp.
# That timestamp will not adequately reflect the time the attachment was uploaded. This job
# right here might be executed way later than the time the attachment was uploaded. Ideally,
# the journals would be created bearing the time stamps of the attachment's created_at.
# This remains a TODO.
# But with the timestamp update in place as it is, at least the collapsing of aggregated journals
# from days before with the newly uploaded attachment is prevented.
touch_journable(journable)
Journals::CreateService
.new(journable, attachment.author)
.call
end
def touch_journable(journable)
# Not using touch here on purpose,
# as to avoid changing lock versions on the journables for this change
attributes = journable.send(:timestamp_attributes_for_update_in_model)
timestamps = attributes.index_with { Time.current }
journable.update_columns(timestamps) if timestamps.any?
end
def attachment_created_event
OpenProject::Notifications.send(
OpenProject::Events::ATTACHMENT_CREATED,
attachment:
)
end
def default_contract_class
::Attachments::CreateContract
end
def local_file
attachment&.diskfile
end
end
end
+1 -1
View File
@@ -39,7 +39,7 @@ module BaseServices
self.params = extract_options!(args).deep_symbolize_keys
run_callbacks(:call) do
perform(*args, **params)
perform(*args)
end
end

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