Merge branch 'release/17.4' into merge-release/17.3-20260512133150

This commit is contained in:
Klaus Zanders
2026-05-12 15:37:29 +02:00
committed by GitHub
2461 changed files with 76157 additions and 38976 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
https://github.com/heroku/heroku-buildpack-nodejs.git#v315
https://github.com/pkgr/heroku-buildpack-ruby.git#v327-1
https://github.com/pkgr/heroku-buildpack-ruby.git#v356-1
@@ -0,0 +1,112 @@
# 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.
#++
# Flags contracts where `attribute :project_id` is being introduced on a
# Base- or UpdateContract. Exposing project_id as a writable attribute allows
# moving a resource between projects, which must be gated on permissions in
# both the source and the destination project. Without that, a user can
# "steal" a resource into a project they control.
BASE_OR_UPDATE_CONTRACT_REGEX = %r{(?:^|/)(?:modules/[^/]+/)?app/contracts/.*?(base|update)_contract\.rb\z}
PROJECT_ID_ATTRIBUTE_REGEX = /^\+\s*attribute\s+:project_id\b/
def contracts_introducing_project_id_attribute
candidate_files = (git.modified_files + git.added_files).grep(BASE_OR_UPDATE_CONTRACT_REGEX)
candidate_files.select do |file|
diff = git.diff_for_file(file)
next false unless diff
diff.patch.to_s.lines.any? { |line| line.match?(PROJECT_ID_ATTRIBUTE_REGEX) }
end
end
flagged_files = contracts_introducing_project_id_attribute
if flagged_files.any?
message_lines = [
"**Attention developer & reviewer: `attribute :project_id` found in a Base- or UpdateContract:**",
""
]
flagged_files.each do |file|
message_lines << "- #{file}"
end
message_lines << <<~EOS
Allowing `project_id` to be written outside of the CreateContract means a resource can be moved between projects.
Without explicit permission checks in **both** the source and the destination project,
a user can move (and thus "steal") resources into a project they control.
**Please make sure the contract:**
1. Includes the `UnchangedProject` concern
2. Validates the `manage`-permission against the **source** project (using `with_unchanged_project_id`)
3. Validates the `manage`-permission against the **destination** project when `project_id_changed?`
**Example:**
```ruby
include UnchangedProject
MANAGE_PERMISSION = :manage_something
validate :validate_manage_allowed_in_source_project
validate :validate_manage_allowed_in_destination_project
private
def validate_manage_allowed_in_source_project
if model.new_record?
errors.add :base, :error_unauthorized unless user.allowed_in_project?(MANAGE_PERMISSION, model.project)
return
end
with_unchanged_project_id do
errors.add :base, :error_unauthorized unless user.allowed_in_project?(MANAGE_PERMISSION, model.project)
end
end
def validate_manage_allowed_in_destination_project
return if model.new_record?
return unless model.project_id_changed?
unless user.allowed_in_project?(MANAGE_PERMISSION, model.project)
errors.add :base, :error_unauthorized
end
end
```
If this contract already handles both checks, feel free to resolve this warning.
EOS
warn message_lines.join("\n")
end
+3
View File
@@ -21,7 +21,10 @@ jobs:
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
uses: contributor-assistant/github-action@v2.6.1
env:
# https://github.com/contributor-assistant/github-action?tab=readme-ov-file#environmental-variables
# Built-in GitHub token to make the API calls for interacting with GitHub. Does not need to be specified the secrets store.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Access token for repository where the signatures are stored (see below for remote-repository-name)
PERSONAL_ACCESS_TOKEN: ${{ secrets.OPENPROJECTCI_GH_LEGAL_TOKEN }}
with:
path-to-signatures: "contributor-license-agreement/signatures/version1.json"
+4 -2
View File
@@ -76,12 +76,14 @@ jobs:
crowdin_branch_name: ${{ steps.vars.outputs.crowdin_branch }}
# Dont create a PR for the updated translations
push_translations: false
user: auto
env:
OPENPROJECT_CROWDIN_PROJECT: ${{ secrets.OPENPROJECT_CROWDINV2_PROJECT }}
OPENPROJECT_CROWDIN_API_KEY: ${{ secrets.OPENPROJECT_CROWDINV2_API_KEY }}
- name: "Fix root key in Portuguese crowdin translation files"
run: |
script/i18n/fix_crowdin_pt_language_root_key
run: script/i18n/fix_crowdin_pt_language_root_key
- name: "Rewrite crowdin translation files using ruby yaml library"
run: script/i18n/rewrite_crowdin_yml_files
- name: "Commit translations"
env:
BRANCH: ${{ matrix.branch }}
+3 -3
View File
@@ -18,8 +18,8 @@ jobs:
if: github.repository == 'opf/openproject'
# References to release/X.Y and X.Y-rc are being
# updated from the devkit (UpdateWorkflows step) whenever a new release branch is created
uses: opf/openproject/.github/workflows/docker.yml@release/17.3
uses: opf/openproject/.github/workflows/docker.yml@release/17.4
with:
branch: release/17.3
tag: 17.3-rc
branch: release/17.4
tag: 17.4-rc
secrets: inherit
+1 -1
View File
@@ -130,7 +130,7 @@ jobs:
needs:
- setup
runs-on:
labels: "runs-on=${{ github.run_id }}/ssh=false/${{ matrix.runner }}"
labels: "runs-on=${{ github.run_id }}/ssh=false/${{ matrix.runner }}/volume=100g"
strategy:
matrix:
include:
+1 -1
View File
@@ -146,7 +146,7 @@ jobs:
- name: Deploy STAGE
# make sure to always use the latest release branch here
if: github.ref == 'refs/heads/release/17.3' && github.repository == 'opf/openproject'
if: github.ref == 'refs/heads/release/17.4' && github.repository == 'opf/openproject'
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4
with:
workflow: stage-deploy-shards.yml
+18
View File
@@ -169,6 +169,10 @@ Rails/ContentTag:
# dynamic finders cop clashes with capybara ID cop
Rails/DynamicFindBy:
Enabled: true
AllowedMethods:
- find_by_display_id
- find_by_display_id!
- find_by_semantic_identifier
Exclude:
- "spec/features/**/*.rb"
- "spec/support/**/*.rb"
@@ -186,6 +190,9 @@ Rails/FindEach:
- limit
- select
- lock
Exclude:
- "spec/**/*"
- "modules/**/spec/**/*"
# The http verbs in Rack::Test do not accept named parameters (params: params)
Rails/HttpPositionalArguments:
@@ -197,11 +204,13 @@ Rails/I18nLocaleAssignment:
Enabled: true
Exclude:
- "spec/**/*.rb"
- "modules/*/spec/**/*.rb"
Rails/I18nLocaleTexts:
Enabled: true
Exclude:
- "spec/**/*.rb"
- "modules/*/spec/**/*.rb"
# We have config.active_record.belongs_to_required_by_default = false ,
# which means, we do have to declare presence validators on belongs_to relations.
@@ -215,6 +224,9 @@ Rails/RequireDependency:
# Require save! to prevent saving without validation when saving outside of a condition.
Rails/SaveBang:
Enabled: true
Exclude:
- "spec/**/*"
- "modules/**/spec/**/*"
# There are valid cases in which to use methods like:
# * update_all
@@ -321,6 +333,7 @@ RSpec/SpecFilePathFormat:
CustomTransform:
OpenIDConnect: openid_connect
OAuthClients: oauth_clients
XWikiProviders: xwiki_providers
EnforcedInflector: active_support
IgnoreMethods: true
@@ -471,6 +484,11 @@ Style/Proc:
Style/RaiseArgs:
Enabled: false
Style/RescueModifier:
Exclude:
- "spec/**/*"
- "modules/**/spec/**/*"
Style/RegexpLiteral:
Enabled: false
+1 -1
View File
@@ -1 +1 @@
4.0.1
4.0.2
+1
View File
@@ -142,6 +142,7 @@ cd frontend && npm test && cd ..
- Keep controllers thin, models focused
- Document with [YARD](https://yardoc.org/)
- Write RSpec tests for all new features
- **Work package identifiers**: `WorkPackage.find("PROJ-42")` resolves semantic identifiers transparently. Use `find_by_display_id` only when input could legitimately be numeric OR semantic (controllers, URL-driven components, macro resolvers). Low-level code (queries, filters, services) should stick to `find_by(id:)` with primary keys. See `app/models/work_package/semantic_identifier/finder_methods.rb`.
### JavaScript/TypeScript
- **New development**: Use Hotwire (Turbo + Stimulus) with server-rendered HTML
+1
View File
@@ -29,3 +29,4 @@
#++
danger.import_dangerfile(path: ".github/dangerfiles/user_references/Dangerfile")
danger.import_dangerfile(path: ".github/dangerfiles/release_migrations/Dangerfile")
danger.import_dangerfile(path: ".github/dangerfiles/project_id_contract/Dangerfile")
+21 -21
View File
@@ -41,10 +41,10 @@ gem "activemodel-serializers-xml", "~> 1.0.1"
gem "activerecord-import", "~> 2.2.0"
gem "activerecord-session_store", "~> 2.2.0"
gem "ox"
gem "rails", "~> 8.1.2"
gem "rails", "~> 8.1.3"
gem "responders", "~> 3.2"
gem "ffi", "~> 1.15"
gem "ffi", "~> 1.17"
gem "connection_pool", "~> 3.0.2"
@@ -72,7 +72,7 @@ gem "awesome_nested_set", "~> 3.9.0"
gem "closure_tree", "~> 9.6.1"
gem "rubytree", "~> 2.2.0"
gem "addressable", "~> 2.8.9"
gem "addressable", "~> 2.9.0"
# Remove whitespace from model input
gem "auto_strip_attributes", "~> 2.5"
@@ -87,7 +87,7 @@ gem "htmldiff"
gem "stringex", "~> 2.8.5"
# CommonMark markdown parser with GFM extension
gem "commonmarker", "~> 2.7.0"
gem "commonmarker", "~> 2.8.0"
# HTML pipeline for transformations on text formatter output
# such as sanitization or additional features
@@ -123,11 +123,11 @@ gem "sys-filesystem", "~> 1.5.0", require: false
gem "bcrypt", "~> 3.1.22"
gem "multi_json", "~> 1.19.0"
gem "multi_json", "~> 1.20.0"
gem "oj", "~> 3.16.16"
gem "daemons"
gem "good_job", "~> 4.13.3" # update should be done manually in sync with saas-openproject version.
gem "good_job", "~> 4.14.2" # update should be done manually in sync with saas-openproject version.
gem "rack-protection", "~> 3.2.0"
@@ -161,7 +161,7 @@ gem "ttfunk", "~> 1.7.0" # remove after https://github.com/prawnpdf/prawn/issues
# prawn implicitly depends on matrix gem no longer in ruby core with 3.1
gem "matrix", "~> 0.4.3"
gem "mcp", "~> 0.8.0"
gem "mcp", "~> 0.10.0"
gem "meta-tags", "~> 2.23.0"
@@ -204,9 +204,9 @@ gem "carrierwave_direct", "~> 3.0.0"
gem "ssrf_filter", "~> 1.3"
gem "fog-aws"
gem "aws-sdk-core", "~> 3.241"
gem "aws-sdk-core", "~> 3.244"
# File upload via fog + screenshots on travis
gem "aws-sdk-s3", "~> 1.213"
gem "aws-sdk-s3", "~> 1.217"
gem "openproject-token", "~> 8.8.2"
@@ -227,7 +227,7 @@ gem "dry-validation"
gem "store_attribute", "~> 2.0"
# Appsignal integration
gem "appsignal", "~> 4.7", require: false
gem "appsignal", "~> 4.8", require: false
# Yabeda integration
gem "yabeda-activerecord"
@@ -236,11 +236,11 @@ gem "yabeda-puma-plugin"
gem "yabeda-rails"
# opentelemetry
gem "opentelemetry-exporter-otlp", "~> 0.32.0", require: false
gem "opentelemetry-exporter-otlp", "~> 0.33.0", require: false
gem "opentelemetry-instrumentation-all", "~> 0.91.0", require: false
gem "opentelemetry-sdk", "~> 1.10", require: false
gem "view_component", "~> 4.5.0"
gem "view_component", "~> 4.6.0"
# Lookbook
gem "lookbook", "2.3.14"
@@ -254,7 +254,7 @@ gem "factory_bot_rails", "~> 6.5.0", require: false
gem "turbo_power", "~> 0.7.0"
gem "turbo-rails", "~> 2.0.20"
gem "httpx", "~> 1.7.4"
gem "httpx", "~> 1.7.5"
# Brings actual deep-freezing to most ruby objects
gem "ice_nine"
@@ -266,7 +266,7 @@ group :test do
# Test prof provides factories from code
# and other niceties
gem "test-prof", "~> 1.5.0"
gem "test-prof", "~> 1.6.0"
gem "turbo_tests", github: "opf/turbo_tests", ref: "with-patches"
gem "rack_session_access"
@@ -334,9 +334,6 @@ group :development do
gem "spring-commands-rubocop"
gem "colored2"
# git hooks manager
gem "lefthook", require: false
end
group :development, :test do
@@ -356,6 +353,9 @@ group :development, :test do
# https://github.com/puma/puma/issues/2835#issuecomment-2302133927
gem "byebug"
# Unreleased fix of readline dependency of pry: https://github.com/pry/pry/pull/2366
# Once this gets released, the specific dev dependency on pry can be removed
gem "pry", github: "pry/pry", ref: "135640262879544c6bfecbf3e78511289bfe956c"
gem "pry-byebug", "~> 3.12.0", platforms: [:mri]
gem "pry-rails", "~> 0.3.6"
gem "pry-rescue", "~> 1.6.0"
@@ -387,7 +387,7 @@ end
gem "bootsnap", "~> 1.23.0", require: false
# API gems
gem "grape", "~> 3.1.1"
gem "grape", "~> 3.2.0"
gem "grape_logging", "~> 3.0.0"
gem "roar", "~> 1.2.0"
@@ -430,6 +430,6 @@ gemfiles.each do |file|
send(:eval_gemfile, file) if File.readable?(file)
end
gem "openproject-octicons", "~>19.32.0"
gem "openproject-octicons_helper", "~>19.32.0"
gem "openproject-primer_view_components", "~>0.84.1"
gem "openproject-octicons", "~>19.34.0"
gem "openproject-octicons_helper", "~>19.34.0"
gem "openproject-primer_view_components", "~>0.84.5"
+262 -263
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+1
View File
@@ -3,6 +3,7 @@
@import "op_primer/border_box_table_component"
@import "op_primer/full_page_prompt_component"
@import "op_primer/form_helpers"
@import "op_primer/inline_macro_component"
@import "open_project/common/attribute_component"
@import "open_project/common/attribute_help_text_component"
@import "open_project/common/attribute_help_text_caption_component"
@@ -0,0 +1,75 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
component_wrapper do
primer_form_with(
url: add_department_admin_departments_path,
method: :post,
scope: :group
) do |f|
cancel_path = if group
helpers.admin_department_path(group)
else
helpers.admin_departments_path
end
parent_id = group&.id
render_inline_form(f) do |form|
form.hidden(name: :parent_id, value: parent_id) if parent_id
form.group(layout: :horizontal) do |row|
row.text_field(
name: :lastname,
label: I18n.t("departments.add_department_form.name_label"),
visually_hide_label: true,
placeholder: I18n.t("departments.add_department_form.name_placeholder"),
required: true,
autofocus: true,
value: "",
autocomplete: "off"
)
row.button(
name: :cancel,
tag: :a,
label: I18n.t(:button_cancel),
scheme: :default,
href: cancel_path
)
row.submit(
name: :submit,
label: I18n.t(:button_add),
scheme: :primary
)
end
end
end
end
%>
@@ -0,0 +1,46 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Admin
module Departments
class AddDepartmentComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
attr_reader :group
def initialize(group:)
super()
@group = group
end
end
end
end
@@ -0,0 +1,77 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
component_wrapper do
primer_form_with(
url: add_user_admin_department_path(group),
method: :post
) do |f|
cancel_path = if group
helpers.admin_department_path(group)
else
helpers.admin_departments_path
end
autocompleter_filters = filters
render_inline_form(f) do |form|
form.group(layout: :horizontal) do |row|
row.autocompleter(
name: :user_id,
label: User.model_name.human,
visually_hide_label: true,
autocomplete_options: {
resource: "principals",
component: "opce-user-autocompleter",
url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals,
searchKey: "any_name_attribute",
focusDirectly: true,
multiple: false,
filters: autocompleter_filters,
inputName: "user_id"
}
)
row.button(
name: :cancel,
tag: :a,
label: I18n.t(:button_cancel),
scheme: :default,
href: cancel_path
)
row.submit(
name: :submit,
label: I18n.t(:button_add),
scheme: :primary
)
end
end
end
end
%>
@@ -0,0 +1,60 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Admin
module Departments
class AddUserComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
attr_reader :group
def initialize(group:)
super()
@group = group
end
def filters
filters = [
{ name: "type", operator: "=", values: %w[User] },
{ name: "status", operator: "=", values: [Principal.statuses[:active].to_s, Principal.statuses[:invited].to_s] }
]
existing_user_ids = group.user_ids.map(&:to_s)
if existing_user_ids.any?
filters << { name: "id", operator: "!", values: existing_user_ids }
end
filters
end
end
end
end
@@ -0,0 +1,54 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Admin
module Departments
class BlankslateComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
def call
render(Primer::Beta::Blankslate.new(border: false)) do |component|
component.with_visual_icon(icon: :people, size: :medium)
component.with_heading(tag: :h2) { t("departments.blankslate.heading") }
component.with_description { t("departments.blankslate.description") }
component.with_primary_action(
href: new_department_admin_departments_path,
scheme: :primary,
data: { turbo_frame: Admin::Departments::DetailComponent.wrapper_key }
) do |button|
button.with_leading_visual_icon(icon: :plus)
t("departments.blankslate.add_button")
end
end
end
end
end
end
@@ -27,40 +27,40 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<% html_title t(:label_backlogs) %>
<% content_controller "backlogs" %>
<% content_for :content_header do %>
<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_backlogs) }
header.with_breadcrumbs(
[{ href: project_overview_path(@project), text: @project.name },
t(:label_backlogs)]
)
end
%>
<%=
render(Primer::OpenProject::SubHeader.new) do |subheader|
subheader.with_action_button(
scheme: :primary,
leading_icon: :plus,
label: I18n.t(:label_version_new),
tag: :a,
href: new_project_version_path(@project)
) do
Version.human_model_name
<%=
render(
Primer::Alpha::Dialog.new(
id: DIALOG_ID,
title: I18n.t(:label_change_parent),
size: :medium_portrait
)
) do |dialog|
dialog.with_body do
primer_form_with(**form_arguments) do |form|
render(
Primer::OpenProject::FilterableTreeView.new(
form_arguments: { builder: form, name: :new_parent_id },
include_sub_items_check_box_arguments: { hidden: true },
filter_mode_control_arguments: { hidden: true }
)
) do |tree_view|
render_tree(tree_view)
end
end
end
%>
<% end %>
<% content_for :content_body do %>
<%= turbo_frame_tag :backlogs_container, refresh: :morph, src: backlogs_project_backlogs_path(@project), class: "op-backlogs-page" %>
<% end %>
<% content_for :content_body_right do %>
<%= render(split_view_instance) if render_work_package_split_view? %>
<% end %>
dialog.with_footer(show_divider: true) do
concat(render(Primer::Beta::Button.new(data: { close_dialog_id: DIALOG_ID })) { I18n.t(:button_cancel) })
concat(
render(
Primer::Beta::Button.new(
scheme: :primary,
form: FORM_ID,
type: :submit,
data: { turbo: true }
)
) { I18n.t(:button_select) }
)
end
end
%>
@@ -0,0 +1,105 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Admin
module Departments
class ChangeParentDialogComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
DIALOG_ID = "departments--change-parent-dialog"
FORM_ID = "departments--change-parent-form"
def initialize(department:, departments:)
super()
@department = department
@departments = departments
end
def form_arguments
{
id: FORM_ID,
url: change_parent_admin_department_path(@department),
method: :post
}
end
def render_tree(tree_view)
add_sub_tree(tree_view, nil)
end
private
def departments_by_parent_id
@departments_by_parent_id ||= @departments.group_by(&:parent_id)
end
def children_for(parent_id)
departments_by_parent_id[parent_id] || []
end
def add_sub_tree(tree_view, parent_id)
children_for(parent_id).each do |dept|
attrs = item_attributes(dept)
children = children_for(dept.id)
if children.any?
tree_view.with_sub_tree(**attrs) do |sub_tree|
add_sub_tree(sub_tree, dept.id)
end
else
tree_view.with_leaf(**attrs)
end
end
end
def item_attributes(dept)
{
label: dept.name,
value: dept.id,
select_variant: :single,
current: dept.id == @department.id,
disabled: disabled_ids.include?(dept.id),
expanded: dept.id == @department.parent_id
}
end
def disabled_ids
@disabled_ids ||= Set.new(
[@department.id, @department.parent_id].compact + descendant_ids(@department.id)
)
end
def descendant_ids(dept_id)
children_for(dept_id).flat_map { |child| [child.id] + descendant_ids(child.id) }
end
end
end
end
@@ -0,0 +1,125 @@
# 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 Admin
module Departments
class DepartmentRowComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
def initialize(department:)
super()
@department = department
end
def call
flex_layout(align_items: :center, justify_content: :space_between) do |row|
row.with_column do
render(Primer::Beta::Link.new(href: admin_department_path(@department))) { @department.name }
end
row.with_column do
render(Primer::Alpha::ActionMenu.new) do |menu|
menu.with_show_button(
icon: "kebab-horizontal",
scheme: :invisible,
"aria-label": I18n.t(:label_actions)
)
menu_items(menu)
end
end
end
end
private
def menu_items(menu)
with_item_group(menu) { edit_item(menu) }
with_item_group(menu) do
add_sub_department_item(menu)
add_user_item(menu)
end
with_item_group(menu) { change_parent_item(menu) }
with_item_group(menu) { delete_item(menu) }
end
def edit_item(menu)
menu.with_item(
label: I18n.t(:button_edit),
tag: :a,
href: edit_admin_department_path(@department)
) { it.with_leading_visual_icon(icon: :pencil) }
end
def add_sub_department_item(menu)
menu.with_item(
label: I18n.t("departments.context_menu.add_sub_department"),
tag: :a,
href: new_department_admin_departments_path(parent_id: @department.id),
content_arguments: { data: { turbo_frame: Admin::Departments::HierarchyLayoutComponent.wrapper_key } }
) { it.with_leading_visual_icon(icon: "op-arrow-in") }
end
def add_user_item(menu)
menu.with_item(
label: I18n.t("departments.context_menu.add_user"),
tag: :a,
href: new_user_admin_department_path(@department),
content_arguments: { data: { turbo_frame: Admin::Departments::HierarchyLayoutComponent.wrapper_key } }
) { it.with_leading_visual_icon(icon: "person-add") }
end
def change_parent_item(menu)
menu.with_item(
label: I18n.t(:label_change_parent),
tag: :a,
href: change_parent_admin_department_path(@department),
content_arguments: { data: { controller: "async-dialog" } }
) { it.with_leading_visual_icon(icon: "arrow-switch") }
end
def delete_item(menu)
menu.with_item(
label: I18n.t(:button_delete),
scheme: :danger,
tag: :a,
href: admin_department_path(@department),
content_arguments: {
data: {
turbo_confirm: I18n.t(:text_are_you_sure),
turbo_method: :delete,
turbo_frame: "_top"
}
}
) { it.with_leading_visual_icon(icon: :trash) }
end
end
end
end
@@ -0,0 +1,45 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Admin
module Departments
class DetailBlankslateComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
def call
render(Primer::Beta::Blankslate.new(border: false)) do |component|
component.with_visual_icon(icon: :people, size: :medium)
component.with_heading(tag: :h2) { t("departments.detail_blankslate.heading") }
component.with_description { t("departments.detail_blankslate.description") }
end
end
end
end
end
@@ -0,0 +1,94 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
component_wrapper(tag: "turbo-frame", target: "_top") do
render(Primer::Beta::BorderBox.new) do |box|
box.with_header do
flex_layout(align_items: :center, justify_content: :space_between) do |header|
header.with_column do
render(Primer::Beta::Breadcrumbs.new) do |loaf|
breadcrumb_items.each do |item|
loaf.with_item(href: item[:href] || "#", target: nil) { item[:label] }
end
end
end
if group
header.with_column do
render(
Primer::Beta::IconButton.new(
tag: :a,
href: edit_admin_department_path(group),
icon: :pencil,
scheme: :invisible,
"aria-label": I18n.t(:button_edit)
)
)
end
end
end
end
if show_global_empty_state? && !add_subgroup?
box.with_row do
render(Admin::Departments::BlankslateComponent.new)
end
elsif show_department_empty_state? && !add_user? && !add_subgroup?
box.with_row do
render(Admin::Departments::DetailBlankslateComponent.new)
end
else
child_groups.each do |child|
box.with_row do
render(Admin::Departments::DepartmentRowComponent.new(department: child))
end
end
if add_subgroup?
box.with_row do
render(Admin::Departments::AddDepartmentComponent.new(group:))
end
end
users.each do |user|
box.with_row do
render(Admin::Departments::UserRowComponent.new(user:, group:))
end
end
if add_user?
box.with_row do
render(Admin::Departments::AddUserComponent.new(group:))
end
end
end
end
end
%>
@@ -0,0 +1,92 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Admin
module Departments
class DetailComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
attr_reader :group, :ancestors, :child_groups
def initialize(group:, ancestors: [], child_groups: [], add_user: false, add_subgroup: false)
super(nil)
@group = group
@ancestors = ancestors
@child_groups = child_groups
@add_user = add_user
@add_subgroup = add_subgroup
end
def add_user?
@add_user
end
def add_subgroup?
@add_subgroup
end
def users
@users ||= group&.users || []
end
def breadcrumb_items
items = []
if group
items << { label: organization_name, href: admin_departments_path }
ancestors.each do |ancestor|
items << { label: ancestor.name, href: admin_department_path(ancestor) }
end
items << { label: group.name }
else
items << { label: organization_name }
end
items
end
def show_global_empty_state?
group.blank? && child_groups.empty?
end
def show_department_empty_state?
group.present? && child_groups.empty? && users.empty?
end
private
def organization_name
Setting.organization_name.presence || I18n.t("setting_organization_name")
end
end
end
end
@@ -26,36 +26,42 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
render Primer::OpenProject::PageHeader.new(classes: "workflows-page-header") do |header|
header.with_title { title }
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { @group.name }
header.with_breadcrumbs(breadcrumb_items)
header.with_action_button(
tag: :a,
mobile_icon: :copy,
mobile_label: t(:button_copy),
mobile_icon: :person,
mobile_label: t(:label_profile),
size: :medium,
href: copy_workflows_path,
aria: { label: I18n.t(:button_copy) },
title: I18n.t(:button_copy)
href: show_group_path(@group),
aria: { label: I18n.t(:label_profile) },
title: I18n.t(:label_profile)
) do |button|
button.with_leading_visual_icon(icon: :copy)
t(:button_copy)
button.with_leading_visual_icon(icon: :person)
t(:label_profile)
end
header.with_action_button(
tag: :a,
mobile_icon: :info,
mobile_label: t(:label_workflow_summary),
size: :medium,
href: summarized_workflows_path,
aria: { label: I18n.t(:label_workflow_summary) },
title: I18n.t(:label_workflow_summary)
) do |button|
button.with_leading_visual_icon(icon: :info)
t(:label_workflow_summary)
if @current_user.admin?
header.with_action_button(
tag: :a,
scheme: :danger,
mobile_icon: :trash,
mobile_label: t(:button_delete),
size: :medium,
href: group_path(@group),
aria: { label: I18n.t(:button_delete) },
data: {
turbo_confirm: t(:text_are_you_sure),
turbo_method: :delete
},
title: I18n.t(:button_delete)
) do |button|
button.with_leading_visual_icon(icon: :trash)
t(:button_delete)
end
end
helpers.render_tab_header_nav(header, @tabs)
@@ -28,24 +28,26 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module BurndownChartsHelper
def xaxis_labels(burndown)
# 14 entries (plus the axis label) have come along as the best value for a good optical result.
# Thus it is enough space between the entries.
entries_displayed = (burndown.days.length / 14.0).ceil
burndown.days.enum_for(:each_with_index).map do |d, i|
if (i % entries_displayed) == 0
["#{escape_javascript(::I18n.t('date.abbr_day_names')[d.wday % 7])} #{d.strftime('%d/%m')}"]
module Admin
module Departments
class EditPageHeaderComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
include ApplicationHelper
include TabsHelper
def initialize(group:, current_user:, tabs: nil)
super
@group = group
@tabs = tabs
@current_user = current_user
end
def breadcrumb_items
[{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_users_path, text: t(:label_user_and_permission) },
{ href: admin_departments_path, text: t(:label_departments) },
@group.name]
end
end
end
def dataseries(burndown)
burndown.series.map do |s|
{
label: I18n.t("burndown.#{s.first}"),
data: s.last.enum_for(:each)
}
end
end
end
@@ -0,0 +1,87 @@
<%#-- 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.
++#%>
<%# helpers.content_controller "admin--departments-page" %>
<%=
component_wrapper(tag: "turbo-frame", class: "admin-groups-tree-page--wrapper", refresh: :morph, data: { turbo_action: :advance }) do
render(Primer::Alpha::Layout.new(stacking_breakpoint: :md, overflow: :hidden, h: :full, classes: "admin-groups-tree-page")) do |content|
content.with_main(overflow: :auto) do
flex_layout do |main|
main.with_row do
render(Primer::OpenProject::SubHeader.new) do |subheader|
subheader.with_action_menu(leading_icon: :plus, trailing_icon: :"triangle-down", label: I18n.t(:button_add), button_arguments: { scheme: :primary, "aria-label": I18n.t(:button_add) }) do |menu|
if active_group
menu.with_item(
label: I18n.t("departments.add_user"),
tag: :a,
href: new_user_admin_department_path(active_group),
content_arguments: { data: { turbo_frame: Admin::Departments::DetailComponent.wrapper_key } }
)
end
menu.with_item(
label: I18n.t("departments.add_department"),
tag: :a,
href: new_department_admin_departments_path(parent_id: active_group&.id),
content_arguments: { data: { turbo_frame: Admin::Departments::DetailComponent.wrapper_key } }
)
end
end
end
main.with_row do
render(
Admin::Departments::DetailComponent.new(
group: active_group,
ancestors: ancestors_for(active_group),
child_groups: children_for(active_group&.id),
add_user: @add_user,
add_subgroup: @add_subgroup
)
)
end
end
end
content.with_sidebar(row_placement: :start, col_placement: :start, border: true, border_bottom: 0, p: 3, overflow: :auto, classes: "admin-groups-tree-page--sidebar") do
flex_layout do |sidebar|
sidebar.with_row(mb: 3) do
render(Admin::Departments::OrganizationNameComponent.new)
end
sidebar.with_row do
render(Primer::Alpha::TreeView.new(node_variant: :anchor)) do |tree_view|
render_group_tree(tree_view)
end
end
end
end
end
end
%>
@@ -0,0 +1,115 @@
# 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 Admin
module Departments
class HierarchyLayoutComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
attr_reader :groups, :active_group
def initialize(groups:, active_group: nil, add_user: false, add_subgroup: false)
super()
@groups = groups
@active_group = active_group
@add_user = add_user
@add_subgroup = add_subgroup
end
def render_group_tree(tree, parent_id: nil)
children_for(parent_id).each do |group|
node_attrs = {
label: group.name,
href: admin_department_path(group),
current: group == active_group
}
if children?(group)
tree.with_sub_tree(**node_attrs, expanded: expanded?(group)) do |sub_tree|
render_group_tree(sub_tree, parent_id: group.id)
end
else
tree.with_leaf(**node_attrs)
end
end
end
private
def children_by_parent_id
@children_by_parent_id ||= groups.group_by(&:parent_id)
end
def children_for(parent_id)
children_by_parent_id[parent_id] || []
end
def children?(group)
children_by_parent_id.key?(group.id)
end
def expanded?(group)
return false unless active_group
active_group == group || active_group_ancestor_ids.include?(group.id)
end
def active_group_ancestor_ids
@active_group_ancestor_ids ||= compute_ancestor_ids(active_group)
end
def groups_by_id
@groups_by_id ||= groups.index_by(&:id)
end
def ancestors_for(group)
return [] unless group
ancestor_ids = active_group_ancestor_ids
ancestor_ids.reverse.filter_map { |id| groups_by_id[id] }
end
def compute_ancestor_ids(group)
return [] unless group
ids = []
current = group
while current.parent_id
ids << current.parent_id
current = groups_by_id[current.parent_id]
break unless current
end
ids
end
end
end
end
@@ -0,0 +1,59 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
render(
Primer::OpenProject::DangerDialog.new(
id: DIALOG_ID,
title: t("departments.move_user_dialog.title"),
confirm_button_text: t("departments.move_user_dialog.confirm"),
size: :medium_portrait,
form_arguments: {
action: add_user_admin_department_path(to_department),
method: :post
}
)
) do |dialog|
dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { t("departments.move_user_dialog.heading") }
message.with_description do
t(
"departments.move_user_dialog.description",
user: moved_user.name,
from_department: from_department.name
)
end
end
dialog.with_additional_details do
concat(hidden_field_tag(:user_id, moved_user.id))
concat(hidden_field_tag(:remove_from_previous_department, "true"))
end
end
%>
@@ -0,0 +1,49 @@
# 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 Admin
module Departments
class MoveUserDialogComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
DIALOG_ID = "move-user-department-dialog"
attr_reader :moved_user, :from_department, :to_department
def initialize(user:, from_department:, to_department:)
super()
@moved_user = user
@from_department = from_department
@to_department = to_department
end
end
end
end
@@ -0,0 +1,52 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
component_wrapper do
flex_layout(align_items: :center, justify_content: :space_between) do |container|
container.with_column do
render(Primer::Beta::Heading.new(tag: :h4)) { organization_name }
end
container.with_column do
render(
Primer::Beta::IconButton.new(
tag: :a,
href: edit_organization_name_admin_departments_path,
icon: :pencil,
scheme: :invisible,
"aria-label": I18n.t(:button_edit),
data: { turbo_stream: true },
test_selector: "edit-organization-name-button"
)
)
end
end
end
%>
@@ -28,18 +28,20 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class RbBurndownChartsController < RbApplicationController
helper :burndown_charts
module Admin
module Departments
class OrganizationNameComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
def show
@burndown = if OpenProject::FeatureDecisions.scrum_projects_active?
Burndown.new(@sprint, @project)
else
@sprint.burndown(@project)
end
def initialize
super(nil)
end
respond_to do |format|
format.html { render layout: true }
def organization_name
Setting.organization_name.presence || I18n.t("setting_organization_name")
end
end
end
end
@@ -0,0 +1,68 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
component_wrapper do
primer_form_with(
url: update_organization_name_admin_departments_path,
method: :patch,
data: { turbo_stream: true }
) do |f|
render_inline_form(f) do |form|
form.group(layout: :horizontal) do |input_group|
input_group.text_field(
name: :organization_name,
label: I18n.t("setting_organization_name"),
value: Setting.organization_name,
visually_hide_label: true,
required: true,
autofocus: true,
input_width: :medium
)
end
form.group(layout: :horizontal) do |button_group|
button_group.button(
name: :cancel,
tag: :a,
label: I18n.t(:button_cancel),
scheme: :default,
href: helpers.cancel_edit_organization_name_admin_departments_path,
data: { turbo_stream: true, turbo_method: :patch }
)
button_group.submit(
name: :submit,
label: I18n.t(:button_save),
scheme: :primary
)
end
end
end
end
%>
@@ -0,0 +1,45 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Admin
module Departments
class OrganizationNameFormComponent < ApplicationComponent
include ApplicationHelper
include OpTurbo::Streamable
include OpPrimer::ComponentHelpers
def initialize
super(nil)
end
delegate :wrapper_key, to: OrganizationNameComponent
end
end
end
@@ -1,4 +1,4 @@
<%# -- copyright
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
@@ -25,31 +25,20 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++# %>
++#%>
<% html_title t(:label_backlogs) %>
<% helpers.html_title t(:label_administration), t(:label_departments) %>
<% content_for :content_header do %>
<%=
render Primer::OpenProject::PageHeader.new do |header|
header.with_title { t(:label_backlogs) }
header.with_breadcrumbs(
[{ href: project_overview_path(@project), text: @project.name },
t(:label_backlogs)]
)
end
%>
<% end %>
<% content_for :content_body do %>
<%=
render(Primer::Beta::Blankslate.new(border: true, spacious: true)) do |blankslate|
blankslate.with_visual_icon(icon: :"op-backlogs")
blankslate.with_heading(tag: :h2).with_content(t(:backlogs_not_configured_title))
blankslate.with_description_content(t(:backlogs_not_configured_description))
blankslate.with_secondary_action(href: admin_backlogs_settings_path, scheme: :default) do
t(:backlogs_not_configured_action_text)
<%= render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t(:label_departments) }
header.with_description do
link_translate(:label_departments_description_html, links: { ldap_docs_article: "#" })
end
end
%>
<% end %>
header.with_breadcrumbs(
[
{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_users_path, text: t(:label_user_and_permission) },
t("label_departments")
]
)
end %>
@@ -1,3 +1,5 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
@@ -26,12 +28,10 @@
# See COPYRIGHT and LICENSE files for more details.
#++
require "spec_helper"
RSpec.describe "shared/not_configured" do
before { assign(:project, create(:project)) }
it "renders without errors" do
render
module Admin
module Departments
class PageHeaderComponent < ApplicationComponent
include ApplicationHelper
end
end
end
@@ -0,0 +1,75 @@
# 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 Admin
module Departments
class UserRowComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers
def initialize(user:, group:)
super()
@user = user
@group = group
end
def call
flex_layout(align_items: :center, justify_content: :space_between) do |row|
row.with_column do
render(Users::AvatarComponent.new(user: @user, size: "mini"))
end
row.with_column do
render(Primer::Alpha::ActionMenu.new) do |menu|
menu.with_show_button(
icon: "kebab-horizontal",
scheme: :invisible,
"aria-label": I18n.t(:label_actions)
)
menu.with_item(
label: I18n.t(:button_remove),
scheme: :danger,
tag: :a,
href: remove_user_admin_department_path(@group, @user.id),
content_arguments: {
data: {
turbo_confirm: I18n.t(:text_are_you_sure),
turbo_method: :delete,
turbo_frame: "_top"
}
}
)
end
end
end
end
end
end
end
@@ -52,7 +52,9 @@ module Admin::Import::Jira::ImportRuns
end
def description
I18n.t("admin.jira.run.wizard.import_dialog.description")
link_translate("admin.jira.run.wizard.import_dialog.description",
links: { link: %i[backup_guide] },
external: true)
end
def confirm_button_text
@@ -34,11 +34,14 @@ module Admin::Import::Jira::ImportRuns
attr_reader :title, :list, :system_arguments
def initialize(title:, list:, show_icon: true, **system_arguments)
def initialize(title:, list:, subtitle: nil, show_icon: true, icon: :"dot-fill", icon_color: :default, **system_arguments)
super()
@title = title
@subtitle = subtitle
@list = list
@show_icon = show_icon
@icon = icon
@icon_color = icon_color
@system_arguments = system_arguments
end
@@ -48,6 +51,11 @@ module Admin::Import::Jira::ImportRuns
flex.with_row(mb: 1) do
render(Primer::Beta::Text.new(font_weight: :bold)) { title }
end
if @subtitle
flex.with_row(mb: 1) do
@subtitle
end
end
list.each do |item|
flex.with_row(mt: 2) do
render_item(item)
@@ -59,12 +67,7 @@ module Admin::Import::Jira::ImportRuns
def render_item(item)
if @show_icon
concat(render(
Primer::Beta::Octicon.new(
icon: item[:checked] ? :"check-circle" : :"x-circle",
color: item[:checked] ? :success : :danger
)
))
concat(render(Primer::Beta::Octicon.new(icon: @icon, color: @icon_color)))
end
if item[:url].present?
concat(render(Primer::Beta::Link.new(href: item[:url], ml: 1, target: "_blank")) { item[:label] })
@@ -34,7 +34,7 @@ See COPYRIGHT and LICENSE files for more details.
)
) do |component|
component.with_row(scheme: :neutral, color: :muted, align_items: :center) do
render(Primer::Beta::Text.new(font_weight: :semibold)) {
render(Primer::Beta::Text.new(font_weight: :bold)) {
I18n.t(:"admin.jira.run.wizard.groups.fetch.title")
}
end
@@ -43,7 +43,7 @@ See COPYRIGHT and LICENSE files for more details.
end
component.with_row(scheme: :neutral, color: :muted, align_items: :center) do
render(Primer::Beta::Text.new(font_weight: :semibold)) {
render(Primer::Beta::Text.new(font_weight: :bold)) {
I18n.t(:"admin.jira.run.wizard.groups.configuration.title")
}
end
@@ -52,7 +52,7 @@ See COPYRIGHT and LICENSE files for more details.
end
component.with_row(scheme: :neutral, color: :muted, align_items: :center) do
render(Primer::Beta::Text.new(font_weight: :semibold)) {
render(Primer::Beta::Text.new(font_weight: :bold)) {
I18n.t(:"admin.jira.run.wizard.groups.confirming.title")
}
end
@@ -61,7 +61,7 @@ See COPYRIGHT and LICENSE files for more details.
end
component.with_row(scheme: :neutral, color: :muted, align_items: :center) do
render(Primer::Beta::Text.new(font_weight: :semibold)) {
render(Primer::Beta::Text.new(font_weight: :bold)) {
I18n.t(:"admin.jira.run.wizard.groups.review.title")
}
end
@@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= flex_layout do |box|
box.with_row do
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
concat(render(Primer::Beta::Text.new(font_weight: :bold, tag: :div)) {
I18n.t(:"admin.jira.run.wizard.sections.confirm_import.title")
})
if model.status_before?(:projects_meta_done)
@@ -52,7 +52,7 @@ See COPYRIGHT and LICENSE files for more details.
render(Admin::Import::Jira::ImportRuns::InfoListBoxComponent.new(
title: I18n.t(:"admin.jira.run.wizard.sections.confirm_import.label_available_data"),
list: import_selection,
show_icon: false
show_icon: true
))
end
if model.in_state?(:projects_meta_done)
@@ -80,7 +80,7 @@ See COPYRIGHT and LICENSE files for more details.
render(Admin::Import::Jira::ImportRuns::InfoListBoxComponent.new(
title: I18n.t(:"admin.jira.run.wizard.sections.confirm_import.label_import_data"),
list: import_selection,
show_icon: false
show_icon: true
))
end
end
@@ -38,9 +38,10 @@ module Admin::Import::Jira::ImportRuns
projects_label(selected_projects_count),
issues_label(selected_issues_count),
statuses_label(selected_statuses_count),
types_label(selected_types_count),
users_label(available_users_count)
].map { |label| { label:, checked: true } }
types_label(selected_types_count)
]
.map { |label| { label:, checked: true } }
.push({ label: I18n.t(:"admin.jira.run.wizard.sections.confirm_import.label_users_import_explanation") })
end
def selected_projects_count
@@ -58,9 +59,5 @@ module Admin::Import::Jira::ImportRuns
def selected_statuses_count
model.selected["status_ids"]&.count || 0
end
def available_users_count
model.available["total_users"]
end
end
end
@@ -31,7 +31,7 @@ See COPYRIGHT and LICENSE files for more details.
box.with_row do
flex_layout do |row|
row.with_column(flex: 1) do
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
concat(render(Primer::Beta::Text.new(font_weight: :bold, tag: :div)) {
I18n.t(:"admin.jira.run.wizard.sections.fetch_data.title")
})
if model.status_equal_or_after?(:instance_meta_done)
@@ -40,20 +40,6 @@ See COPYRIGHT and LICENSE files for more details.
})
end
end
if model.in_state?(:instance_meta_done, :configuring)
row.with_column do
render(
Primer::Beta::IconButton.new(
icon: :"op-reload",
tag: :a,
scheme: :invisible,
href: continue_admin_import_jira_run_path(jira_id: model.jira.id, id: model.id, step: 'fetch_instance_meta'),
aria: { label: "Back to fetch data" },
data: { turbo_stream: true }
)
)
end
end
end
end
if model.in_state?(:initial)
@@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= flex_layout do |box|
box.with_row do
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
concat(render(Primer::Beta::Text.new(font_weight: :bold, tag: :div)) {
I18n.t(:"admin.jira.run.wizard.sections.import_scope.title")
})
if model.status_before?(:instance_meta_done)
@@ -43,32 +43,16 @@ See COPYRIGHT and LICENSE files for more details.
end
end
if model.in_state?(:instance_meta_done)
box.with_row(mb: 3, mt: 3) do
render(Primer::Alpha::Banner.new(scheme: :warning, icon: :alert)) {
I18n.t(:"admin.jira.run.wizard.sections.import_scope.label_info")
}
end
box.with_row(mb: 1) do
render(Primer::Beta::Text.new(font_size: :small, color: :muted)) {
server_info
}
end
box.with_row(mb: 3) do
flex_layout do |flex|
flex.with_column(flex: 1, mr: 1) do
render(Admin::Import::Jira::ImportRuns::InfoListBoxComponent.new(
title: I18n.t(:"admin.jira.run.wizard.sections.import_scope.label_available_data"),
title: I18n.t(:"admin.jira.run.wizard.sections.import_scope.label_available_server_data", server_info: model.jira.name),
subtitle: server_info,
list: import_stats_available,
h: :full
))
end
flex.with_column(flex: 1, ml: 1) do
render(Admin::Import::Jira::ImportRuns::InfoListBoxComponent.new(
title: I18n.t(:"admin.jira.run.wizard.sections.import_scope.label_not_available_data"),
list: import_stats_unavailable,
h: :full
))
end
end
end
box.with_row do
@@ -43,25 +43,22 @@ module Admin::Import::Jira::ImportRuns
].map { |label| { label:, checked: true } }
end
def import_stats_unavailable
[
I18n.t(:"admin.jira.run.wizard.sections.import_scope.elements.relations"),
I18n.t(:"admin.jira.run.wizard.sections.import_scope.elements.workflows"),
I18n.t(:"admin.jira.run.wizard.sections.import_scope.elements.permissions"),
I18n.t(:"admin.jira.run.wizard.sections.import_scope.elements.sprints"),
I18n.t(:"admin.jira.run.wizard.sections.import_scope.elements.schemes")
].map { |label| { label:, checked: false } }
end
def server_info
info = model.available["server_info"]
return "" unless info
return nil unless info
[
info["serverTitle"],
info["version"],
"(#{info['baseUrl']})"
].join(" ")
render(Primer::Beta::Text.new(font_size: :small, color: :subtle)) do
safe_join([
info["serverTitle"],
" ",
info["version"],
" ",
render(Primer::Beta::Link.new(href: model.jira.url, target: "_blank")) do |link|
link.with_trailing_visual_icon(icon: :"link-external")
info["baseUrl"]
end
])
end
end
def selected_projects_count
@@ -30,7 +30,7 @@ See COPYRIGHT and LICENSE files for more details.
<%= flex_layout do |box|
if model.in_state?(:reverting, :reverted, :revert_error)
box.with_row do
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
concat(render(Primer::Beta::Text.new(font_weight: :bold, tag: :div)) {
I18n.t(:"admin.jira.run.wizard.sections.import_result.label_revert")
})
end
@@ -54,7 +54,7 @@ See COPYRIGHT and LICENSE files for more details.
end
elsif model.in_state?(:finalizing, :finalizing_done, :finalizing_error)
box.with_row do
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
concat(render(Primer::Beta::Text.new(font_weight: :bold, tag: :div)) {
I18n.t(:"admin.jira.run.wizard.sections.import_result.label_finalize_import")
})
end
@@ -78,7 +78,7 @@ See COPYRIGHT and LICENSE files for more details.
end
else
box.with_row do
concat(render(Primer::Beta::Text.new(font_weight: :semibold, tag: :div)) {
concat(render(Primer::Beta::Text.new(font_weight: :bold, tag: :div)) {
I18n.t(:"admin.jira.run.wizard.sections.import_result.title")
})
if model.status_before?(:imported)
@@ -98,7 +98,8 @@ See COPYRIGHT and LICENSE files for more details.
box.with_row(mt: 3, mb: 3) do
render(Admin::Import::Jira::ImportRuns::InfoListBoxComponent.new(
title: I18n.t(:"admin.jira.run.wizard.sections.import_result.label_results"),
list: imported_data
list: imported_data,
show_icon: false
))
end
end
@@ -36,8 +36,7 @@ module Admin::Import::Jira::ImportRuns
def imported_data
[
{ label: projects_label(imported_projects.count), checked: true, url: imported_projects_url },
{ label: work_packages_label(imported_work_packages.count), checked: true, url: imported_work_packages_url },
imported_users.none? ? nil : { label: users_label(imported_users.count), checked: true, url: imported_users_url }
{ label: work_packages_label(imported_work_packages.count), checked: true, url: imported_work_packages_url }
].compact
end
@@ -50,7 +49,11 @@ module Admin::Import::Jira::ImportRuns
return nil if imported_projects.none?
ids = imported_projects.pluck(:op_entity_id).map(&:to_s)
helpers.projects_path(filters: [{ id: { operator: "=", values: ids } }].to_json)
if ids.length == 1
helpers.project_path(id: ids[0])
else
helpers.projects_path(filters: [{ id: { operator: "=", values: ids } }].to_json)
end
end
def imported_work_packages
@@ -61,19 +64,13 @@ module Admin::Import::Jira::ImportRuns
def imported_work_packages_url
return nil if imported_work_packages.none?
project_ids = imported_projects.pluck(:op_entity_id).map(&:to_s)
helpers.work_packages_path(query_props: { f: [{ n: "project", o: "=", v: project_ids }] }.to_json)
end
def imported_users
@imported_users ||= Import::JiraOpenProjectReference
.where(jira_import: model, op_entity_class: "User", uses_existing: false)
end
def imported_users_url
return nil if imported_users.none?
helpers.users_path
wp_ids = imported_work_packages.pluck(:op_entity_id).map(&:to_s)
if wp_ids.length == 1
helpers.work_package_path(id: wp_ids[0])
else
project_ids = imported_projects.pluck(:op_entity_id).map(&:to_s)
helpers.work_packages_path(query_props: { f: [{ n: "project", o: "=", v: project_ids }] }.to_json)
end
end
end
end
@@ -81,6 +81,16 @@ module EnterpriseEdition
end
def plan_text
if trial_feature?
safe_join [helpers.t("ee.upsell.trial_text"), upsell_plan_text], " "
else
upsell_plan_text
end
end
private
def upsell_plan_text
plan_name = render(Primer::Beta::Text.new(font_weight: :bold, classes: "upsell-colored-text")) do
I18n.t("ee.upsell.plan_name", plan: plan.capitalize)
end
+15 -14
View File
@@ -28,12 +28,10 @@
# See COPYRIGHT and LICENSE files for more details.
# ++
module Filter
# rubocop:disable OpenProject/AddPreviewForViewComponent
class FilterComponent < ApplicationComponent
OPERATORS_WITHOUT_VALUES = %w[* !* t w].freeze
TURBO_FRAME_ID = "filter_component"
# rubocop:enable OpenProject/AddPreviewForViewComponent
options :query
# The path used for fetching the filter section lazily from the backend upon opening it.
# If none is provided, the filters are rendered right away.
@@ -113,15 +111,15 @@ module Filter
end
def custom_field_list_autocomplete_options(filter)
options = if filter.custom_field.version?
{
items: filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } },
groupBy: "project_name"
}
else
{ items: filter.allowed_values.map { |name, id| { name:, id: } } }
end
autocomplete_options.merge(options).merge(model: filter.values)
all_items = if filter.custom_field.version?
filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } }
else
filter.allowed_values.map { |name, id| { name:, id: } }
end
selected = filter.values
options = { items: all_items }
options[:groupBy] = "project_name" if filter.custom_field.version?
autocomplete_options.merge(options).merge(model: all_items.select { |item| selected.include?(item[:id]) })
end
def custom_field_hierarchy_autocomplete_options(filter)
@@ -129,14 +127,17 @@ module Filter
path = name.split(" / ")
{ name: path.last, id:, depth: path.length - 1 }
end
selected = filter.values
autocomplete_options.merge({ items: }).merge(model: filter.values)
autocomplete_options.merge({ items: }).merge(model: items.select { |item| selected.include?(item[:id]) })
end
def list_autocomplete_options(filter)
all_items = filter.allowed_values.map { |name, id| { name:, id: } }
selected = filter.values
autocomplete_options.merge(
items: filter.allowed_values.map { |name, id| { name:, id: } },
model: filter.values
items: all_items,
model: all_items.select { |item| selected.include?(item[:id]) }
)
end
@@ -35,15 +35,15 @@ See COPYRIGHT and LICENSE files for more details.
header.with_action_button(
tag: :a,
mobile_icon: :pencil,
mobile_label: t(:button_edit),
mobile_label: edit_label,
size: :medium,
href: edit_group_path(@group),
aria: { label: I18n.t(:button_edit) },
href: edit_path,
aria: { label: edit_label },
data: { "test-selector": "groups--edit-group-button" },
title: I18n.t(:button_edit)
title: edit_label
) do |button|
button.with_leading_visual_icon(icon: :pencil)
t(:button_edit)
edit_label
end
header.with_action_button(
@@ -40,8 +40,50 @@ module Groups
end
def breadcrumb_items
[{ href: groups_path, text: t(:label_group_plural) },
@group.name]
if @current_user.admin?
admin_breadcrumb_items
else
non_admin_breadcrumb_items
end
end
private
def admin_breadcrumb_items
items = [{ href: admin_index_path, text: t("label_administration") },
{ href: admin_settings_users_path, text: t(:label_user_and_permission) }]
items << if @group.organizational_unit?
{ href: admin_departments_path, text: t(:label_departments) }
else
{ href: groups_path, text: t(:label_group_plural) }
end
items << @group.name
end
def non_admin_breadcrumb_items
if @group.organizational_unit?
[t(:label_departments), @group.name]
else
[t(:label_group_plural), @group.name]
end
end
def edit_path
if @group.organizational_unit?
edit_admin_department_path(@group)
else
edit_group_path(@group)
end
end
def edit_label
if @group.organizational_unit?
t("departments.edit")
else
t(:button_edit)
end
end
end
end
@@ -27,4 +27,4 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= render(Primer::Alpha::Banner.new(scheme: :default)) { format_text announcement.text } %>
<%= render(Primer::Alpha::Banner.new(scheme: :default, mb: 3)) { format_text announcement.text } %>
@@ -30,19 +30,9 @@
module Homescreen
module Blocks
class Users < Grids::WidgetComponent
include IconsHelper
include OpenProject::ObjectLinking
include Redmine::I18n
def initialize(*)
super
@newest_users = User.active.newest.take(3)
end
def title
I18n.t(:label_user_plural)
class Meetings < Grids::WidgetComponent
def call
render(::Meetings::Widgets::Meetings.new(limit: 3))
end
end
end
@@ -1,23 +0,0 @@
<%= widget_wrapper do %>
<p class="widget-box--additional-info"><%= t("homescreen.additional.users") %></p>
<% unless @newest_users.empty? %>
<ul class="widget-box--arrow-links">
<% @newest_users.each do |user| %>
<li>
<%= link_to_user user %>
<small>(<%= format_date(user.created_at) %>)</small>
</li>
<% end %>
</ul>
<% end %>
<div class="widget-box--buttons">
<% if current_user.admin? %>
<%= link_to new_user_path, class: "button -primary" do %>
<%= op_icon("button--icon icon-add") %>
<span class="button--text"><%= t(:label_invite_user) %></span>
<% end %>
<% end %>
</div>
<% end %>
@@ -75,7 +75,7 @@ class IndividualPrincipalBaseFilterComponent < ApplicationComponent
end
def base_query
raise NotImplementedError
raise SubclassResponsibilityError
end
protected
@@ -93,7 +93,7 @@ class IndividualPrincipalBaseFilterComponent < ApplicationComponent
# INSTANCE METHODS:
def filter_path
raise NotImplementedError
raise SubclassResponsibilityError
end
def initially_visible?
@@ -0,0 +1,62 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%= render(
Primer::Alpha::Dialog.new(
id: DIALOG_ID,
title: t("my_account.notifications.project_specific_settings.dialog_title"),
position: :right,
test_selector: "project-specific-settings-form"
)
) do |d|
d.with_body do
primer_form_with(
model: notification_setting,
scope: :notification_setting,
url: @form_url,
method: edit_mode? ? :patch : :post,
html: { id: FORM_ID },
data: {
turbo: false,
controller: "show-when-checked",
show_when_checked_visibility_class: "d-none"
}
) do |form|
forms = [My::Notifications::ProjectAutocompleterForm.new(form, readonly: edit_mode?, user: @user)]
forms << My::Notifications::ParticipatingForm.new(form, show_submit: false)
forms << My::Notifications::DateAlertsForm.new(form, show_submit: false) if date_alerts_available?
forms << My::Notifications::NonParticipatingForm.new(form, show_submit: false)
render(Primer::Forms::FormList.new(*forms))
end
end
d.with_footer(show_divider: true) do
concat(render(Primer::Beta::Button.new(data: { "close-dialog-id": DIALOG_ID })) { t("button_cancel") })
concat(render(Primer::Beta::Button.new(scheme: :primary, type: :submit, form: FORM_ID)) { t("button_save") })
end
end %>
@@ -0,0 +1,62 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module My
module Notifications
class ProjectSettingsDialogComponent < ApplicationComponent
include OpTurbo::Streamable
include OpPrimer::FormHelpers
DIALOG_ID = "project-notification-settings-dialog"
FORM_ID = "project-notification-settings-form"
def initialize(user:, form_url:, notification_setting: nil)
super
@user = user
@form_url = form_url
@provided_setting = notification_setting
end
private
def notification_setting
@notification_setting ||= @provided_setting || @user.notification_settings.build
end
def edit_mode?
notification_setting.persisted?
end
def date_alerts_available?
EnterpriseToken.allows_to?(:date_alerts)
end
end
end
end
@@ -0,0 +1,137 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
settings_primer_form_with(
model: global_notification_setting,
scope: :notification_setting,
url: update_participating_url,
method: :patch,
data: { turbo: false, test_selector: "participating-form" }
) do |form|
render(My::Notifications::ParticipatingForm.new(form))
end
%>
<% if date_alerts_available? %>
<%=
settings_primer_form_with(
model: global_notification_setting,
scope: :notification_setting,
url: update_date_alerts_url,
method: :patch,
data: {
turbo: false,
controller: "show-when-checked",
show_when_checked_visibility_class: "d-none",
test_selector: "date-alerts-form"
}
) do |form|
render(My::Notifications::DateAlertsForm.new(form))
end
%>
<% else %>
<%= render(Primer::Beta::Subhead.new(mt: 3)) do |component|
component.with_heading(size: :medium) { t("my_account.notifications.date_alerts.title") }
component.with_description { t("my_account.notifications.date_alerts.description") }
end %>
<%= render(EnterpriseEdition::BannerComponent.new(:date_alerts, variant: :inline)) %>
<% end %>
<%=
settings_primer_form_with(
model: global_notification_setting,
scope: :notification_setting,
url: update_non_participating_url,
method: :patch,
data: { turbo: false, test_selector: "non-participating-form" }
) do |form|
render(My::Notifications::NonParticipatingForm.new(form))
end
%>
<%= render(Primer::BaseComponent.new(tag: :div, classes: "op-admin-settings-form-wrapper")) do %>
<%= render(Primer::Beta::Subhead.new(mt: 3)) do |component|
component.with_heading(size: :medium) { t("my_account.notifications.project_specific_settings.title") }
component.with_description { t("my_account.notifications.project_specific_settings.description") }
end %>
<% if project_notification_settings.any? %>
<%= render(Primer::Beta::BorderBox.new(mb: 3)) do |box| %>
<% box.with_header { t("my_account.notifications.project_specific_settings.list_header") } %>
<% project_notification_settings.each do |setting| %>
<% box.with_row(test_selector: "project-specific-settings-list") do %>
<%= flex_layout(justify_content: :space_between, align_items: :center, width: :full) do |flex| %>
<%= flex.with_column do %>
<span><%= setting.project.name %></span>
<% end %>
<%= flex.with_column do %>
<%= render(Primer::Alpha::ActionMenu.new(test_selector: "project-specific-settings-list--action-menu")) do |menu|
menu.with_show_button(
scheme: :invisible,
size: :small,
icon: :"kebab-horizontal",
"aria-label": t(:label_open_menu),
tooltip_direction: :w
)
menu.with_item(
label: t("button_edit"),
href: edit_project_settings_url(setting.project_id),
content_arguments: { data: { controller: "async-dialog" } }
) do |item|
item.with_leading_visual_icon(icon: :pencil)
end
menu.with_item(
label: t("button_delete"),
scheme: :danger,
href: project_setting_url(setting.project_id),
content_arguments: { data: { turbo_method: :delete, turbo_confirm: t("text_are_you_sure") } }
) do |item|
item.with_leading_visual_icon(icon: :trash)
end
end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= render(
Primer::Beta::Button.new(
tag: :a,
href: new_project_settings_url,
data: { controller: "async-dialog" },
mb: 3
)
) do |b|
b.with_leading_visual_icon(icon: :plus)
helpers.t("my_account.notifications.project_specific_settings.add_button")
end %>
<% end %>
@@ -0,0 +1,78 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module My
module Notifications
class ShowPageComponent < ApplicationComponent
include OpPrimer::FormHelpers
include OpPrimer::ComponentHelpers
attr_reader :global_notification_setting,
:update_participating_url,
:update_non_participating_url,
:update_date_alerts_url,
:new_project_settings_url,
:project_notification_settings
def initialize(user:,
global_notification_setting:,
update_participating_url:,
update_non_participating_url:,
update_date_alerts_url:,
new_project_settings_url:,
edit_project_settings_url:,
project_setting_url:)
super
@user = user
@global_notification_setting = global_notification_setting
@update_participating_url = update_participating_url
@update_non_participating_url = update_non_participating_url
@update_date_alerts_url = update_date_alerts_url
@new_project_settings_url = new_project_settings_url
@edit_project_settings_url_builder = edit_project_settings_url
@project_setting_url_builder = project_setting_url
@project_notification_settings = user.notification_settings.where.not(project: nil).includes(:project)
end
def edit_project_settings_url(project_id)
@edit_project_settings_url_builder.call(project_id)
end
def project_setting_url(project_id)
@project_setting_url_builder.call(project_id)
end
def date_alerts_available?
EnterpriseToken.allows_to?(:date_alerts)
end
end
end
end
@@ -0,0 +1,64 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module My
module Notifications
class ShowPageHeaderComponent < ApplicationComponent
def call
render(Primer::OpenProject::PageHeader.new) do |header|
header.with_title { t("my_account.notifications_and_email.title") }
header.with_breadcrumbs(
[{ href: helpers.my_account_path, text: t(:label_my_account) },
t("my_account.notifications_and_email.title")]
)
helpers.render_tab_header_nav(header, tabs)
end
end
def tabs
[
{
name: "notifications",
path: helpers.my_notifications_path(tab: "notifications"),
label: t("my_account.notifications_and_email.tabs.notifications"),
data: { turbo: false }
},
{
name: "reminders",
path: helpers.my_notifications_path(tab: "reminders"),
label: t("my_account.notifications_and_email.tabs.email_reminders"),
data: { turbo: false }
}
]
end
end
end
end
@@ -0,0 +1,111 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<div data-controller="my--daily-reminders">
<div data-my--daily-reminders-target="list">
<% @times.each do |time| %>
<%=
flex_layout(mb: 2, data: { "my--daily-reminders-target": "row" }) do |flex|
flex.with_column do
render(
Primer::Alpha::Select.new(
name: field_name,
label: t("my_account.email_reminders.daily_reminders.time_slot_label"),
visually_hide_label: true,
data: { test_selector: "settings-daily-time" }
)
) do |select|
time_options.each do |label, value|
select.option(label:, value:, selected: selected_value_for(time) == value)
end
end
end
flex.with_column do
render(
Primer::Beta::IconButton.new(
icon: :x,
scheme: :invisible,
type: :button,
hidden: @times.size <= 1,
aria: { label: t("my_account.email_reminders.daily_reminders.remove_time") },
data: { action: "my--daily-reminders#removeTime", test_selector: "settings-daily-time--remove" }
)
)
end
end
%>
<% end %>
</div>
<template data-my--daily-reminders-target="rowTemplate">
<%=
flex_layout(mb: 2, data: { "my--daily-reminders-target": "row" }) do |flex|
flex.with_column do
render(
Primer::Alpha::Select.new(
name: field_name,
label: t("my_account.email_reminders.daily_reminders.time_slot_label"),
visually_hide_label: true,
id: nil,
data: { test_selector: "settings-daily-time" }
)
) do |select|
time_options.each do |label, value|
select.option(label:, value:)
end
end
end
flex.with_column do
render(
Primer::Beta::IconButton.new(
icon: :x,
scheme: :invisible,
type: :button,
aria: { label: t("my_account.email_reminders.daily_reminders.remove_time") },
data: { action: "my--daily-reminders#removeTime", test_selector: "settings-daily-time--remove" }
)
)
end
end
%>
</template>
<%= render(
Primer::Beta::Button.new(
scheme: :invisible,
type: :button,
data: { action: "my--daily-reminders#addTime" }
)
) do |btn|
btn.with_leading_visual_icon(icon: :plus)
t("my_account.email_reminders.daily_reminders.add_time")
end %>
</div>
@@ -0,0 +1,59 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module My
module Reminders
class DailyTimesComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
def initialize(times:, scope:)
super
@times = Array(times)
@scope = scope
end
def field_name
"#{@scope}[times][]"
end
def time_options
(0..23).map do |hour|
time = Time.utc(2000, 1, 1, hour)
[I18n.l(time, format: :time), time.strftime("%H:00:00+00:00")]
end
end
def selected_value_for(time_str)
Time.zone.parse(time_str.to_s).strftime("%H:00:00+00:00")
end
end
end
end
@@ -0,0 +1,98 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%=
settings_primer_form_with(
model: @user.pref,
scope: "pref[immediate_reminders]",
url: update_url,
method: :patch,
data: { turbo: false, test_selector: "immediate-reminders-form" }
) do |form|
render(My::Reminders::ImmediateRemindersForm.new(form))
end
%>
<%=
settings_primer_form_with(
model: daily_reminders_form_model,
scope: "pref[daily_reminders]",
url: update_url,
method: :patch,
data: {
turbo: false,
controller: "show-when-checked",
show_when_checked_visibility_class: "d-none",
test_selector: "daily-reminders-form"
}
) do |form|
render(My::Reminders::DailyRemindersForm.new(form))
end
%>
<%=
settings_primer_form_with(
model: @user.pref,
scope: :pref,
url: update_workdays_url,
method: :patch,
data: { turbo: false, test_selector: "workdays-form" }
) do |form|
render(My::Reminders::WorkdaysForm.new(form))
end
%>
<%=
settings_primer_form_with(
model: pause_reminders_form_model,
scope: "pref[pause_reminders]",
url: update_url,
method: :patch,
data: {
turbo: false,
controller: "show-when-checked",
show_when_checked_visibility_class: "d-none",
test_selector: "pause-reminders-form"
}
) do |form|
render(My::Reminders::PauseRemindersForm.new(form))
end
%>
<%=
settings_primer_form_with(
model: global_notification_setting,
scope: :notification_setting,
url: update_email_alerts_url,
method: :patch,
data: { turbo: false }
) do |form|
render(My::Reminders::EmailAlertsForm.new(form))
end
%>
@@ -0,0 +1,66 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module My
module Reminders
class ShowPageComponent < ApplicationComponent
include OpPrimer::FormHelpers
attr_reader :global_notification_setting, :update_url, :update_workdays_url, :update_email_alerts_url
def initialize(user:, global_notification_setting:, update_url:, update_workdays_url:, update_email_alerts_url:)
super
@user = user
@global_notification_setting = global_notification_setting
@update_url = update_url
@update_workdays_url = update_workdays_url
@update_email_alerts_url = update_email_alerts_url
end
def daily_reminders_form_model
daily_reminders = @user.pref.daily_reminders
My::Reminders::DailyRemindersForm::DailyRemindersFormModel.new(
enabled: daily_reminders[:enabled],
times: daily_reminders[:times]
)
end
def pause_reminders_form_model
pause_reminders = @user.pref.pause_reminders
My::Reminders::PauseRemindersForm::PauseRemindersFormModel.new(
enabled: pause_reminders[:enabled],
first_day: pause_reminders[:first_day],
last_day: pause_reminders[:last_day]
)
end
end
end
end
@@ -0,0 +1,43 @@
# frozen_string_literal: true
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module OpPrimer
class BackButtonLinkComponent < ApplicationComponent
def call
link_to :back, class: "Button--secondary Button--medium Button" do
tag.span class: "Button-content" do
tag.span class: "Button-label" do
model || helpers.t("button_cancel")
end
end
end
end
end
end
@@ -38,7 +38,7 @@ module OpPrimer
super()
if !show_button && alt_text.blank?
raise NotImplementedError, "alt_text must be provided when the button is shown conditionally"
raise ArgumentError, "alt_text must be provided when the button is shown conditionally"
end
@@ -33,6 +33,7 @@ See COPYRIGHT and LICENSE files for more details.
tag: :div,
classes: "op-primer-flash--item",
data: {
turbo_temporary: true,
"flash-target": "item",
autohide: @autohide,
unique_key: @unique_key
@@ -27,8 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<div class="errors">
<% model_errors.full_messages.each do |err| %>
<div><%= err %></div>
<% end %>
</div>
<%= render(Primer::BaseComponent.new(tag: :span, **@system_arguments)) do %>
<%= leading_visual_icon %>
<%= content %>
<% end %>
@@ -28,13 +28,20 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class WorkPackages::IdentifierAutofix::ApplyHandlesJob < ApplicationJob
# FIXME: The admin UI's job_in_progress? query and :change_in_progress state
# assume at most one active instance of this job at any given time.
# Enforce this with good_job_control_concurrency_with(perform_limit: 1)
# when the real migration body is implemented.
def perform
# FIXME: replace with actual project handle migration
sleep 5
module OpPrimer
class InlineMacroComponent < Primer::Component
renders_one :leading_visual_icon, ->(icon:, color: :muted) do
Primer::Beta::Octicon.new(icon:, color:, mr: 2, vertical_align: :middle)
end
def initialize(**system_arguments)
super()
@system_arguments = system_arguments
@system_arguments[:classes] = class_names(
@system_arguments[:classes],
"op-inline-macro"
)
end
end
end
@@ -0,0 +1,7 @@
@media screen
.op-inline-macro
display: inline
background: var(--bgColor-muted)
border: 1px solid transparent
border-radius: var(--borderRadius-medium)
padding: 4px 8px
@@ -50,7 +50,7 @@ module OpPrimer
end
def default_button_title
raise NotImplementedError
raise SubclassResponsibilityError
end
def disabled?
@@ -57,7 +57,7 @@
@media only screen and (max-width: $breakpoint-lg)
// Special logic for those pages that already use the new layout and have the content-bodyRight filled (so basically, the split screen opened full height on mobile)
#content:has(#content-bodyRight > *)
@include split-pane-active
#menu-toggle--expand-button
top: 60px
.op-wp-breadcrumb
@@ -40,7 +40,7 @@
size: :large,
data: {
hover_card_trigger_target: "trigger",
hover_card_popover_id: sub_status_hover_card_id,
hover_card_popover_template_id: sub_status_hover_card_id,
test_selector: "op-portfolios--sub-status-bar"
}
)
@@ -166,9 +166,8 @@
<%=
# Card that appears when hovering over the progress bar
if render_sub_status_bar?
content_tag(:div, class: "op-hover-card--hidden-container") do
content_tag(:template, id: sub_status_hover_card_id) do
flex_layout(
id: sub_status_hover_card_id,
classes: "op-portfolios--popover",
data: {
test_selector: "op-portfolios--hover-card-#{portfolio.id}"
@@ -51,3 +51,6 @@ $status_not-set: var(--progressBar-track-bgColor) // invisible background color
@media screen and (max-width: $breakpoint-sm)
margin-top: var(--base-size-16, 1rem)
margin-bottom: var(--base-size-16, 1rem) !important
.op-hover-card:has(> .op-portfolios--popover)
width: fit-content
@@ -32,7 +32,7 @@ module Projects
module Concerns
module IdentifierSuggestion
def identifier_suggestion_data
suggestion_mode = Setting::WorkPackageIdentifier.alphanumeric? ? "semantic" : "legacy"
suggestion_mode = Setting::WorkPackageIdentifier.semantic? ? "semantic" : "classic"
{
controller: "projects--identifier-suggestion",
@@ -50,9 +50,8 @@
# As a courtesy to users, we show a hover card explaining why the toggle is disabled.
if toggle_disabled?
concat(
content_tag(:div, class: "op-hover-card--hidden-container") do
content_tag(:template, id: unique_hovercard_id) do
flex_layout(
id: unique_hovercard_id,
classes: "op-project-custom-field--popover",
data: {
test_selector: "op-project-custom-field--hover-card-#{@project_custom_field.id}"
@@ -72,7 +72,7 @@ module Projects
if toggle_disabled?
# Add hover card that explains why this toggle switch is disabled
data[:hover_card_trigger_target] = "trigger"
data[:hover_card_popover_id] = unique_hovercard_id
data[:hover_card_popover_template_id] = unique_hovercard_id
end
end
end
@@ -56,9 +56,8 @@
# As a courtesy to users, we show a hover card explaining why the toggle is disabled.
if toggle_disabled?
concat(
content_tag(:div, class: "op-hover-card--hidden-container") do
content_tag(:template, id: unique_hovercard_id) do
flex_layout(
id: unique_hovercard_id,
classes: "op-project-custom-field--popover",
data: {
test_selector: "op-project-custom-field--hover-card-#{@project_custom_field.id}"
@@ -87,7 +87,7 @@ module Projects
if toggle_disabled?
# Add hover card that explains why this toggle switch is disabled
data[:hover_card_trigger_target] = "trigger"
data[:hover_card_popover_id] = unique_hovercard_id
data[:hover_card_popover_template_id] = unique_hovercard_id
end
end
end
+1 -1
View File
@@ -85,7 +85,7 @@ module Versions
end
def button_links
[edit_link, delete_link, backlogs_edit_link].compact
[edit_link, delete_link].compact
end
private
@@ -1,4 +1,4 @@
$pattern-input-height: calc(3*var(--control-medium-lineBoxHeight) + var(--control-medium-paddingInline-condensed))
$pattern-input-height: calc(3*var(--base-size-20) + var(--control-medium-paddingInline-condensed))
.op-pattern-input
&--text-field
@@ -58,7 +58,7 @@
)
dialog.with_additional_details(display: :none) do
hidden_field_tag("settings[work_packages_identifier]", Setting::WorkPackageIdentifier::ALPHANUMERIC)
hidden_field_tag("settings[work_packages_identifier]", Setting::WorkPackageIdentifier::SEMANTIC)
end
end
%>
@@ -34,7 +34,7 @@ module WorkPackages
class IdentifierAutofixSectionComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
DISPLAY_COUNT = WorkPackages::IdentifierAutofix::PreviewQuery::DISPLAY_COUNT
DISPLAY_COUNT = ProjectIdentifiers::IdentifierAutofix::PreviewQuery::DISPLAY_COUNT
def initialize(projects_data:, total_count: projects_data.size)
super()
@@ -47,7 +47,7 @@
if change_in_progress?
render(Primer::Beta::Text.new(my: 3)) do
render(Primer::Beta::Spinner.new(size: :small, mr: 2)).to_s +
I18n.t("admin.settings.work_packages_identifier.in_progress.banner_message")
in_progress_banner_message
end
elsif completed?
render(Primer::Alpha::Banner.new(scheme: :success, dismiss_scheme: :remove, mb: 3)) do
@@ -46,7 +46,7 @@ module WorkPackages
super()
@state = state
if state == :edit
result = WorkPackages::IdentifierAutofix::PreviewQuery.new.call
result = ProjectIdentifiers::IdentifierAutofix::PreviewQuery.new.call
@projects_data = result.projects_data
@total_count = result.total_count
else
@@ -63,8 +63,17 @@ module WorkPackages
def form_id = "wp-identifier-settings-form"
def in_progress_banner_message
key = if ProjectIdentifiers::IdentifierAutofix.reversion_in_progress?
"admin.settings.work_packages_identifier.in_progress.reverting_banner_message"
else
"admin.settings.work_packages_identifier.in_progress.converting_banner_message"
end
I18n.t(key)
end
def show_autofix_section?
state == :edit && Setting::WorkPackageIdentifier.alphanumeric? && has_problematic_projects?
state == :edit && Setting::WorkPackageIdentifier.semantic? && has_problematic_projects?
end
def change_in_progress? = state == :change_in_progress
@@ -99,11 +108,22 @@ module WorkPackages
def radio_button_options
if change_in_progress?
{ button_options: { disabled: true } }
{
values: identifier_values(checked: nil),
button_options: { disabled: true }
}
elsif completed?
{ values: identifier_values(checked: Setting[:work_packages_identifier]) }
else
{ button_options: { data: { action: "change->admin--work-packages-identifier#handleChange" } } }
end
end
def identifier_values(checked:)
Setting::WorkPackageIdentifier::ALLOWED_VALUES.map do |v|
{ name: v, value: v, checked: v == checked }
end
end
end
end
end
@@ -0,0 +1,81 @@
<%#
-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#
%>
<%=
render(
Primer::OpenProject::DangerDialog.new(
id:,
title:,
form_arguments: {
action: form_action,
method: :delete,
data: { turbo: false }
},
size: :medium_portrait
)
) do |dialog|
%>
<% dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { heading }
message.with_description_content(description)
end %>
<% dialog.with_additional_details(display: :block) do %>
<% if multiple_projects? %>
<%= render(Primer::Alpha::Banner.new(mb: 2, scheme: :warning, icon: :alert)) do %>
<%= I18n.t("work_packages.bulk_delete_dialog.cross_project_warning", projects: project_names) %>
<% end %>
<% end %>
<%= render(OpPrimer::InsetBoxComponent.new) do %>
<% work_packages.each do |wp| %>
<%= render WorkPackages::InfoLineComponent.new(work_package: wp,
show_subject: true,
show_status: false,
show_project: multiple_projects?) %>
<% if descendants_for(wp).any? %>
<%= render(Primer::Box.new(pl: 2, my: 1)) do %>
<%= render(Primer::Beta::Text.new(mt: 1, font_size: :small, font_weight: :bold, display: :block, mb: 1)) do %>
<%= I18n.t("work_packages.bulk_delete_dialog.children_label") %>
<% end %>
<% descendants_for(wp).each do |descendant| %>
<%= render WorkPackages::InfoLineComponent.new(work_package: descendant,
show_subject: true,
show_status: false,
show_project: descendant.project != wp.project) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% dialog.with_confirmation_check_box_content(confirmation_checkbox_text) %>
<% end %>
@@ -0,0 +1,121 @@
# 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
class BulkDeleteDialogComponent < ApplicationComponent
include OpTurbo::Streamable
attr_reader :work_packages
def initialize(work_packages:, back_url: nil)
super
@work_packages = work_packages
@back_url = back_url
end
private
def id = "wp-delete-dialog"
def title
I18n.t("work_packages.bulk_delete_dialog.title", count: total_count)
end
def heading
I18n.t("work_packages.bulk_delete_dialog.heading", count: total_count)
end
def description
if has_descendants?
I18n.t("work_packages.bulk_delete_dialog.description_with_children")
else
I18n.t("work_packages.bulk_delete_dialog.description")
end
end
def confirmation_checkbox_text
if has_descendants?
I18n.t("work_packages.bulk_delete_dialog.confirm_children_deletion")
else
I18n.t("text_permanent_delete_confirmation_checkbox_label")
end
end
def total_count
@total_count ||= work_packages.count + descendants_by_work_package.values.sum(&:size)
end
def multiple_projects?
projects.size > 1
end
def project_names
projects.map(&:name).join(", ")
end
def descendants_for(work_package)
(descendants_by_work_package[work_package.id] || [])
.reject { |child| work_packages.include?(child) }
end
def has_descendants?
work_packages.any? { |wp| descendants_for(wp).any? }
end
def form_action
helpers.work_packages_bulk_path(ids: work_packages.map(&:id), back_url: @back_url)
end
def projects
@projects ||= begin
all_work_packages = work_packages + descendants_by_work_package.values.flatten
all_work_packages.filter_map(&:project).uniq
end
end
def descendants_by_work_package
@descendants_by_work_package ||= begin
hierarchies = WorkPackageHierarchy
.where(ancestor_id: work_packages.map(&:id))
.where("generations > 0")
.order(:generations, :descendant_id)
descendant_records = WorkPackage
.where(id: hierarchies.pluck(:descendant_id))
.includes(:project, :type, :status)
.index_by(&:id)
hierarchies
.group_by(&:ancestor_id)
.transform_values { |rows| rows.filter_map { |r| descendant_records[r.descendant_id] } }
end
end
end
end
@@ -0,0 +1,75 @@
<%#
-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#
%>
<%=
render(
Primer::OpenProject::DangerDialog.new(
id:,
title:,
form_arguments: {
action: form_action,
method: :delete,
data: { turbo: false }
},
size: :medium_portrait
)
) do |dialog|
%>
<% dialog.with_confirmation_message do |message|
message.with_heading(tag: :h2) { heading }
message.with_description_content(description)
end %>
<% if has_descendants? %>
<% dialog.with_additional_details(display: :block) do %>
<% if cross_project_descendants? %>
<%= render(Primer::Alpha::Banner.new(mb: 2, scheme: :warning, icon: :alert)) do %>
<%= I18n.t("work_packages.delete_dialog.cross_project_warning", projects: all_project_names) %>
<% end %>
<% end %>
<%= render(OpPrimer::InsetBoxComponent.new) do %>
<%= render(Primer::Beta::Text.new(font_size: :small, font_weight: :bold, display: :block, mb: 2)) do %>
<%= I18n.t("work_packages.bulk_delete_dialog.children_label") %>
<% end %>
<% descendants.each do |descendant| %>
<%= render WorkPackages::InfoLineComponent.new(pl:2,
my: 1,
work_package: descendant,
show_subject: true,
show_status: false,
show_project: descendant.project != work_package.project) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% dialog.with_confirmation_check_box_content(confirmation_checkbox_text) %>
<% end %>
@@ -0,0 +1,100 @@
# 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
class DeleteDialogComponent < ApplicationComponent
include OpTurbo::Streamable
attr_reader :work_package
def initialize(work_package:, back_url: nil)
super
@work_package = work_package
@back_url = back_url
end
private
def id = "wp-delete-dialog"
def title
I18n.t("work_packages.delete_dialog.title")
end
def heading
I18n.t("work_packages.delete_dialog.heading")
end
def description
I18n.t("work_packages.delete_dialog.description", name: work_package.to_s)
end
def confirmation_checkbox_text
if has_descendants?
I18n.t("work_packages.delete_dialog.confirm_descendants_deletion")
else
I18n.t("text_permanent_delete_confirmation_checkbox_label")
end
end
def descendants
@descendants ||= WorkPackage
.joins("INNER JOIN work_package_hierarchies ON work_package_hierarchies.descendant_id = work_packages.id")
.where(work_package_hierarchies: { ancestor_id: work_package.id })
.where("work_package_hierarchies.generations > 0")
.includes(:project, :type, :status)
.order("work_package_hierarchies.generations ASC, work_packages.id ASC")
end
def has_descendants?
descendants.any?
end
def cross_project_descendants?
descendants.any? { |d| d.project != work_package.project }
end
def all_project_names
names = descendants
.filter_map(&:project)
.uniq
.reject { |p| p == work_package.project }
.map(&:name)
names
.unshift(work_package.project.name)
.join(", ")
end
def form_action
helpers.work_packages_bulk_path(ids: [work_package.id], back_url: @back_url)
end
end
end
@@ -40,7 +40,7 @@ module WorkPackages
end
def format
raise NotImplementedError, "Must be overridden in subclass"
raise SubclassResponsibilityError
end
def export_settings
@@ -13,7 +13,7 @@ module WorkPackages
@id = id
@tab = tab
@work_package = WorkPackage.visible.find_by(id:)
@work_package = WorkPackage.visible.find_by_display_id(id)
end
def wrapper_uniq_by
@@ -35,7 +35,7 @@ class WorkPackages::HoverCardComponent < ApplicationComponent
super
@id = id
@work_package = WorkPackage.visible.find_by(id:)
@work_package = WorkPackage.visible.find_by_display_id(id)
@assignee = @work_package.present? ? @work_package.assigned_to : nil
end
@@ -1,9 +1,14 @@
<%=
flex_layout(flex_wrap: :wrap) do |flex|
flex_layout(flex_wrap: :wrap, **@system_arguments) do |flex|
if @show_project && !@show_subject
flex.with_column(mr: 2) do
render(Primer::Beta::Text.new(font_size: @font_size)) { "#{@work_package.project.name}: " }
end
end
flex.with_column(mr: 2) do
render(WorkPackages::HighlightedTypeComponent.new(work_package: @work_package, font_size: :small))
end
flex.with_column(mr: 2) do
flex.with_column do
render(
Primer::Beta::Link.new(
href: url_for(controller: "/work_packages", action: "show", id: @work_package),
@@ -13,8 +18,23 @@
)
) { "##{@work_package.id}" }
end
flex.with_column do
render WorkPackages::StatusBadgeComponent.new(status: @work_package.status)
if @show_status
flex.with_column(ml: 2) do
render WorkPackages::StatusBadgeComponent.new(status: @work_package.status)
end
end
if @show_subject
flex.with_column(classes: "ellipsis", ml: 1) do
render(Primer::Beta::Text.new(font_size: @font_size)) do
if @show_project
"#{@work_package.project.name}: #{@work_package.subject}"
else
@work_package.subject
end
end
end
end
end
%>
@@ -31,10 +31,20 @@
class WorkPackages::InfoLineComponent < ApplicationComponent
include OpPrimer::ComponentHelpers
def initialize(work_package:, font_size: :small)
def initialize(work_package:,
show_project: false,
show_subject: false,
show_status: true,
font_size: :small,
**system_arguments)
super
@work_package = work_package
@font_size = font_size
@show_project = show_project
@show_subject = show_subject
@show_status = show_status
@system_arguments = system_arguments
end
end
@@ -39,7 +39,7 @@ class WorkPackages::SplitViewComponent < ApplicationComponent
@id = id
@tab = tab
@work_package = WorkPackage.visible.find_by(id:)
@work_package = WorkPackage.visible.find_by_display_id(id)
@base_route = base_route
end
@@ -32,7 +32,7 @@ See COPYRIGHT and LICENSE files for more details.
blankslate.with_heading(tag: :h2).with_content(t("admin.workflows.blankslate.title"))
blankslate.with_description_content(t("admin.workflows.blankslate.description"))
blankslate.with_primary_action(
href: helpers.status_dialog_workflows_path(role_id: @role.id, type_id: @type.id, tab: @tab),
href: helpers.status_dialog_workflow_tab_path(@type, @tab, role_id: @role.id),
scheme: :secondary,
data: { controller: "async-dialog" }
) do |button|
@@ -1,73 +0,0 @@
<%#-- copyright
OpenProject is an open source project management software.
Copyright (C) the OpenProject GmbH
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See COPYRIGHT and LICENSE files for more details.
++#%>
<%= component_wrapper do %>
<%=
render Primer::OpenProject::SubHeader.new do |subheader|
if @type && @available_roles.any?
subheader.with_filter_component do
render(Primer::Alpha::ActionMenu.new(select_variant: :single)) do |menu|
menu.with_show_button(scheme: :secondary) do |button|
button.with_trailing_visual_icon(icon: :"triangle-down")
@current_role ? t("admin.workflows.role_selector.label", role: @current_role.name) : t("admin.workflows.role_selector.no_role")
end
@available_roles.each do |role|
menu.with_item(
label: role.name,
active: role == @current_role,
href: helpers.confirmation_dialog_workflows_path(
type_id: @type.id,
role_id: @current_role&.id,
next_role_id: role.id,
tab: @tab || "always",
dirty: @dirty
),
form_arguments: {
method: :post,
data: { turbo_stream: true, workflow_role_form: true, turbo_frame: "_top" }
}
)
end
end
end
end
subheader.with_action_button(
tag: :a,
scheme: :secondary,
leading_icon: :plus,
label: t("admin.workflows.status_button"),
href: helpers.status_dialog_workflows_path(role_id: @current_role&.id, type_id: @type&.id, tab: @tab, status_ids: @status_ids.presence),
data: { controller: "async-dialog" }
) do
t("admin.workflows.status_button")
end
end
%>
<% end %>

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