mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Add template assignments and settings page
This commit is contained in:
@@ -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.
|
||||
#++
|
||||
|
||||
class Projects::Settings::SubitemsController < Projects::SettingsController
|
||||
menu_item :settings_subitems
|
||||
|
||||
def show; end
|
||||
|
||||
def update
|
||||
workspace_type = params[:workspace_type]
|
||||
template_id = params[:template_id]
|
||||
|
||||
assignment = @project.subproject_template_assignments.find_or_initialize_by(workspace_type:)
|
||||
|
||||
if template_id.blank?
|
||||
if assignment.persisted?
|
||||
assignment.destroy
|
||||
flash[:notice] = I18n.t(:notice_successful_update)
|
||||
end
|
||||
else
|
||||
assignment.template_id = template_id
|
||||
if assignment.save
|
||||
flash[:notice] = I18n.t(:notice_successful_update)
|
||||
else
|
||||
flash[:error] = assignment.errors.full_messages.join(", ")
|
||||
end
|
||||
end
|
||||
|
||||
redirect_to project_settings_subitems_path(@project)
|
||||
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 Projects
|
||||
module Settings
|
||||
class SubitemsTemplateForm < ApplicationForm
|
||||
attr_reader :project, :assignments
|
||||
|
||||
def initialize(project:)
|
||||
super()
|
||||
|
||||
@project = project
|
||||
@assignments = @project.subproject_template_assignments
|
||||
end
|
||||
|
||||
form do |f|
|
||||
if project.portfolio?
|
||||
program_template = assignments.detect(&:program?)
|
||||
|
||||
f.select_list(
|
||||
name: :program_template,
|
||||
scope_name_to_model: false,
|
||||
label: I18n.t("projects.settings.subitems.program_template_label"),
|
||||
caption: I18n.t("projects.settings.subitems.program_template_caption"),
|
||||
input_width: :large,
|
||||
include_blank: I18n.t("projects.settings.subitems.no_template")
|
||||
) do |list|
|
||||
available_templates
|
||||
.workspace_type(:program)
|
||||
.find_each do |template|
|
||||
list.option(
|
||||
value: template.id,
|
||||
label: template.name,
|
||||
selected: template.id == program_template&.template_id
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
project_template = assignments.detect(&:project?)
|
||||
f.select_list(
|
||||
name: :project_template,
|
||||
scope_name_to_model: false,
|
||||
label: I18n.t("projects.settings.subitems.project_template_label"),
|
||||
caption: I18n.t("projects.settings.subitems.project_template_caption"),
|
||||
input_width: :large,
|
||||
include_blank: I18n.t("projects.settings.subitems.no_template")
|
||||
) do |list|
|
||||
available_templates
|
||||
.workspace_type(:project)
|
||||
.find_each do |template|
|
||||
list.option(
|
||||
value: template.id,
|
||||
label: template.name,
|
||||
selected: template.id == project_template&.template_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
f.submit(
|
||||
name: :submit,
|
||||
label: I18n.t(:button_save),
|
||||
scheme: :primary
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def available_templates
|
||||
Project
|
||||
.visible(User.current)
|
||||
.active
|
||||
.templated
|
||||
.order(name: :asc)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -111,12 +111,19 @@ class Project < ApplicationRecord
|
||||
has_many :recurring_meetings, dependent: :destroy
|
||||
|
||||
belongs_to :template, class_name: "Project", optional: true
|
||||
|
||||
has_many :templated_projects,
|
||||
class_name: "Project",
|
||||
foreign_key: "template_id",
|
||||
inverse_of: :template,
|
||||
dependent: nil
|
||||
|
||||
has_many :subproject_template_assignments,
|
||||
class_name: "SubprojectTemplateAssignment",
|
||||
foreign_key: "template_id",
|
||||
inverse_of: :template,
|
||||
dependent: :delete_all
|
||||
|
||||
accepts_nested_attributes_for :available_phases
|
||||
validates_associated :available_phases, on: :saving_phases
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class SubprojectTemplateAssignment < ApplicationRecord
|
||||
belongs_to :project, inverse_of: :subproject_template_assignments
|
||||
belongs_to :template, class_name: "Project", inverse_of: :template_assignments
|
||||
|
||||
enum :workspace_type, {
|
||||
project: "project",
|
||||
program: "program"
|
||||
}, validate: true
|
||||
|
||||
validates :project_id, presence: true
|
||||
validates :template_id, presence: true
|
||||
validates :workspace_type, presence: true
|
||||
validates :project_id, uniqueness: { scope: :workspace_type }
|
||||
|
||||
validate :template_must_be_templated
|
||||
|
||||
private
|
||||
|
||||
def template_must_be_templated
|
||||
return if template.blank?
|
||||
|
||||
unless template.templated?
|
||||
errors.add(:template, :must_be_template)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
<%#-- 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::PageHeader.new do |header|
|
||||
header.with_title { I18n.t(:label_subitems) }
|
||||
header.with_breadcrumbs(
|
||||
[{ href: project_overview_path(@project.id), text: @project.name },
|
||||
{ href: project_settings_general_path(@project.id), text: I18n.t("label_project_settings") },
|
||||
I18n.t(:label_subitems)]
|
||||
)
|
||||
|
||||
header.with_tab_nav(label: nil) do |tab_nav|
|
||||
tab_nav.with_tab(
|
||||
selected: true,
|
||||
href: project_settings_subitems_path(@project)
|
||||
) do |t|
|
||||
t.with_text { I18n.t(:label_templates) }
|
||||
end
|
||||
end
|
||||
end
|
||||
%>
|
||||
|
||||
<%= error_messages_for(@project) %>
|
||||
|
||||
<%= render Primer::Beta::Text.new { I18n.t("projects.settings.subitems.template_section") } %>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(url: project_settings_subitems_path(@project)) do |f|
|
||||
render(
|
||||
Primer::Forms::FormList.new(
|
||||
Projects::Settings::SubitemsTemplateForm.new(
|
||||
f,
|
||||
project: @project,
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
%>
|
||||
@@ -731,6 +731,7 @@ Redmine::MenuManager.map :project_menu do |menu|
|
||||
if: ->(_) { OpenProject::FeatureDecisions.project_initiation_active? }
|
||||
},
|
||||
modules: { caption: :label_module_plural },
|
||||
subitems: { caption: :label_subitems },
|
||||
work_packages: {
|
||||
caption: :label_work_package_plural,
|
||||
if: ->(project) {
|
||||
|
||||
@@ -153,6 +153,7 @@ Rails.application.reloader.to_prepare do
|
||||
update_artifact_export_settings
|
||||
toggle_project_custom_field
|
||||
disable_all_of_section enable_all_of_section],
|
||||
"projects/settings/subitems": %i[show update],
|
||||
"projects/templated": %i[create destroy],
|
||||
"projects/identifier": %i[show update],
|
||||
"projects/status": %i[update destroy]
|
||||
|
||||
@@ -559,6 +559,14 @@ en:
|
||||
The project will only be visible to project members depending on their role and associated permissions.
|
||||
Sub-projects are not affected and have their own settings.
|
||||
change_identifier: Change identifier
|
||||
subitems:
|
||||
template_section: >
|
||||
Select templates to be used when creating new subitems.
|
||||
project_template_label: "Template for projects"
|
||||
project_template_caption: "Select a template project to be used as the default for new subitems of this type."
|
||||
program_template_label: "Template for programs"
|
||||
program_template_caption: "Select a template program to be used as the default for new subitems of this type."
|
||||
no_template: "No predefined template"
|
||||
actions:
|
||||
label_enable_all: "Enable all"
|
||||
label_disable_all: "Disable all"
|
||||
@@ -3610,6 +3618,7 @@ en:
|
||||
label_subproject: "Subproject"
|
||||
label_subproject_new: "New subproject"
|
||||
label_subproject_plural: "Subprojects"
|
||||
label_subitems: "Subitems"
|
||||
label_subtask_plural: "Subtasks"
|
||||
label_summary: "Summary"
|
||||
label_system: "System"
|
||||
@@ -3617,6 +3626,8 @@ en:
|
||||
label_table_of_contents: "Table of contents"
|
||||
label_tag: "Tag"
|
||||
label_team_planner: "Team Planner"
|
||||
label_template: "Template"
|
||||
label_templates: "Templates"
|
||||
label_text: "Long text"
|
||||
label_this_month: "this month"
|
||||
label_this_week: "this week"
|
||||
|
||||
@@ -285,6 +285,7 @@ Rails.application.routes.draw do
|
||||
post :toggle_public
|
||||
end
|
||||
resource :modules, only: %i[show update]
|
||||
resource :subitems, only: %i[show update]
|
||||
resource :creation_wizard, controller: "creation_wizard", only: %i[show] do
|
||||
get :disable_dialog
|
||||
post :toggle
|
||||
|
||||
@@ -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.
|
||||
#++
|
||||
|
||||
class CreateSubprojectTemplateAssignments < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :subproject_template_assignments do |t|
|
||||
t.references :project, null: false, foreign_key: { on_delete: :cascade }, index: true
|
||||
t.references :template, null: false, foreign_key: { to_table: :projects, on_delete: :cascade }, index: true
|
||||
t.string :workspace_type, null: false, index: true
|
||||
|
||||
t.timestamps
|
||||
|
||||
t.index %i[project_id workspace_type], unique: true, name: "idx_subproj_tmpl_assignments_on_project_workspace"
|
||||
end
|
||||
end
|
||||
end
|
||||
Generated
+1
@@ -424,6 +424,7 @@
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe SubprojectTemplateAssignment do
|
||||
let(:project) { create(:project) }
|
||||
let(:template) { create(:project, templated: true) }
|
||||
|
||||
describe "associations" do
|
||||
it { is_expected.to belong_to(:project).inverse_of(:subproject_template_assignments) }
|
||||
it { is_expected.to belong_to(:template).class_name("Project").inverse_of(:template_assignments) }
|
||||
|
||||
it "allows accessing assignments from project" do
|
||||
assignment = create(:subproject_template_assignment, project:, template:)
|
||||
expect(project.subproject_template_assignments).to include(assignment)
|
||||
end
|
||||
end
|
||||
|
||||
describe "enums" do
|
||||
it {
|
||||
expect(described_class).to define_enum_for(:workspace_type)
|
||||
.with_values(project: "project", program: "program")
|
||||
.backed_by_column_of_type(:string)
|
||||
}
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
subject(:assignment) do
|
||||
build(:subproject_template_assignment,
|
||||
project:,
|
||||
template:,
|
||||
workspace_type: "project")
|
||||
end
|
||||
|
||||
it { is_expected.to validate_presence_of(:project_id) }
|
||||
it { is_expected.to validate_presence_of(:template_id) }
|
||||
it { is_expected.to validate_presence_of(:workspace_type) }
|
||||
|
||||
describe "uniqueness" do
|
||||
before do
|
||||
create(:subproject_template_assignment,
|
||||
project:,
|
||||
template:,
|
||||
workspace_type: "project")
|
||||
end
|
||||
|
||||
it "allows only one assignment per project and workspace_type" do
|
||||
duplicate = build(:subproject_template_assignment,
|
||||
project:,
|
||||
template:,
|
||||
workspace_type: "project")
|
||||
|
||||
expect(duplicate).not_to be_valid
|
||||
expect(duplicate.errors[:project_id]).to include("has already been taken")
|
||||
end
|
||||
|
||||
it "allows different workspace_types for the same project" do
|
||||
different_type = build(:subproject_template_assignment,
|
||||
project:,
|
||||
template:,
|
||||
workspace_type: "program")
|
||||
|
||||
expect(different_type).to be_valid
|
||||
end
|
||||
|
||||
it "allows same workspace_type for different projects" do
|
||||
other_project = create(:project)
|
||||
different_project = build(:subproject_template_assignment,
|
||||
project: other_project,
|
||||
template:,
|
||||
workspace_type: "project")
|
||||
|
||||
expect(different_project).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "template validation" do
|
||||
context "when template is marked as templated" do
|
||||
let(:template) { create(:project, templated: true) }
|
||||
|
||||
it "is valid" do
|
||||
expect(assignment).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context "when template is not marked as templated" do
|
||||
let(:template) { create(:project, templated: false) }
|
||||
|
||||
it "is invalid" do
|
||||
expect(assignment).not_to be_valid
|
||||
expect(assignment.errors[:template]).to include("must be template")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "cascading deletes" do
|
||||
let!(:assignment) do
|
||||
create(:subproject_template_assignment,
|
||||
project:,
|
||||
template:)
|
||||
end
|
||||
|
||||
context "when project is deleted" do
|
||||
it "deletes the assignment via cascade" do
|
||||
expect { project.destroy }.to change(described_class, :count).by(-1)
|
||||
end
|
||||
end
|
||||
|
||||
context "when template is deleted" do
|
||||
it "deletes the assignment via cascade" do
|
||||
expect { template.destroy }.to change(described_class, :count).by(-1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "factory" do
|
||||
it "creates a valid assignment with default attributes" do
|
||||
assignment = build(:subproject_template_assignment)
|
||||
expect(assignment).to be_valid
|
||||
end
|
||||
|
||||
it "creates a valid assignment with :for_project trait" do
|
||||
assignment = build(:subproject_template_assignment, :for_project)
|
||||
expect(assignment).to be_valid
|
||||
expect(assignment.workspace_type).to eq("project")
|
||||
expect(assignment.template.workspace_type).to eq("project")
|
||||
end
|
||||
|
||||
it "creates a valid assignment with :for_program trait" do
|
||||
assignment = build(:subproject_template_assignment, :for_program)
|
||||
expect(assignment).to be_valid
|
||||
expect(assignment.workspace_type).to eq("program")
|
||||
expect(assignment.template.workspace_type).to eq("program")
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user