mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
rename steps and step definitions
This commit is contained in:
@@ -196,7 +196,7 @@ module Projects
|
||||
end
|
||||
|
||||
def project_life_cycle_step_by_definition(definition, project)
|
||||
@project_life_cycle_steps_by_definition ||= Project::LifeCycleStep
|
||||
@project_life_cycle_steps_by_definition ||= Project::Phase
|
||||
.visible
|
||||
.index_by { |s| [s.definition_id, s.project_id] }
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ module ProjectLifeCycleSteps
|
||||
validate :select_custom_fields_permission
|
||||
validate :consecutive_steps_have_increasing_dates
|
||||
|
||||
def valid?(context = :saving_life_cycle_steps) = super
|
||||
def valid?(context = :saving_phases) = super
|
||||
|
||||
def select_custom_fields_permission
|
||||
return if user.allowed_in_project?(:edit_project_stages_and_gates, model)
|
||||
@@ -41,7 +41,7 @@ module ProjectLifeCycleSteps
|
||||
|
||||
def consecutive_steps_have_increasing_dates
|
||||
# Filter out steps with missing dates before proceeding with comparison
|
||||
filtered_steps = model.available_life_cycle_steps.select(&:start_date)
|
||||
filtered_steps = model.available_phases.select(&:start_date)
|
||||
|
||||
# Only proceed with comparisons if there are at least 2 valid steps
|
||||
return if filtered_steps.size < 2
|
||||
@@ -53,7 +53,7 @@ module ProjectLifeCycleSteps
|
||||
field = current_step.is_a?(Project::Stage) ? :date_range : :date
|
||||
model.errors.import(
|
||||
current_step.errors.add(field, :non_continuous_dates, step:),
|
||||
attribute: :"available_life_cycle_steps.#{field}"
|
||||
attribute: :"available_phases.#{field}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -61,7 +61,7 @@ module Admin::Settings
|
||||
end
|
||||
|
||||
def create
|
||||
@definition = Project::LifeCycleStepDefinition.new(definition_params)
|
||||
@definition = Project::PhaseDefinition.new(definition_params)
|
||||
|
||||
if @definition.save
|
||||
flash[:notice] = I18n.t(:notice_successful_create)
|
||||
@@ -123,11 +123,11 @@ module Admin::Settings
|
||||
end
|
||||
|
||||
def find_definitions
|
||||
@definitions = Project::LifeCycleStepDefinition.with_project_count
|
||||
@definitions = Project::PhaseDefinition.with_project_count
|
||||
end
|
||||
|
||||
def find_definition
|
||||
@definition = Project::LifeCycleStepDefinition.find(params[:id])
|
||||
@definition = Project::PhaseDefinition.find(params[:id])
|
||||
end
|
||||
|
||||
def definition_params
|
||||
|
||||
@@ -38,7 +38,7 @@ class Projects::Settings::LifeCycleStepsController < Projects::SettingsControlle
|
||||
def index; end
|
||||
|
||||
def toggle
|
||||
definition = Project::LifeCycleStepDefinition.where(id: params[:id])
|
||||
definition = Project::PhaseDefinition.where(id: params[:id])
|
||||
|
||||
upsert_steps(definition, active: params["value"])
|
||||
end
|
||||
@@ -58,7 +58,7 @@ class Projects::Settings::LifeCycleStepsController < Projects::SettingsControlle
|
||||
private
|
||||
|
||||
def load_life_cycle_definitions
|
||||
@life_cycle_definitions = Project::LifeCycleStepDefinition.order(position: :asc)
|
||||
@life_cycle_definitions = Project::PhaseDefinition.order(position: :asc)
|
||||
end
|
||||
|
||||
def deny_access_on_feature_flag
|
||||
@@ -66,7 +66,7 @@ class Projects::Settings::LifeCycleStepsController < Projects::SettingsControlle
|
||||
end
|
||||
|
||||
def upsert_steps(definitions, active:)
|
||||
Project::LifeCycleStep.upsert_all(
|
||||
Project::Phase.upsert_all(
|
||||
definitions.map do |definition|
|
||||
{
|
||||
project_id: @project.id,
|
||||
|
||||
@@ -4,6 +4,6 @@ module HighlightingHelper
|
||||
end
|
||||
|
||||
def highlight_css_updated_at
|
||||
ApplicationRecord.most_recently_changed Status, IssuePriority, Type, UserPreference, Project::LifeCycleStepDefinition
|
||||
ApplicationRecord.most_recently_changed Status, IssuePriority, Type, UserPreference, Project::PhaseDefinition
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
class Journal::ProjectLifeCycleStepJournal < Journal::AssociatedJournal
|
||||
self.table_name = "project_life_cycle_step_journals"
|
||||
|
||||
belongs_to :life_cycle_step, class_name: "Project::LifeCycleStep"
|
||||
belongs_to :phase, class_name: "Project::LifeCycleStep"
|
||||
|
||||
alias_attribute :date, :start_date
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ class PermittedParams
|
||||
|
||||
def project_life_cycles
|
||||
params.require(:project).permit(
|
||||
available_life_cycle_steps_attributes: %i[id date date_range]
|
||||
available_phases_attributes: %i[id date date_range]
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -86,17 +86,17 @@ class Project < ApplicationRecord
|
||||
has_many :notification_settings, dependent: :destroy
|
||||
has_many :project_storages, dependent: :destroy, class_name: "Storages::ProjectStorage"
|
||||
has_many :storages, through: :project_storages
|
||||
has_many :life_cycle_steps, class_name: "Project::LifeCycleStep", dependent: :destroy
|
||||
has_many :available_life_cycle_steps,
|
||||
has_many :phases, class_name: "Project::Phase", dependent: :destroy
|
||||
has_many :available_phases,
|
||||
-> { visible.eager_load(:definition).order(position: :asc) },
|
||||
class_name: "Project::LifeCycleStep",
|
||||
class_name: "Project::Phase",
|
||||
inverse_of: :project,
|
||||
dependent: :destroy
|
||||
|
||||
has_many :recurring_meetings, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :available_life_cycle_steps
|
||||
validates_associated :available_life_cycle_steps, on: :saving_life_cycle_steps
|
||||
accepts_nested_attributes_for :available_phases
|
||||
validates_associated :available_phase, on: :saving_phases
|
||||
|
||||
store_attribute :settings, :deactivate_work_package_attachments, :boolean
|
||||
|
||||
|
||||
@@ -1,45 +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.
|
||||
#++
|
||||
|
||||
class Project::Gate < Project::LifeCycleStep
|
||||
alias_attribute :date, :start_date
|
||||
|
||||
# This ensures the type cannot be changed after initialising the class.
|
||||
validates :type, inclusion: { in: %w[Project::Gate], message: :must_be_a_gate }
|
||||
validate :end_date_not_allowed
|
||||
|
||||
def end_date_not_allowed
|
||||
if end_date.present?
|
||||
errors.add(:base, :end_date_not_allowed)
|
||||
end
|
||||
end
|
||||
|
||||
def not_set?
|
||||
date.blank?
|
||||
end
|
||||
end
|
||||
@@ -1,62 +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.
|
||||
#++
|
||||
|
||||
class Project::LifeCycleStep < ApplicationRecord
|
||||
belongs_to :project, optional: false, inverse_of: :available_life_cycle_steps
|
||||
belongs_to :definition,
|
||||
optional: false,
|
||||
class_name: "Project::LifeCycleStepDefinition"
|
||||
has_many :work_packages, inverse_of: :project_life_cycle_step, dependent: :nullify
|
||||
|
||||
delegate :name, :position, to: :definition
|
||||
|
||||
attr_readonly :definition_id, :type
|
||||
|
||||
validates :type, inclusion: { in: %w[Project::Stage Project::Gate], message: :must_be_a_stage_or_gate }
|
||||
validate :validate_type_and_class_name_are_identical
|
||||
|
||||
scope :active, -> { where(active: true) }
|
||||
|
||||
class << self
|
||||
def visible(user = User.current)
|
||||
allowed_projects = Project.allowed_to(user, :view_project_stages_and_gates)
|
||||
active.where(project: allowed_projects)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_type_and_class_name_are_identical
|
||||
if type != self.class.name
|
||||
errors.add(:type, :type_and_class_name_mismatch)
|
||||
end
|
||||
end
|
||||
|
||||
def column_name
|
||||
# The id of the associated definition is relevant for displaying the correct column headers
|
||||
"lcsd_#{definition_id}"
|
||||
end
|
||||
end
|
||||
@@ -26,11 +26,28 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Project::Stage < Project::LifeCycleStep
|
||||
# This ensures the type cannot be changed after initialising the class.
|
||||
validates :type, inclusion: { in: %w[Project::Stage], message: :must_be_a_stage }
|
||||
class Project::Phase < ApplicationRecord
|
||||
belongs_to :project, optional: false, inverse_of: :available_phases
|
||||
belongs_to :definition,
|
||||
optional: false,
|
||||
class_name: "Project::PhaseDefinition"
|
||||
has_many :work_packages, inverse_of: :project_phase, dependent: :nullify
|
||||
|
||||
validate :validate_date_range
|
||||
|
||||
delegate :name, :position, to: :definition
|
||||
|
||||
attr_readonly :definition_id, :type
|
||||
|
||||
scope :active, -> { where(active: true) }
|
||||
|
||||
class << self
|
||||
def visible(user = User.current)
|
||||
allowed_projects = Project.allowed_to(user, :view_project_stages_and_gates)
|
||||
active.where(project: allowed_projects)
|
||||
end
|
||||
end
|
||||
|
||||
def working_days_count
|
||||
return nil if not_set?
|
||||
|
||||
@@ -61,4 +78,9 @@ class Project::Stage < Project::LifeCycleStep
|
||||
errors.add(:date_range, :start_date_must_be_before_end_date)
|
||||
end
|
||||
end
|
||||
|
||||
def column_name
|
||||
# The id of the associated definition is relevant for displaying the correct column headers
|
||||
"lcsd_#{definition_id}"
|
||||
end
|
||||
end
|
||||
+7
-20
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -26,22 +28,18 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Project::LifeCycleStepDefinition < ApplicationRecord
|
||||
class Project::PhaseDefinition < ApplicationRecord
|
||||
include ::Scopes::Scoped
|
||||
|
||||
has_many :life_cycle_steps,
|
||||
class_name: "Project::LifeCycleStep",
|
||||
has_many :phases,
|
||||
class_name: "Project::Phase",
|
||||
foreign_key: :definition_id,
|
||||
inverse_of: :definition,
|
||||
dependent: :destroy
|
||||
has_many :projects, through: :life_cycle_steps
|
||||
has_many :projects, through: :phases
|
||||
belongs_to :color, optional: false
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :type, inclusion: { in: %w[Project::StageDefinition Project::GateDefinition], message: :must_be_a_stage_or_gate }
|
||||
validate :validate_type_and_class_name_are_identical
|
||||
|
||||
attr_readonly :type
|
||||
|
||||
acts_as_list
|
||||
|
||||
@@ -49,19 +47,8 @@ class Project::LifeCycleStepDefinition < ApplicationRecord
|
||||
|
||||
scopes :with_project_count
|
||||
|
||||
def step_class
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def column_name
|
||||
# TODO: rename
|
||||
"lcsd_#{id}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_type_and_class_name_are_identical
|
||||
if type != self.class.name
|
||||
errors.add(:type, :type_and_class_name_mismatch)
|
||||
end
|
||||
end
|
||||
end
|
||||
+3
-3
@@ -28,18 +28,18 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Project::LifeCycleStepDefinitions::Scopes
|
||||
module Project::PhaseDefinitions::Scopes
|
||||
module WithProjectCount
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def with_project_count
|
||||
project_counts = Project::LifeCycleStep
|
||||
project_counts = Project::Phase
|
||||
.where(active: true)
|
||||
.group(:definition_id)
|
||||
.select(:definition_id, "COUNT(project_id) AS count")
|
||||
|
||||
Project::LifeCycleStepDefinition
|
||||
Project::PhaseDefinition
|
||||
.with(project_counts:)
|
||||
.joins("LEFT OUTER JOIN project_counts ON #{quoted_table_name}.id = project_counts.definition_id")
|
||||
.select("#{quoted_table_name}.*")
|
||||
@@ -69,7 +69,7 @@ module Queries::Projects::Filters::DynamicallyFromLifeCycle
|
||||
key = %w[Queries::Projects::Filters::LifeCycleStepFilter all_step_definitions]
|
||||
|
||||
RequestStore
|
||||
.fetch(key) { Project::LifeCycleStepDefinition.all.to_a }
|
||||
.fetch(key) { Project::PhaseDefinition.all.to_a }
|
||||
.select { |lcsd| lcsd.is_a?(step_subclass) }
|
||||
end
|
||||
|
||||
|
||||
@@ -100,14 +100,14 @@ module Queries::Projects::Filters::FilterOnLifeCycle
|
||||
|
||||
def stage_where_on(start_date, end_date = start_date)
|
||||
life_cycle_scope(Project::Stage.name)
|
||||
.where(date_range_clause(Project::LifeCycleStep.table_name, "start_date", nil, start_date))
|
||||
.where(date_range_clause(Project::LifeCycleStep.table_name, "end_date", end_date, nil))
|
||||
.where(date_range_clause(Project::Phase.table_name, "start_date", nil, start_date))
|
||||
.where(date_range_clause(Project::Phase.table_name, "end_date", end_date, nil))
|
||||
end
|
||||
|
||||
def stage_where_between(start_date, end_date)
|
||||
life_cycle_scope(Project::Stage.name)
|
||||
.where(date_range_clause(Project::LifeCycleStep.table_name, "start_date", start_date, nil))
|
||||
.where(date_range_clause(Project::LifeCycleStep.table_name, "end_date", nil, end_date))
|
||||
.where(date_range_clause(Project::Phase.table_name, "start_date", start_date, nil))
|
||||
.where(date_range_clause(Project::Phase.table_name, "end_date", nil, end_date))
|
||||
end
|
||||
|
||||
def stage_overlaps_this_week
|
||||
@@ -116,8 +116,8 @@ module Queries::Projects::Filters::FilterOnLifeCycle
|
||||
.where.not(end_date: nil)
|
||||
.where(
|
||||
<<~SQL.squish, beginning_of_week, end_of_week
|
||||
daterange(#{Project::LifeCycleStep.table_name}.start_date,
|
||||
#{Project::LifeCycleStep.table_name}.end_date,
|
||||
daterange(#{Project::Phase.table_name}.start_date,
|
||||
#{Project::Phase.table_name}.end_date,
|
||||
'[]')
|
||||
&&
|
||||
daterange(?, ?, '[]')
|
||||
@@ -140,7 +140,7 @@ module Queries::Projects::Filters::FilterOnLifeCycle
|
||||
def gate_where(start_date, end_date = start_date)
|
||||
# On gates, only the start_date is set.
|
||||
life_cycle_scope(Project::Gate.name)
|
||||
.where(date_range_clause(Project::LifeCycleStep.table_name, "start_date", start_date, end_date))
|
||||
.where(date_range_clause(Project::Phase.table_name, "start_date", start_date, end_date))
|
||||
end
|
||||
|
||||
def parsed_start
|
||||
@@ -164,8 +164,8 @@ module Queries::Projects::Filters::FilterOnLifeCycle
|
||||
end
|
||||
|
||||
def life_cycle_scope(type)
|
||||
life_cycle_scope = Project::LifeCycleStep
|
||||
.where("#{Project::LifeCycleStep.table_name}.project_id = #{Project.table_name}.id")
|
||||
life_cycle_scope = Project::Phase
|
||||
.where("#{Project::Phase.table_name}.project_id = #{Project.table_name}.id")
|
||||
.where(project_id: Project.allowed_to(User.current, :view_project_stages_and_gates))
|
||||
.where(type:)
|
||||
.active
|
||||
|
||||
@@ -40,7 +40,7 @@ class Queries::Projects::Orders::LifeCycleStepOrder < Queries::Orders::Base
|
||||
def life_cycle_step_definition
|
||||
return @life_cycle_step_definition if defined?(@life_cycle_step_definition)
|
||||
|
||||
@life_cycle_step_definition = Project::LifeCycleStepDefinition.find_by(id: attribute[/\Alcsd_(\d+)\z/, 1])
|
||||
@life_cycle_step_definition = Project::PhaseDefinition.find_by(id: attribute[/\Alcsd_(\d+)\z/, 1])
|
||||
end
|
||||
|
||||
def available?
|
||||
|
||||
@@ -38,7 +38,7 @@ class Queries::Projects::Selects::LifeCycleStep < Queries::Selects::Base
|
||||
def self.all_available
|
||||
return [] unless available?
|
||||
|
||||
Project::LifeCycleStepDefinition
|
||||
Project::PhaseDefinition
|
||||
.pluck(:id)
|
||||
.map { |id| new(:"lcsd_#{id}") }
|
||||
end
|
||||
@@ -50,7 +50,7 @@ class Queries::Projects::Selects::LifeCycleStep < Queries::Selects::Base
|
||||
def life_cycle_step_definition
|
||||
return @life_cycle_step_definition if defined?(@life_cycle_step_definition)
|
||||
|
||||
@life_cycle_step_definition = Project::LifeCycleStepDefinition
|
||||
@life_cycle_step_definition = Project::PhaseDefinition
|
||||
.find_by(id: attribute[KEY, 1])
|
||||
end
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class WorkPackage < ApplicationRecord
|
||||
belongs_to :assigned_to, class_name: "Principal", optional: true
|
||||
belongs_to :responsible, class_name: "Principal", optional: true
|
||||
belongs_to :version, optional: true
|
||||
belongs_to :project_life_cycle_step, class_name: "Project::LifeCycleStep", optional: true
|
||||
belongs_to :project_phase, class_name: "Project::Phase", optional: true
|
||||
belongs_to :priority, class_name: "IssuePriority"
|
||||
belongs_to :category, class_name: "Category", optional: true
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#++
|
||||
module BasicData
|
||||
class LifeCycleStepDefinitionSeeder < ModelSeeder
|
||||
self.model_class = Project::LifeCycleStepDefinition
|
||||
self.model_class = Project::PhaseDefinition
|
||||
self.seed_data_model_key = "life_cycles"
|
||||
self.needs = [
|
||||
BasicData::LifeCycleColorSeeder
|
||||
|
||||
@@ -79,7 +79,7 @@ class Journals::CreateService
|
||||
FROM project_phases
|
||||
WHERE project_phases.project_id = :journable_id) phases
|
||||
ON
|
||||
phases.id = project_phase_journals.life_cycle_step_id
|
||||
phases.id = project_phase_journals.phase_id
|
||||
WHERE
|
||||
phases.start_date IS DISTINCT FROM project_phase_journals.start_date
|
||||
OR phases.end_date IS DISTINCT FROM project_phase_journals.end_date
|
||||
|
||||
@@ -39,7 +39,7 @@ module ProjectLifeCycleSteps
|
||||
def clear_unchanged_fields(service_call)
|
||||
service_call
|
||||
.result
|
||||
.available_life_cycle_steps
|
||||
.available_phases
|
||||
.select(&:not_set?)
|
||||
.each { _1.errors.clear }
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
<%=
|
||||
admin_settings_primer_form_with(
|
||||
model: [:admin, :settings, @definition.becomes(Project::LifeCycleStepDefinition)]
|
||||
model: [:admin, :settings, @definition.becomes(Project::PhaseDefinition)]
|
||||
) do |f|
|
||||
render Projects::LifeCycleStepDefinitions::Form.new(f)
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<%= resources_scope_color_css("priority", ::IssuePriority) %>
|
||||
<%= resources_scope_color_css("type", ::Type, inline_foreground: true) %>
|
||||
<%# Color coded icons %>
|
||||
<%= resources_scope_color_css("life_cycle_step_definition", Project::LifeCycleStepDefinition, inline_foreground: true) %>
|
||||
<%= resources_scope_color_css("life_cycle_step_definition", Project::PhaseDefinition, inline_foreground: true) %>
|
||||
|
||||
<% Meetings::Statuses::AVAILABLE.each do |meeting_status| %>
|
||||
<%= resource_color_css("meeting_status", meeting_status) %>
|
||||
|
||||
@@ -1034,9 +1034,9 @@ en:
|
||||
project_custom_field:
|
||||
is_required: "Required for all projects"
|
||||
custom_field_section: Section
|
||||
project/life_cycle_step:
|
||||
project/phase:
|
||||
definition: "Definition"
|
||||
project/life_cycle_step_definition:
|
||||
project/phase_definition:
|
||||
name: "Name"
|
||||
color: "Color"
|
||||
query:
|
||||
|
||||
@@ -103,6 +103,10 @@ class MergeLifecycleSteps < ActiveRecord::Migration[8.0]
|
||||
change_table(:project_life_cycle_step_journals) do |t|
|
||||
t.rename :life_cycle_step_id, :phase_id
|
||||
end
|
||||
|
||||
change_table(:work_packages) do |t|
|
||||
t.rename :project_life_cycle_step_id, :project_phase_id
|
||||
end
|
||||
end
|
||||
|
||||
def rename_tables
|
||||
|
||||
@@ -32,7 +32,7 @@ class OpenProject::JournalFormatter::ProjectLifeCycleStepActive < JournalFormatt
|
||||
def render(key, values, options = { html: true })
|
||||
return if !values[0] == !values[1]
|
||||
|
||||
step = Project::LifeCycleStep.find(key[/\d+/])
|
||||
step = Project::Phase.find(key[/\d+/])
|
||||
|
||||
name = step.definition.name
|
||||
label = options[:html] ? content_tag(:strong, name) : name
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
class OpenProject::JournalFormatter::ProjectLifeCycleStepDates < JournalFormatter::Base
|
||||
def render(key, values, options = { html: true })
|
||||
step = Project::LifeCycleStep.find(key[/\d+/])
|
||||
step = Project::Phase.find(key[/\d+/])
|
||||
|
||||
name = step.definition.name
|
||||
label = options[:html] ? content_tag(:strong, name) : name
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
url: update_project_life_cycles_path(project_id: model.id)
|
||||
) do |f|
|
||||
render(Primer::Forms::SpacingWrapper.new) do
|
||||
f.fields_for(:available_life_cycle_steps) do |life_cycle_form|
|
||||
f.fields_for(:available_phases) do |life_cycle_form|
|
||||
render(Projects::LifeCycles::Form.new(life_cycle_form))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,7 +37,7 @@ module ProjectLifeCycles
|
||||
super
|
||||
|
||||
@project = project
|
||||
@life_cycle_steps = @project.available_life_cycle_steps
|
||||
@life_cycle_steps = @project.available_phases
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -39,7 +39,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do
|
||||
|
||||
context "with authorized user" do
|
||||
let(:user) { build_stubbed(:user) }
|
||||
let(:project) { build_stubbed(:project, available_life_cycle_steps: steps) }
|
||||
let(:project) { build_stubbed(:project, available_phases: steps) }
|
||||
let(:steps) { [] }
|
||||
|
||||
before do
|
||||
@@ -81,7 +81,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do
|
||||
let(:steps) { [gate3, gate1] }
|
||||
|
||||
it_behaves_like "contract is invalid",
|
||||
"available_life_cycle_steps.date": :non_continuous_dates
|
||||
"available_phases.date": :non_continuous_dates
|
||||
|
||||
it "adds an error to the decreasing step" do
|
||||
contract.validate
|
||||
@@ -93,7 +93,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do
|
||||
let(:steps) { [gate3, stage2] }
|
||||
|
||||
it_behaves_like "contract is invalid",
|
||||
"available_life_cycle_steps.date_range": :non_continuous_dates
|
||||
"available_phases.date_range": :non_continuous_dates
|
||||
|
||||
it "adds an error to the decreasing step" do
|
||||
contract.validate
|
||||
@@ -107,7 +107,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do
|
||||
let(:steps) { [gate1, step4] }
|
||||
|
||||
it_behaves_like "contract is invalid",
|
||||
"available_life_cycle_steps.date": :non_continuous_dates
|
||||
"available_phases.date": :non_continuous_dates
|
||||
end
|
||||
|
||||
context "when steps have touching start and end dates" do
|
||||
@@ -116,14 +116,14 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do
|
||||
let(:steps) { [stage2, stage4] }
|
||||
|
||||
it_behaves_like "contract is invalid",
|
||||
"available_life_cycle_steps.date_range": :non_continuous_dates
|
||||
"available_phases.date_range": :non_continuous_dates
|
||||
|
||||
context "when having an empty step in between" do
|
||||
let(:step_missing_dates) { build_stubbed(:project_stage, start_date: nil, end_date: nil) }
|
||||
let(:steps) { [stage2, step_missing_dates, stage4] }
|
||||
|
||||
it_behaves_like "contract is invalid",
|
||||
"available_life_cycle_steps.date_range": :non_continuous_dates
|
||||
"available_phases.date_range": :non_continuous_dates
|
||||
end
|
||||
end
|
||||
|
||||
@@ -132,14 +132,14 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do
|
||||
let(:steps) { [gate1, gate4] }
|
||||
|
||||
it_behaves_like "contract is invalid",
|
||||
"available_life_cycle_steps.date": :non_continuous_dates
|
||||
"available_phases.date": :non_continuous_dates
|
||||
|
||||
context "when having an empty step in between" do
|
||||
let(:step_missing_dates) { build_stubbed(:project_stage, start_date: nil, end_date: nil) }
|
||||
let(:steps) { [gate1, step_missing_dates, gate4] }
|
||||
|
||||
it_behaves_like "contract is invalid",
|
||||
"available_life_cycle_steps.date": :non_continuous_dates
|
||||
"available_phases.date": :non_continuous_dates
|
||||
end
|
||||
end
|
||||
|
||||
@@ -200,7 +200,7 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do
|
||||
let(:steps) { [stage2, step_missing_dates, gate1] }
|
||||
|
||||
it_behaves_like "contract is invalid",
|
||||
"available_life_cycle_steps.date": :non_continuous_dates
|
||||
"available_phases.date": :non_continuous_dates
|
||||
|
||||
it "adds an error to the decreasing step" do
|
||||
contract.validate
|
||||
@@ -211,11 +211,11 @@ RSpec.describe ProjectLifeCycleSteps::BaseContract do
|
||||
end
|
||||
|
||||
describe "triggering validations on the model" do
|
||||
it "sets the :saving_life_cycle_steps validation context" do
|
||||
it "sets the :saving_phases validation context" do
|
||||
allow(project).to receive(:valid?)
|
||||
|
||||
contract.validate
|
||||
expect(project).to have_received(:valid?).with(:saving_life_cycle_steps)
|
||||
expect(project).to have_received(:valid?).with(:saving_phases)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,41 +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.
|
||||
#++
|
||||
|
||||
FactoryBot.define do
|
||||
factory :project_life_cycle_step_definition, class: "Project::LifeCycleStepDefinition" do
|
||||
color
|
||||
|
||||
factory :project_gate_definition, class: "Project::GateDefinition" do
|
||||
sequence(:name) { |n| "Gate Definition No. #{n}" }
|
||||
end
|
||||
|
||||
factory :project_stage_definition, class: "Project::StageDefinition" do
|
||||
sequence(:name) { |n| "Stage Definition No. #{n}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,49 +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.
|
||||
#++
|
||||
|
||||
FactoryBot.define do
|
||||
factory :project_life_cycle_step, class: "Project::LifeCycleStep" do
|
||||
project
|
||||
active { true }
|
||||
|
||||
trait :skip_validate do
|
||||
to_create { |instance| instance.save(validate: false) }
|
||||
end
|
||||
|
||||
factory :project_stage, class: "Project::Stage" do
|
||||
definition factory: :project_stage_definition
|
||||
start_date { Date.current - 2.days }
|
||||
end_date { Date.current + 2.days }
|
||||
end
|
||||
|
||||
factory :project_gate, class: "Project::Gate" do
|
||||
definition factory: :project_gate_definition
|
||||
date { Date.current + 2.days }
|
||||
end
|
||||
end
|
||||
end
|
||||
+4
-8
@@ -26,14 +26,10 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Project::GateDefinition < Project::LifeCycleStepDefinition
|
||||
has_many :gates, # Alias for life_cycle_steps
|
||||
class_name: "Project::Gate",
|
||||
foreign_key: :definition_id,
|
||||
inverse_of: :definition,
|
||||
dependent: :destroy
|
||||
FactoryBot.define do
|
||||
factory :project_phase_definition, class: "Project::PhaseDefinition" do
|
||||
sequence(:name) { |n| "Phase definition #{n}" }
|
||||
|
||||
def step_class
|
||||
Project::Gate
|
||||
color
|
||||
end
|
||||
end
|
||||
@@ -26,14 +26,13 @@
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Project::StageDefinition < Project::LifeCycleStepDefinition
|
||||
has_many :stages, # Alias for life_cycle_steps
|
||||
class_name: "Project::Stage",
|
||||
foreign_key: :definition_id,
|
||||
inverse_of: :definition,
|
||||
dependent: :destroy
|
||||
FactoryBot.define do
|
||||
factory :project_phase, class: "Project::Phase" do
|
||||
project
|
||||
definition factory: :project_phase_definition
|
||||
active { true }
|
||||
|
||||
def step_class
|
||||
Project::Stage
|
||||
start_date { Date.current - 2.days }
|
||||
end_date { Date.current + 2.days }
|
||||
end
|
||||
end
|
||||
@@ -47,7 +47,7 @@ RSpec.describe "Edit project stages and gates on project overview page", :js, wi
|
||||
describe "with the dialog open" do
|
||||
context "when all LifeCycleSteps are blank" do
|
||||
before do
|
||||
Project::LifeCycleStep.update_all(start_date: nil, end_date: nil)
|
||||
Project::Phase.update_all(start_date: nil, end_date: nil)
|
||||
end
|
||||
|
||||
it "shows all the Project::LifeCycleSteps without a value" do
|
||||
@@ -80,7 +80,7 @@ RSpec.describe "Edit project stages and gates on project overview page", :js, wi
|
||||
|
||||
expect_angular_frontend_initialized
|
||||
|
||||
project.available_life_cycle_steps.each do |step|
|
||||
project.available_phases.each do |step|
|
||||
dialog.expect_input_for(step)
|
||||
end
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ RSpec.describe "Show project life cycles on project overview page", :js, with_fl
|
||||
|
||||
describe "with no values" do
|
||||
before do
|
||||
Project::LifeCycleStep.update_all(start_date: nil, end_date: nil)
|
||||
Project::Phase.update_all(start_date: nil, end_date: nil)
|
||||
end
|
||||
|
||||
it "shows the correct value for the project custom field if given" do
|
||||
|
||||
@@ -37,7 +37,7 @@ RSpec.describe OpenProject::JournalFormatter::ProjectLifeCycleStepActive do
|
||||
subject(:result) { described_class.new(nil).render(key, values, html:) }
|
||||
|
||||
before do
|
||||
allow(Project::LifeCycleStep).to receive(:find).with(step.id.to_s).and_return(step)
|
||||
allow(Project::Phase).to receive(:find).with(step.id.to_s).and_return(step)
|
||||
end
|
||||
|
||||
def date(day) = Date.new(2025, 1, day)
|
||||
|
||||
@@ -37,7 +37,7 @@ RSpec.describe OpenProject::JournalFormatter::ProjectLifeCycleStepDates do
|
||||
subject(:result) { described_class.new(nil).render(key, values, html:) }
|
||||
|
||||
before do
|
||||
allow(Project::LifeCycleStep).to receive(:find).with(step.id.to_s).and_return(step)
|
||||
allow(Project::Phase).to receive(:find).with(step.id.to_s).and_return(step)
|
||||
end
|
||||
|
||||
def date(day) = Date.new(2025, 1, day)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
require "support/shared/project_life_cycle_helpers"
|
||||
|
||||
RSpec.describe Project::GateDefinition do
|
||||
it_behaves_like "a Project::LifeCycleStepDefinition event"
|
||||
|
||||
describe "associations" do
|
||||
it "has many gates" do
|
||||
expect(subject).to have_many(:gates).class_name("Project::Gate")
|
||||
.with_foreign_key(:definition_id)
|
||||
.inverse_of(:definition)
|
||||
.dependent(:destroy)
|
||||
end
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
it "is invalid if type and class name do not match" do
|
||||
subject.type = "Project::StageDefinition"
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#step_class" do
|
||||
it "returns Project::Stage" do
|
||||
expect(subject.step_class).to eq(Project::Gate)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,57 +0,0 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
require "support/shared/project_life_cycle_helpers"
|
||||
|
||||
RSpec.describe Project::Gate do
|
||||
it_behaves_like "a Project::LifeCycleStep event"
|
||||
|
||||
describe "validations" do
|
||||
it { is_expected.to validate_inclusion_of(:type).in_array(["Project::Gate"]).with_message(:must_be_a_gate) }
|
||||
|
||||
it "is invalid if `end_date` is present" do
|
||||
subject.end_date = Time.zone.today
|
||||
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors[:base])
|
||||
.to include("Cannot assign end date to a Project::Gate")
|
||||
end
|
||||
|
||||
it "is valid if `end_date` is not present" do
|
||||
valid_gate = build(:project_gate, end_date: nil)
|
||||
expect(valid_gate).to be_valid
|
||||
end
|
||||
|
||||
it "is invalid if type and class name do not match" do
|
||||
subject.type = "Project::Stage"
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,77 +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.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Project::LifeCycleStep do
|
||||
it "can be instantiated" do
|
||||
expect { described_class.new }.not_to raise_error(NotImplementedError)
|
||||
end
|
||||
|
||||
describe "with an instantiated Gate" do
|
||||
subject { build :project_gate }
|
||||
|
||||
it { is_expected.to have_readonly_attribute(:definition_id) }
|
||||
it { is_expected.to have_readonly_attribute(:type) }
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
it "is invalid if type and class name do not match" do
|
||||
subject.type = "Project::Gate"
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".visible" do
|
||||
let(:project) { create(:project) }
|
||||
let(:development_project) { create(:project) }
|
||||
let(:user) do
|
||||
create(:user,
|
||||
member_with_permissions:
|
||||
{ project => %i(view_project view_project_stages_and_gates),
|
||||
development_project => %i(view_project) })
|
||||
end
|
||||
|
||||
let!(:life_cycle_gate) { create(:project_gate, project:) }
|
||||
let!(:life_cycle_stage) { create(:project_stage, project:) }
|
||||
let!(:life_cycle_stage_dev) { create(:project_stage, project: development_project) }
|
||||
let!(:inactive_life_cycle_gate) { create(:project_gate, project:, active: false) }
|
||||
let!(:inactive_life_cycle_stage) { create(:project_stage, project: development_project, active: false) }
|
||||
|
||||
it "returns active steps where the user has a view_project_stages_and_gates permission" do
|
||||
expected_steps = [life_cycle_gate, life_cycle_stage]
|
||||
expect(described_class.visible(user)).to eq(expected_steps)
|
||||
end
|
||||
end
|
||||
|
||||
# For more specs see:
|
||||
# - spec/support/shared/project_life_cycle_helpers.rb
|
||||
# - spec/models/project/gate_spec.rb
|
||||
# - spec/models/project/stage_spec.rb
|
||||
end
|
||||
+14
-23
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
@@ -28,36 +30,25 @@
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Project::LifeCycleStepDefinition do
|
||||
RSpec.describe Project::PhaseDefinition do
|
||||
it "can be instantiated" do
|
||||
expect { described_class.new }.not_to raise_error
|
||||
end
|
||||
|
||||
context "with a Project::StageDefinition" do
|
||||
subject { create :project_stage_definition }
|
||||
|
||||
it { is_expected.to have_readonly_attribute(:type) }
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_uniqueness_of(:name) }
|
||||
|
||||
it {
|
||||
expect(subject).to validate_inclusion_of(:type)
|
||||
.in_array(%w[Project::StageDefinition Project::GateDefinition])
|
||||
.with_message(:must_be_a_stage_or_gate)
|
||||
}
|
||||
|
||||
it "is invalid if type and class name do not match" do
|
||||
subject.type = "Project::GateDefinition"
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch)
|
||||
end
|
||||
end
|
||||
|
||||
# For more specs see:
|
||||
# - spec/support/shared/project_life_cycle_helpers.rb
|
||||
# - spec/models/project/gate_definition_spec.rb
|
||||
# - spec/models/project/stage_definition_spec.rb
|
||||
describe "associations" do
|
||||
it "has many phases" do
|
||||
expect(subject).to have_many(:phases).class_name("Project::Phase")
|
||||
.with_foreign_key(:definition_id)
|
||||
.inverse_of(:definition)
|
||||
.dependent(:destroy)
|
||||
end
|
||||
|
||||
it { is_expected.to have_many(:projects).through(:phases) }
|
||||
it { is_expected.to belong_to(:color).required }
|
||||
end
|
||||
end
|
||||
+2
-2
@@ -30,7 +30,7 @@
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Project::LifeCycleStepDefinitions::Scopes::WithProjectCount do
|
||||
RSpec.describe Project::PhaseDefinitions::Scopes::WithProjectCount do
|
||||
let!(:definition_a) { create(:project_stage_definition, name: "foo") }
|
||||
let!(:definition_b) { create(:project_gate_definition, name: "bar") }
|
||||
let!(:definition_c) { create(:project_stage_definition, name: "baz") }
|
||||
@@ -49,7 +49,7 @@ RSpec.describe Project::LifeCycleStepDefinitions::Scopes::WithProjectCount do
|
||||
|
||||
describe ".with_project_count" do
|
||||
it "queries project counts alongside definitions" do
|
||||
expect(Project::LifeCycleStepDefinition.with_project_count).to contain_exactly(
|
||||
expect(Project::PhaseDefinition.with_project_count).to contain_exactly(
|
||||
having_attributes(id: definition_a.id, name: "foo", project_count: 2),
|
||||
having_attributes(id: definition_b.id, name: "bar", project_count: 1),
|
||||
having_attributes(id: definition_c.id, name: "baz", project_count: 0)
|
||||
@@ -27,17 +27,36 @@
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
require "support/shared/project_life_cycle_helpers"
|
||||
|
||||
RSpec.describe Project::Stage do
|
||||
it_behaves_like "a Project::LifeCycleStep event"
|
||||
RSpec.describe Project::Phase do
|
||||
it "can be instantiated" do
|
||||
expect { described_class.new }.not_to raise_error(NotImplementedError)
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
it { is_expected.to validate_inclusion_of(:type).in_array(["Project::Stage"]).with_message(:must_be_a_stage) }
|
||||
it { is_expected.to have_readonly_attribute(:definition_id) }
|
||||
|
||||
it "is valid when `start_date` and `end_date` are present" do
|
||||
valid_stage = build(:project_stage)
|
||||
expect(valid_stage).to be_valid
|
||||
describe "associations" do
|
||||
it { is_expected.to belong_to(:project).required }
|
||||
it { is_expected.to belong_to(:definition).required }
|
||||
it { is_expected.to have_many(:work_packages) }
|
||||
end
|
||||
|
||||
describe ".visible" do
|
||||
let(:project) { create(:project) }
|
||||
let(:development_project) { create(:project) }
|
||||
let(:user) do
|
||||
create(:user,
|
||||
member_with_permissions:
|
||||
{ project => %i(view_project view_project_stages_and_gates),
|
||||
development_project => %i(view_project) })
|
||||
end
|
||||
|
||||
let!(:phase) { create(:project_phase, project:) }
|
||||
let!(:phase_dev) { create(:project_phase, project: development_project) }
|
||||
let!(:inactive_phase) { create(:project_phase, project: development_project, active: false) }
|
||||
|
||||
it "returns active phases where the user has a view_project_stages_and_gates permission" do
|
||||
expect(described_class.visible(user)).to contain_exactly(phase)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -69,7 +88,7 @@ RSpec.describe Project::Stage do
|
||||
|
||||
describe "#validate_date_range" do
|
||||
it "is valid when both dates are blank" do
|
||||
stage = build(:project_stage, start_date: nil, end_date: nil)
|
||||
stage = build(:project_phase, start_date: nil, end_date: nil)
|
||||
expect(stage).to be_valid
|
||||
end
|
||||
|
||||
@@ -98,12 +117,6 @@ RSpec.describe Project::Stage do
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors[:date_range]).to be_empty
|
||||
end
|
||||
|
||||
it "is invalid if type and class name do not match" do
|
||||
subject.type = "Project::Gate"
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#working_days_count" do
|
||||
@@ -135,8 +148,8 @@ RSpec.describe Project::Stage do
|
||||
|
||||
allow(Day).to receive(:working).and_return(Day)
|
||||
allow(Day).to receive(:from_range)
|
||||
.with(from: subject.start_date, to: subject.end_date)
|
||||
.and_return([])
|
||||
.with(from: subject.start_date, to: subject.end_date)
|
||||
.and_return([])
|
||||
|
||||
expect(subject.working_days_count).to eq(0)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "rails_helper"
|
||||
require "support/shared/project_life_cycle_helpers"
|
||||
|
||||
RSpec.describe Project::StageDefinition do
|
||||
it_behaves_like "a Project::LifeCycleStepDefinition event"
|
||||
|
||||
describe "associations" do
|
||||
it "has many stages" do
|
||||
expect(subject).to have_many(:stages).class_name("Project::Stage")
|
||||
.with_foreign_key(:definition_id)
|
||||
.inverse_of(:definition)
|
||||
.dependent(:destroy)
|
||||
end
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
it "is invalid if type and class name do not match" do
|
||||
subject.type = "Project::GateDefinition"
|
||||
expect(subject).not_to be_valid
|
||||
expect(subject.errors.symbols_for(:type)).to include(:type_and_class_name_mismatch)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#step_class" do
|
||||
it "returns Project::Stage" do
|
||||
expect(subject.step_class).to eq(Project::Stage)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -383,8 +383,8 @@ RSpec.describe Project do
|
||||
describe "life_cycles" do
|
||||
it { is_expected.to have_many(:life_cycle_steps).class_name("Project::LifeCycleStep").dependent(:destroy) }
|
||||
|
||||
it "has many available_life_cycle_steps" do
|
||||
expect(subject).to have_many(:available_life_cycle_steps)
|
||||
it "has many available_phases" do
|
||||
expect(subject).to have_many(:available_phases)
|
||||
.class_name("Project::LifeCycleStep")
|
||||
.inverse_of(:project)
|
||||
.dependent(:destroy)
|
||||
@@ -392,18 +392,18 @@ RSpec.describe Project do
|
||||
end
|
||||
|
||||
it "checks for active flag" do
|
||||
expect(subject.available_life_cycle_steps.to_sql)
|
||||
expect(subject.available_phases.to_sql)
|
||||
.to include("\"project_life_cycle_steps\".\"active\" = TRUE")
|
||||
end
|
||||
|
||||
it "checks for :view_project_stages_and_gates permission" do
|
||||
project_condition = described_class.allowed_to(User.current, :view_project_stages_and_gates).select(:id)
|
||||
|
||||
expect(subject.available_life_cycle_steps.to_sql).to include(project_condition.to_sql)
|
||||
expect(subject.available_phases.to_sql).to include(project_condition.to_sql)
|
||||
end
|
||||
|
||||
it "eager loads :definition" do
|
||||
expect(subject.available_life_cycle_steps.to_sql)
|
||||
expect(subject.available_phases.to_sql)
|
||||
.to include("LEFT OUTER JOIN \"project_life_cycle_step_definitions\" ON")
|
||||
end
|
||||
|
||||
@@ -419,8 +419,8 @@ RSpec.describe Project do
|
||||
expect(project).to be_valid
|
||||
end
|
||||
|
||||
it "is invalid with the :saving_life_cycle_steps validation context" do
|
||||
expect(project).not_to be_valid(:saving_life_cycle_steps)
|
||||
it "is invalid with the :saving_phases validation context" do
|
||||
expect(project).not_to be_valid(:saving_phases)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ RSpec.describe Queries::Projects::Filters::LifeCycleGateFilter do
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Project::LifeCycleStepDefinition)
|
||||
allow(Project::PhaseDefinition)
|
||||
.to receive(:all)
|
||||
.and_return([stage, gate])
|
||||
end
|
||||
|
||||
@@ -41,7 +41,7 @@ RSpec.describe Queries::Projects::Filters::LifeCycleStageFilter do
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Project::LifeCycleStepDefinition)
|
||||
allow(Project::PhaseDefinition)
|
||||
.to receive(:all)
|
||||
.and_return([stage, gate])
|
||||
end
|
||||
|
||||
@@ -104,11 +104,11 @@ RSpec.describe Queries::Projects::Orders::LifeCycleStepOrder do
|
||||
let(:id) { 42 }
|
||||
|
||||
before do
|
||||
allow(Project::LifeCycleStepDefinition).to receive(:find_by).with(id: id.to_s).and_return(step_definition)
|
||||
allow(Project::PhaseDefinition).to receive(:find_by).with(id: id.to_s).and_return(step_definition)
|
||||
end
|
||||
|
||||
context "when life cycle definition exists" do
|
||||
let(:step_definition) { instance_double(Project::LifeCycleStepDefinition) }
|
||||
let(:step_definition) { instance_double(Project::PhaseDefinition) }
|
||||
|
||||
it "returns the life cycle definition" do
|
||||
expect(instance.life_cycle_step_definition).to eq(step_definition)
|
||||
@@ -117,7 +117,7 @@ RSpec.describe Queries::Projects::Orders::LifeCycleStepOrder do
|
||||
it "memoizes the life cycle definition" do
|
||||
2.times { instance.life_cycle_step_definition }
|
||||
|
||||
expect(Project::LifeCycleStepDefinition).to have_received(:find_by).once
|
||||
expect(Project::PhaseDefinition).to have_received(:find_by).once
|
||||
end
|
||||
end
|
||||
|
||||
@@ -131,7 +131,7 @@ RSpec.describe Queries::Projects::Orders::LifeCycleStepOrder do
|
||||
it "memoizes the life cycle" do
|
||||
2.times { instance.life_cycle_step_definition }
|
||||
|
||||
expect(Project::LifeCycleStepDefinition).to have_received(:find_by).once
|
||||
expect(Project::PhaseDefinition).to have_received(:find_by).once
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -61,25 +61,25 @@ RSpec.describe ProjectQuery, "results of 'Any stage or gate' filter" do
|
||||
# rubocop:disable RSpec/ScatteredSetup
|
||||
def self.remove_gate
|
||||
before do
|
||||
Project::LifeCycleStep.where(type: Project::Gate.name).destroy_all
|
||||
Project::Phase.where(type: Project::Gate.name).destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
def self.remove_stage
|
||||
before do
|
||||
Project::LifeCycleStep.where(type: Project::Stage.name).destroy_all
|
||||
Project::Phase.where(type: Project::Stage.name).destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
def self.disable_stage
|
||||
before do
|
||||
Project::LifeCycleStep.where(type: Project::Stage.name).update_all(active: false)
|
||||
Project::Phase.where(type: Project::Stage.name).update_all(active: false)
|
||||
end
|
||||
end
|
||||
|
||||
def self.disable_gate
|
||||
before do
|
||||
Project::LifeCycleStep.where(type: Project::Gate.name).update_all(active: false)
|
||||
Project::Phase.where(type: Project::Gate.name).update_all(active: false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ RSpec.describe ProjectQuery, "results of a life cycle gate filter" do
|
||||
# rubocop:disable RSpec/ScatteredSetup
|
||||
def self.disable_gate
|
||||
before do
|
||||
Project::LifeCycleStep.where(type: Project::Gate.name).update_all(active: false)
|
||||
Project::Phase.where(type: Project::Gate.name).update_all(active: false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ RSpec.describe ProjectQuery, "results of a life cycle stage filter" do
|
||||
# rubocop:disable RSpec/ScatteredSetup
|
||||
def self.disable_stage
|
||||
before do
|
||||
Project::LifeCycleStep.where(type: Project::Stage.name).update_all(active: false)
|
||||
Project::Phase.where(type: Project::Stage.name).update_all(active: false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ RSpec.describe BasicData::LifeCycleStepDefinitionSeeder do
|
||||
end
|
||||
|
||||
it "creates the corresponding life cycles with the given attributes" do
|
||||
expect(Project::LifeCycleStepDefinition.count).to eq(5)
|
||||
expect(Project::PhaseDefinition.count).to eq(5)
|
||||
expect(Project::StageDefinition.find_by(name: "Initiating")).to have_attributes(
|
||||
color: have_attributes(name: "PM2 Orange")
|
||||
)
|
||||
@@ -87,7 +87,7 @@ RSpec.describe BasicData::LifeCycleStepDefinitionSeeder do
|
||||
end
|
||||
|
||||
it "references the life cycles in the seed data" do
|
||||
Project::LifeCycleStepDefinition.find_each do |expected_stage|
|
||||
Project::PhaseDefinition.find_each do |expected_stage|
|
||||
reference = :"default_life_cycle_#{expected_stage.name.downcase.gsub(/\s+/, '_')}"
|
||||
expect(seed_data.find_reference(reference)).to eq(expected_stage)
|
||||
end
|
||||
@@ -126,7 +126,7 @@ RSpec.describe BasicData::LifeCycleStepDefinitionSeeder do
|
||||
end
|
||||
|
||||
it "creates no life cycles" do
|
||||
expect(Project::LifeCycleStepDefinition.count).to eq(0)
|
||||
expect(Project::PhaseDefinition.count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -59,7 +59,7 @@ module Components
|
||||
end
|
||||
|
||||
datepicker.open(
|
||||
"input[id^='project_available_life_cycle_steps_attributes_#{step.position - 1}']"
|
||||
"input[id^='project_available_phases_attributes_#{step.position - 1}']"
|
||||
)
|
||||
|
||||
Array(value).each do |date|
|
||||
@@ -104,7 +104,7 @@ module Components
|
||||
expect(page).to have_field(
|
||||
label,
|
||||
with: value,
|
||||
name: "project[available_life_cycle_steps_attributes][#{position - 1}][#{field}]"
|
||||
name: "project[available_phases_attributes][#{position - 1}][#{field}]"
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -144,7 +144,7 @@ module Components
|
||||
def expect_selector_for(step, selector:, text: nil, present: true)
|
||||
within_async_content do
|
||||
field = step.is_a?(Project::Stage) ? :date_range : :date
|
||||
input_id = "#project_available_life_cycle_steps_attributes_#{step.position - 1}_#{field}"
|
||||
input_id = "#project_available_phases_attributes_#{step.position - 1}_#{field}"
|
||||
parent = find(input_id).ancestor("primer-datepicker-field")
|
||||
|
||||
if present
|
||||
|
||||
@@ -1,72 +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.
|
||||
#++
|
||||
|
||||
RSpec.shared_examples_for "a Project::LifeCycleStepDefinition event" do
|
||||
it "inherits from LifeCycleStepDefinition" do
|
||||
expect(described_class).to be < Project::LifeCycleStepDefinition
|
||||
end
|
||||
|
||||
describe "associations" do
|
||||
it { is_expected.to have_many(:life_cycle_steps).inverse_of(:definition).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:projects).through(:life_cycle_steps) }
|
||||
it { is_expected.to belong_to(:color).required }
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
|
||||
it "is invalid if type is not Project::StageDefinition or Project::GateDefinition" do
|
||||
life_cycle = described_class.new
|
||||
life_cycle.type = "InvalidType"
|
||||
expect(life_cycle).not_to be_valid
|
||||
expect(life_cycle.errors[:type])
|
||||
.to include("must be either Project::StageDefinition or Project::GateDefinition")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples_for "a Project::LifeCycleStep event" do
|
||||
it "inherits from Project::LifeCycleStep" do
|
||||
expect(described_class).to be < Project::LifeCycleStep
|
||||
end
|
||||
|
||||
describe "associations" do
|
||||
it { is_expected.to belong_to(:project).required }
|
||||
it { is_expected.to belong_to(:definition).required }
|
||||
it { is_expected.to have_many(:work_packages) }
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
it "is invalid if type is not Project::Stage or Project::Gate" do
|
||||
life_cycle = described_class.new
|
||||
life_cycle.type = "InvalidType"
|
||||
expect(life_cycle).not_to be_valid
|
||||
expect(life_cycle.errors[:type]).to include("must be either Project::Stage or Project::Gate")
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user