diff --git a/app/models/custom_actions/actions/assigned_to.rb b/app/models/custom_actions/actions/assigned_to.rb index 241c640230..28f8152cb2 100644 --- a/app/models/custom_actions/actions/assigned_to.rb +++ b/app/models/custom_actions/actions/assigned_to.rb @@ -29,28 +29,12 @@ #++ class CustomActions::Actions::AssignedTo < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Associated + include CustomActions::Actions::Strategies::MeAssociated def self.key :assigned_to end - def associated - [[current_user_value_key, I18n.t('custom_actions.actions.assigned_to.executing_user_value')]] + available_principles - end - - def values=(values) - values = Array(values).map do |v| - if v == current_user_value_key - v - else - to_integer_or_nil(v) - end - end - - @values = values.uniq - end - def available_principles principal_class .not_locked @@ -63,39 +47,6 @@ class CustomActions::Actions::AssignedTo < CustomActions::Actions::Base work_package.assigned_to_id = transformed_value(values.first) end - ## - # Returns the me value if the user is logged - def transformed_value(val) - return val unless has_me_value? - - if User.current.logged? - User.current.id - end - end - - def current_user_value_key - 'current_user'.freeze - end - - def has_me_value? - values.first == current_user_value_key - end - - def validate(errors) - super - validate_me_value(errors) - end - - private - - def validate_me_value(errors) - if has_me_value? && !User.current.logged? - errors.add :actions, - :not_logged_in, - name: human_name - end - end - def principal_class Principal end diff --git a/app/models/custom_actions/actions/custom_field.rb b/app/models/custom_actions/actions/custom_field.rb index 53c0e97857..d622dae650 100644 --- a/app/models/custom_actions/actions/custom_field.rb +++ b/app/models/custom_actions/actions/custom_field.rb @@ -90,7 +90,9 @@ class CustomActions::Actions::CustomField < CustomActions::Actions::Base CustomActions::Actions::Strategies::Date when 'bool' CustomActions::Actions::Strategies::Boolean - when 'list', 'version', 'user' + when 'user' + CustomActions::Actions::Strategies::UserCustomField + when 'list', 'version' CustomActions::Actions::Strategies::AssociatedCustomField end end diff --git a/app/models/custom_actions/actions/strategies/me_associated.rb b/app/models/custom_actions/actions/strategies/me_associated.rb new file mode 100644 index 0000000000..e265fedb0e --- /dev/null +++ b/app/models/custom_actions/actions/strategies/me_associated.rb @@ -0,0 +1,84 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. +#++ + +module CustomActions::Actions::Strategies::MeAssociated + include ::CustomActions::Actions::Strategies::Associated + + def associated + me_value = [current_user_value_key, I18n.t('custom_actions.actions.assigned_to.executing_user_value')] + + [me_value] + available_principles + end + + def values=(values) + values = Array(values).map do |v| + if v == current_user_value_key + v + else + to_integer_or_nil(v) + end + end + + @values = values.uniq + end + + ## + # Returns the me value if the user is logged + def transformed_value(val) + return val unless has_me_value? + + if User.current.logged? + User.current.id + end + end + + def current_user_value_key + 'current_user'.freeze + end + + def has_me_value? + values.first == current_user_value_key + end + + def validate(errors) + super + validate_me_value(errors) + end + + private + + def validate_me_value(errors) + if has_me_value? && !User.current.logged? + errors.add :actions, + :not_logged_in, + name: human_name + end + end +end diff --git a/app/models/custom_actions/actions/strategies/user_custom_field.rb b/app/models/custom_actions/actions/strategies/user_custom_field.rb new file mode 100644 index 0000000000..56e9bf399a --- /dev/null +++ b/app/models/custom_actions/actions/strategies/user_custom_field.rb @@ -0,0 +1,46 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. +#++ + +module CustomActions::Actions::Strategies::UserCustomField + include CustomActions::Actions::Strategies::CustomField + include ::CustomActions::Actions::Strategies::MeAssociated + + def apply(work_package) + if work_package.respond_to?(:"#{custom_field.accessor_name}=") + work_package.send(:"#{custom_field.accessor_name}=", transformed_value(values.first)) + end + end + + def available_principles + custom_field + .possible_values_options + .map { |label, value| [value.empty? ? nil : value.to_i, label] } + end +end diff --git a/spec/features/work_packages/custom_actions/custom_actions_me_value_spec.rb b/spec/features/work_packages/custom_actions/custom_actions_me_value_spec.rb new file mode 100644 index 0000000000..900caeb531 --- /dev/null +++ b/spec/features/work_packages/custom_actions/custom_actions_me_value_spec.rb @@ -0,0 +1,86 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 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 docs/COPYRIGHT.rdoc for more details. +#++ + +require 'spec_helper' + +describe 'Custom actions me value', type: :feature, js: true do + shared_let(:admin) { FactoryBot.create :admin } + + let(:permissions) { %i(view_work_packages edit_work_packages) } + let(:role) { FactoryBot.create(:role, permissions: permissions) } + let(:user) do + FactoryBot.create(:user, + member_in_project: project, + member_through_role: role) + end + let(:type) { FactoryBot.create(:type_task) } + let(:project) { FactoryBot.create(:project, types: [type], name: 'This project') } + let!(:custom_field) { FactoryBot.create :user_wp_custom_field, types: [type], projects: [project] } + let!(:work_package) do + FactoryBot.create(:work_package, + type: type, + project: project) + end + + let(:wp_page) { Pages::FullWorkPackage.new(work_package) } + let(:default_priority) do + FactoryBot.create(:default_priority, name: 'Normal') + end + let(:index_ca_page) { Pages::Admin::CustomActions::Index.new } + + before do + with_enterprise_token(:custom_actions) + login_as(admin) + end + + it 'can assign user custom field to self' do + # create custom action 'Unassign' + index_ca_page.visit! + + new_ca_page = index_ca_page.new + retry_block do + new_ca_page.visit! + new_ca_page.set_name('Set CF to me') + new_ca_page.add_action(custom_field.name, I18n.t('custom_actions.actions.assigned_to.executing_user_value')) + end + + new_ca_page.create + + assign = CustomAction.last + expect(assign.actions.length).to eq(1) + expect(assign.conditions.length).to eq(0) + expect(assign.actions.first.values).to eq(['current_user']) + + login_as user + wp_page.visit! + + wp_page.expect_custom_action('Set CF to me') + wp_page.click_custom_action('Set CF to me') + wp_page.expect_attributes "customField#{custom_field.id}": user.name + end +end diff --git a/spec/features/work_packages/custom_actions_spec.rb b/spec/features/work_packages/custom_actions/custom_actions_spec.rb similarity index 100% rename from spec/features/work_packages/custom_actions_spec.rb rename to spec/features/work_packages/custom_actions/custom_actions_spec.rb diff --git a/spec/models/custom_actions/actions/custom_field_spec.rb b/spec/models/custom_actions/actions/custom_field_spec.rb index 31bd0a0a30..f467868fc3 100644 --- a/spec/models/custom_actions/actions/custom_field_spec.rb +++ b/spec/models/custom_actions/actions/custom_field_spec.rb @@ -243,6 +243,60 @@ describe CustomActions::Actions::CustomField, type: :model do expect(instance.type) .to eql(:associated_property) end + + describe 'current_user special value' do + let(:work_package) { FactoryBot.build_stubbed(:work_package) } + let(:user) { FactoryBot.build_stubbed(:user) } + + before do + allow(work_package).to receive(:available_custom_fields).and_return([custom_field]) + instance.values = ['current_user'] + end + + it 'can set the value' do + expect(instance).to have_me_value + end + + it 'includes the value in available_values' do + expect(instance.associated) + .to include([instance.current_user_value_key, I18n.t('custom_actions.actions.assigned_to.executing_user_value')]) + end + + context 'when logged in' do + before do + login_as user + end + + it 'sets the current user' do + instance.apply work_package + expect(work_package.custom_value_for(custom_field).value).to eq(user.id.to_s) + end + + it 'validates the me value when executing' do + errors = ActiveModel::Errors.new(CustomAction.new) + instance.validate errors + expect(errors.symbols_for(:actions)).to be_empty + end + end + + context 'when not logged in' do + before do + login_as User.anonymous + end + + it 'returns nil for the current user id' do + instance.apply work_package + expect(work_package.custom_value_for(custom_field).value).to be_nil + end + + it 'validates the me value when executing' do + errors = ActiveModel::Errors.new(CustomAction.new) + instance.validate errors + expect(errors.symbols_for(:actions)).to include :not_logged_in + end + end + end + end context 'for an int custom field' do @@ -372,8 +426,8 @@ describe CustomActions::Actions::CustomField, type: :model do .and_return(users) end let(:expected) do - users - .map { |u| { value: u.id, label: u.name } } + values = [{ label: "(Assign to executing user)", value: "current_user" }] + values + users.map { |u| { value: u.id, label: u.name } } end context 'for a non required field' do